You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2026/02/12 09:22:59 UTC
(sedona) 03/03: fix: [EWT-4149] Fix the geometry factory of geometries produced by proj4sedona transform (#585)
This is an automated email from the ASF dual-hosted git repository.
jiayu pushed a commit to branch proj4sedona
in repository https://gitbox.apache.org/repos/asf/sedona.git
commit 0a97e7caa92e3d64bd1ba2ce3e389b83c159d56d
Author: Kristin Cowalcijk <bo...@wherobots.com>
AuthorDate: Fri Jan 30 01:17:16 2026 +0800
fix: [EWT-4149] Fix the geometry factory of geometries produced by proj4sedona transform (#585)
---
.../org/apache/sedona/common/Constructors.java | 18 ++++++------
.../java/org/apache/sedona/common/Functions.java | 4 +--
.../org/apache/sedona/common/FunctionsProj4.java | 16 ++++++-----
.../sql/sedona_sql/expressions/Functions.scala | 32 +++++++++++++++-------
4 files changed, 41 insertions(+), 29 deletions(-)
diff --git a/common/src/main/java/org/apache/sedona/common/Constructors.java b/common/src/main/java/org/apache/sedona/common/Constructors.java
index 6542e691a2..d44aede0e8 100644
--- a/common/src/main/java/org/apache/sedona/common/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/Constructors.java
@@ -264,20 +264,24 @@ public class Constructors {
}
public static Geometry polygonFromEnvelope(double minX, double minY, double maxX, double maxY) {
+ return polygonFromEnvelope(minX, minY, maxX, maxY, GEOMETRY_FACTORY);
+ }
+
+ public static Geometry polygonFromEnvelope(
+ double minX, double minY, double maxX, double maxY, GeometryFactory factory) {
Coordinate[] coordinates = new Coordinate[5];
coordinates[0] = new Coordinate(minX, minY);
coordinates[1] = new Coordinate(minX, maxY);
coordinates[2] = new Coordinate(maxX, maxY);
coordinates[3] = new Coordinate(maxX, minY);
coordinates[4] = coordinates[0];
- return GEOMETRY_FACTORY.createPolygon(coordinates);
+ return factory.createPolygon(coordinates);
}
public static Geometry makeEnvelope(
double minX, double minY, double maxX, double maxY, int srid) {
- Geometry envelope = polygonFromEnvelope(minX, minY, maxX, maxY);
- envelope.setSRID(srid);
- return envelope;
+ GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
+ return polygonFromEnvelope(minX, minY, maxX, maxY, geometryFactory);
}
public static Geometry makeEnvelope(double minX, double minY, double maxX, double maxY) {
@@ -315,10 +319,6 @@ public class Constructors {
buffer.get(wkb);
- Geometry geom = geomFromWKB(wkb);
-
- geom.setSRID(srid);
-
- return geom;
+ return geomFromWKB(wkb, srid);
}
}
diff --git a/common/src/main/java/org/apache/sedona/common/Functions.java b/common/src/main/java/org/apache/sedona/common/Functions.java
index 62a42e5384..22b9a2fbf4 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -270,9 +270,7 @@ public class Functions {
newCoords[4] = newCoords[0];
return geometry.getFactory().createPolygon(newCoords);
}
- Geometry result = Constructors.polygonFromEnvelope(minX, minY, maxX, maxY);
- result.setSRID(geometry.getSRID());
- return result;
+ return Constructors.polygonFromEnvelope(minX, minY, maxX, maxY, geometry.getFactory());
}
public static Geometry buffer(Geometry geometry, double radius) {
diff --git a/common/src/main/java/org/apache/sedona/common/FunctionsProj4.java b/common/src/main/java/org/apache/sedona/common/FunctionsProj4.java
index a00bfcf21f..b5a5c1c43e 100644
--- a/common/src/main/java/org/apache/sedona/common/FunctionsProj4.java
+++ b/common/src/main/java/org/apache/sedona/common/FunctionsProj4.java
@@ -125,11 +125,11 @@ public class FunctionsProj4 {
Integer sourceSRID = extractEpsgCode(effectiveSourceCRS);
Integer targetSRID = extractEpsgCode(targetCRS);
- if (sourceSRID != null && targetSRID != null && sourceSRID.equals(targetSRID)) {
+ if (sourceSRID != null && sourceSRID.equals(targetSRID)) {
// Same CRS, just update SRID if needed
if (geometry.getSRID() != targetSRID) {
Geometry result = geometry.copy();
- result.setSRID(targetSRID);
+ result = Functions.setSRID(result, targetSRID);
result.setUserData(geometry.getUserData());
return result;
}
@@ -144,17 +144,14 @@ public class FunctionsProj4 {
Geometry transformed = transformer.transform(geometry);
// Set SRID on result
- if (targetSRID != null) {
- transformed.setSRID(targetSRID);
- } else {
- // Try to identify EPSG code from the target CRS
+ if (targetSRID == null) {
try {
Proj targetProj = new Proj(targetCRS);
String epsgCode = CRSSerializer.toEpsgCode(targetProj);
if (epsgCode != null) {
Integer epsg = extractEpsgCode(epsgCode);
if (epsg != null) {
- transformed.setSRID(epsg);
+ targetSRID = epsg;
}
}
} catch (Exception e) {
@@ -162,6 +159,11 @@ public class FunctionsProj4 {
}
}
+ if (targetSRID == null) {
+ targetSRID = 0;
+ }
+ transformed = Functions.setSRID(transformed, targetSRID);
+
// Preserve user data
transformed.setUserData(geometry.getUserData());
diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
index 3018e8540e..565a9e9957 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Functions.scala
@@ -304,13 +304,22 @@ private[apache] case class ST_Centroid(inputExpressions: Seq[Expression])
* during Spark's query optimization phases like constant folding.
*
* @param inputExpressions
+ * @param useGeoTools
*/
-private[apache] case class ST_Transform(inputExpressions: Seq[Expression])
+private[apache] case class ST_Transform(inputExpressions: Seq[Expression], useGeoTools: Boolean)
extends InferredExpression(
inferrableFunction4(FunctionsProj4.transform),
inferrableFunction3(FunctionsProj4.transform),
inferrableFunction2(FunctionsProj4.transform)) {
+ def this(inputExpressions: Seq[Expression]) {
+ // We decide whether to use GeoTools based on active session config.
+ // SparkSession may not be available on executors, so we need to
+ // construct ST_Transform on driver. useGeoTools will be passed down
+ // to executors through object serialization/deserialization.
+ this(inputExpressions, ST_Transform.useGeoTools())
+ }
+
// Define proj4sedona function overloads (2, 3, 4-arg versions)
// Note: 4-arg version ignores the lenient parameter
private lazy val proj4Functions: Seq[InferrableFunction] = Seq(
@@ -327,15 +336,6 @@ private[apache] case class ST_Transform(inputExpressions: Seq[Expression])
override lazy val f: InferrableFunction = {
// Check config to decide between proj4sedona and GeoTools
// Note: 4-arg lenient parameter is ignored by proj4sedona
- val useGeoTools =
- try {
- SedonaConf.fromActiveSession().getCRSTransformMode.useGeoToolsForVector()
- } catch {
- case _: Exception =>
- // If no active session, fall back to default (proj4sedona)
- false
- }
-
val candidateFunctions = if (useGeoTools) geoToolsFunctions else proj4Functions
FunctionResolver.resolveFunction(inputExpressions, candidateFunctions)
}
@@ -345,6 +345,18 @@ private[apache] case class ST_Transform(inputExpressions: Seq[Expression])
}
}
+object ST_Transform {
+ private def useGeoTools(): Boolean = {
+ try {
+ SedonaConf.fromActiveSession().getCRSTransformMode.useGeoToolsForVector()
+ } catch {
+ case _: Exception =>
+ // If no active session, fall back to default (proj4sedona)
+ false
+ }
+ }
+}
+
/**
* Return the intersection shape of two geometries. The return type is a geometry
*