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
  *

Reply via email to