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 <[email protected]> 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 *
