This is an automated email from the ASF dual-hosted git repository.

jiayu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 658bf6054 [SEDONA-626] Make ST functions return geometries with 
correct SRIDs (#1521)
658bf6054 is described below

commit 658bf60548be0fa3c60ea8b0a9637dabab4b30bc
Author: Kristin Cowalcijk <[email protected]>
AuthorDate: Sun Jul 14 09:59:19 2024 +0800

    [SEDONA-626] Make ST functions return geometries with correct SRIDs (#1521)
    
    * Deserialized geometries have factories with proper SRID values
    
    * The SRIDs of geometries could be persisted in transformations
    
    * Fix handling of SRIDs for hex-wkb constructors
    
    * Fix handling of SRIDs for hex-wkb constructors
    
    * Add Spark tests for SRID preserving ST functions
---
 .../org/apache/sedona/common/Constructors.java     |  33 +++--
 .../java/org/apache/sedona/common/Functions.java   | 144 ++++++++++---------
 .../apache/sedona/common/FunctionsGeoTools.java    |  41 +++++-
 .../sedona/common/geometryObjects/Circle.java      |   3 +-
 .../common/geometrySerde/GeometrySerializer.java   | 124 +++++++++--------
 .../sedona/common/raster/GeometryFunctions.java    |  18 +--
 .../sedona/common/simplify/BaseSimplifier.java     |   6 +-
 .../simplify/GeometryCollectionSimplifier.java     |   1 +
 .../common/simplify/LineStringSimplifier.java      |   2 +
 .../sedona/common/simplify/PolygonSimplifier.java  |   4 +-
 .../common/subDivide/GeometrySubDivider.java       |   1 -
 .../sedona/common/subDivide/PivotFinder.java       |   3 +-
 .../apache/sedona/common/utils/FormatUtils.java    |   8 +-
 .../org/apache/sedona/common/utils/GeomUtils.java  |  29 ++--
 .../common/utils/GeometryForce3DTransformer.java   |  52 +++++++
 .../org/apache/sedona/common/utils/H3Utils.java    |   3 +-
 .../org/apache/sedona/common/ConstructorsTest.java |  42 ++++++
 .../org/apache/sedona/common/FunctionsTest.java    |  67 ++++++---
 docs/api/sql/Constructor.md                        |  36 -----
 .../sedona/flink/expressions/Constructors.java     |  23 ++-
 .../sedona/snowflake/snowsql/GeometrySerde.java    |   4 +-
 .../org/apache/sedona/snowflake/snowsql/UDFs.java  |   6 +-
 .../sql/sedona_sql/expressions/Constructors.scala  |  29 ++--
 .../sql/sedona_sql/expressions/Functions.scala     |   4 +-
 .../sedona_sql/expressions/st_constructors.scala   |  12 +-
 .../org/apache/sedona/sql/PreserveSRIDSuite.scala  | 154 +++++++++++++++++++++
 .../apache/sedona/sql/constructorTestScala.scala   |  11 ++
 .../org/apache/sedona/sql/functionTestScala.scala  |  14 +-
 28 files changed, 581 insertions(+), 293 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 572fba660..4789f4430 100644
--- a/common/src/main/java/org/apache/sedona/common/Constructors.java
+++ b/common/src/main/java/org/apache/sedona/common/Constructors.java
@@ -66,16 +66,28 @@ public class Constructors {
   }
 
   public static Geometry geomFromWKB(byte[] wkb) throws ParseException {
-    return new WKBReader().read(wkb);
+    return geomFromWKB(wkb, -1);
+  }
+
+  public static Geometry geomFromWKB(byte[] wkb, int SRID) throws 
ParseException {
+    Geometry geom = new WKBReader().read(wkb);
+    if (geom.getFactory().getSRID() != geom.getSRID() || (SRID >= 0 && 
geom.getSRID() != SRID)) {
+      // Make sure that the geometry and the geometry factory have the correct 
SRID
+      if (SRID < 0) {
+        SRID = geom.getSRID();
+      }
+      return Functions.setSRID(geom, SRID);
+    } else {
+      return geom;
+    }
   }
 
   public static Geometry pointFromWKB(byte[] wkb) throws ParseException {
-    return pointFromWKB(wkb, 0);
+    return pointFromWKB(wkb, -1);
   }
 
   public static Geometry pointFromWKB(byte[] wkb, int srid) throws 
ParseException {
-    GeometryFactory geometryFactory = new GeometryFactory(new 
PrecisionModel(), srid);
-    Geometry geom = new WKBReader(geometryFactory).read(wkb);
+    Geometry geom = geomFromWKB(wkb, srid);
     if (!(geom instanceof Point)) {
       return null;
     }
@@ -83,12 +95,11 @@ public class Constructors {
   }
 
   public static Geometry lineFromWKB(byte[] wkb) throws ParseException {
-    return lineFromWKB(wkb, 0);
+    return lineFromWKB(wkb, -1);
   }
 
   public static Geometry lineFromWKB(byte[] wkb, int srid) throws 
ParseException {
-    GeometryFactory geometryFactory = new GeometryFactory(new 
PrecisionModel(), srid);
-    Geometry geom = new WKBReader(geometryFactory).read(wkb);
+    Geometry geom = geomFromWKB(wkb, srid);
     if (!(geom instanceof LineString)) {
       return null;
     }
@@ -141,17 +152,15 @@ public class Constructors {
    */
   public static Geometry point(double x, double y) {
     // See srid parameter discussion in 
https://issues.apache.org/jira/browse/SEDONA-234
-    GeometryFactory geometryFactory = new GeometryFactory();
-    return geometryFactory.createPoint(new Coordinate(x, y));
+    return GEOMETRY_FACTORY.createPoint(new Coordinate(x, y));
   }
 
   public static Geometry makePointM(double x, double y, double m) {
-    GeometryFactory geometryFactory = new GeometryFactory();
-    return geometryFactory.createPoint(new CoordinateXYM(x, y, m));
+    return GEOMETRY_FACTORY.createPoint(new CoordinateXYM(x, y, m));
   }
 
   public static Geometry makePoint(Double x, Double y, Double z, Double m) {
-    GeometryFactory geometryFactory = new GeometryFactory();
+    GeometryFactory geometryFactory = GEOMETRY_FACTORY;
     if (x == null || y == null) {
       return null;
     }
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 ebedb1762..a43519db1 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -37,7 +37,6 @@ import 
org.locationtech.jts.algorithm.construct.LargestEmptyCircle;
 import org.locationtech.jts.algorithm.construct.MaximumInscribedCircle;
 import org.locationtech.jts.algorithm.hull.ConcaveHull;
 import org.locationtech.jts.geom.*;
-import org.locationtech.jts.geom.impl.CoordinateArraySequence;
 import org.locationtech.jts.geom.util.AffineTransformation;
 import org.locationtech.jts.geom.util.GeometryFixer;
 import org.locationtech.jts.io.ByteOrderValues;
@@ -65,10 +64,6 @@ import 
org.locationtech.jts.triangulate.polygon.ConstrainedDelaunayTriangulator;
 import org.wololo.jts2geojson.GeoJSONWriter;
 
 public class Functions {
-  private static final GeometryFactory GEOMETRY_FACTORY = new 
GeometryFactory();
-  private static Geometry EMPTY_POLYGON = GEOMETRY_FACTORY.createPolygon(null, 
null);
-  private static GeometryCollection EMPTY_GEOMETRY_COLLECTION =
-      GEOMETRY_FACTORY.createGeometryCollection(null);
   private static final double DEFAULT_TOLERANCE = 1e-6;
   private static final int DEFAULT_MAX_ITER = 1000;
   private static final int OGC_SFS_VALIDITY = 0; // Use usual OGC SFS validity 
semantics
@@ -90,7 +85,7 @@ public class Functions {
   public static Geometry boundary(Geometry geometry) {
     Geometry boundary = geometry.getBoundary();
     if (boundary instanceof LinearRing) {
-      boundary = GEOMETRY_FACTORY.createLineString(boundary.getCoordinates());
+      boundary = 
geometry.getFactory().createLineString(boundary.getCoordinates());
     }
     return boundary;
   }
@@ -546,7 +541,11 @@ public class Functions {
 
   public static Geometry geometryN(Geometry geometry, int n) {
     if (n < geometry.getNumGeometries()) {
-      return geometry.getGeometryN(n);
+      Geometry subGeom = geometry.getGeometryN(n);
+      if (subGeom.getSRID() == geometry.getSRID()) {
+        return subGeom;
+      }
+      return setSRID(subGeom, geometry.getSRID());
     }
     return null;
   }
@@ -557,7 +556,7 @@ public class Functions {
       if (n < polygon.getNumInteriorRing()) {
         Geometry interiorRing = polygon.getInteriorRingN(n);
         if (interiorRing instanceof LinearRing) {
-          interiorRing = 
GEOMETRY_FACTORY.createLineString(interiorRing.getCoordinates());
+          interiorRing = 
polygon.getFactory().createLineString(interiorRing.getCoordinates());
         }
         return interiorRing;
       }
@@ -575,7 +574,7 @@ public class Functions {
   public static Geometry exteriorRing(Geometry geometry) {
     Geometry ring = GeomUtils.getExteriorRing(geometry);
     if (ring instanceof LinearRing) {
-      ring = GEOMETRY_FACTORY.createLineString(ring.getCoordinates());
+      ring = geometry.getFactory().createLineString(ring.getCoordinates());
     }
     return ring;
   }
@@ -763,7 +762,7 @@ public class Functions {
         } else {
           coordinates.add(position, point.getCoordinate());
         }
-        return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new 
Coordinate[0]));
+        return 
linestring.getFactory().createLineString(coordinates.toArray(new 
Coordinate[0]));
       }
     }
     return null;
@@ -784,7 +783,7 @@ public class Functions {
           position = coordinates.size() - 1;
         }
         coordinates.remove(position);
-        return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new 
Coordinate[0]));
+        return 
linestring.getFactory().createLineString(coordinates.toArray(new 
Coordinate[0]));
       }
     }
     return null;
@@ -799,7 +798,7 @@ public class Functions {
         } else {
           coordinates.set(position, point.getCoordinate());
         }
-        return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new 
Coordinate[0]));
+        return 
linestring.getFactory().createLineString(coordinates.toArray(new 
Coordinate[0]));
       }
     }
     return null;
@@ -813,14 +812,14 @@ public class Functions {
     for (Coordinate c : geometry.getCoordinates()) {
       coordinates.add(c);
     }
-    return GEOMETRY_FACTORY.createLineString(coordinates.toArray(new 
Coordinate[0]));
+    return geometry.getFactory().createLineString(coordinates.toArray(new 
Coordinate[0]));
   }
 
   public static Geometry closestPoint(Geometry left, Geometry right) {
     DistanceOp distanceOp = new DistanceOp(left, right);
     try {
       Coordinate[] closestPoints = distanceOp.nearestPoints();
-      return GEOMETRY_FACTORY.createPoint(closestPoints[0]);
+      return left.getFactory().createPoint(closestPoints[0]);
     } catch (Exception e) {
       throw new IllegalArgumentException("ST_ClosestPoint doesn't support 
empty geometry object.");
     }
@@ -884,7 +883,7 @@ public class Functions {
   public static Geometry intersection(Geometry leftGeometry, Geometry 
rightGeometry) {
     boolean isIntersects = leftGeometry.intersects(rightGeometry);
     if (!isIntersects) {
-      return EMPTY_POLYGON;
+      return leftGeometry.getFactory().createPolygon();
     }
     if (leftGeometry.contains(rightGeometry)) {
       return rightGeometry;
@@ -925,7 +924,7 @@ public class Functions {
         return geometry;
       }
     }
-    return EMPTY_GEOMETRY_COLLECTION;
+    return geometry.getFactory().createGeometryCollection();
   }
 
   public static Geometry minimumBoundingCircle(Geometry geometry, int 
quadrantSegments) {
@@ -975,7 +974,7 @@ public class Functions {
     MinimumBoundingCircle minimumBoundingCircle = new 
MinimumBoundingCircle(geometry);
     Coordinate coods = minimumBoundingCircle.getCentre();
     double radius = minimumBoundingCircle.getRadius();
-    Point centre = GEOMETRY_FACTORY.createPoint(coods);
+    Point centre = geometry.getFactory().createPoint(coods);
     return Pair.of(centre, radius);
   }
 
@@ -998,7 +997,7 @@ public class Functions {
     double length = geom.getLength();
     LengthIndexedLine indexedLine = new LengthIndexedLine(geom);
     Coordinate interPoint = indexedLine.extractPoint(length * fraction);
-    return GEOMETRY_FACTORY.createPoint(interPoint);
+    return geom.getFactory().createPoint(interPoint);
   }
 
   /**
@@ -1023,7 +1022,7 @@ public class Functions {
         polygons.add((Polygon) transformCW(polygon));
       }
 
-      return new GeometryFactory().createMultiPolygon(polygons.toArray(new 
Polygon[0]));
+      return geom.getFactory().createMultiPolygon(polygons.toArray(new 
Polygon[0]));
     }
     // Non-polygonal geometries are returned unchanged
     return geom;
@@ -1038,7 +1037,8 @@ public class Functions {
       interiorRings.add(transformCW(polygon.getInteriorRingN(i), false));
     }
 
-    return new GeometryFactory(polygon.getPrecisionModel(), polygon.getSRID())
+    return polygon
+        .getFactory()
         .createPolygon(exteriorRingEnforced, interiorRings.toArray(new 
LinearRing[0]));
   }
 
@@ -1135,7 +1135,7 @@ public class Functions {
         polygons.add((Polygon) transformCCW(polygon));
       }
 
-      return new GeometryFactory().createMultiPolygon(polygons.toArray(new 
Polygon[0]));
+      return geom.getFactory().createMultiPolygon(polygons.toArray(new 
Polygon[0]));
     }
     // Non-polygonal geometries are returned unchanged
     return geom;
@@ -1150,7 +1150,8 @@ public class Functions {
       interiorRings.add(transformCCW(polygon.getInteriorRingN(i), false));
     }
 
-    return new GeometryFactory(polygon.getPrecisionModel(), polygon.getSRID())
+    return polygon
+        .getFactory()
         .createPolygon(exteriorRingEnforced, interiorRings.toArray(new 
LinearRing[0]));
   }
 
@@ -1240,7 +1241,7 @@ public class Functions {
     if (!isIntersects) {
       return leftGeometry;
     } else if (rightGeometry.contains(leftGeometry)) {
-      return EMPTY_POLYGON;
+      return leftGeometry.getFactory().createPolygon();
     } else {
       return leftGeometry.difference(rightGeometry);
     }
@@ -1248,7 +1249,7 @@ public class Functions {
 
   public static Geometry split(Geometry input, Geometry blade) {
     // check input geometry
-    return new GeometrySplitter(GEOMETRY_FACTORY).split(input, blade);
+    return new GeometrySplitter(input.getFactory()).split(input, blade);
   }
 
   public static Integer dimension(Geometry geometry) {
@@ -1475,9 +1476,8 @@ public class Functions {
   }
 
   public static Geometry[] dumpPoints(Geometry geometry) {
-    return Arrays.stream(geometry.getCoordinates())
-        .map(GEOMETRY_FACTORY::createPoint)
-        .toArray(Point[]::new);
+    GeometryFactory factory = geometry.getFactory();
+    return 
Arrays.stream(geometry.getCoordinates()).map(factory::createPoint).toArray(Point[]::new);
   }
 
   public static Geometry symDifference(Geometry leftGeom, Geometry rightGeom) {
@@ -1489,7 +1489,8 @@ public class Functions {
   }
 
   public static Geometry union(Geometry[] geoms) {
-    return GEOMETRY_FACTORY.createGeometryCollection(geoms).union();
+    GeometryFactory factory = (geoms.length > 0) ? geoms[0].getFactory() : new 
GeometryFactory();
+    return factory.createGeometryCollection(geoms).union();
   }
 
   public static Geometry unaryUnion(Geometry geom) {
@@ -1497,18 +1498,19 @@ public class Functions {
   }
 
   public static Geometry createMultiGeometryFromOneElement(Geometry geometry) {
+    GeometryFactory factory = geometry.getFactory();
     if (geometry instanceof Circle) {
-      return GEOMETRY_FACTORY.createGeometryCollection(new Circle[] {(Circle) 
geometry});
+      return factory.createGeometryCollection(new Circle[] {(Circle) 
geometry});
     } else if (geometry instanceof GeometryCollection) {
       return geometry;
     } else if (geometry instanceof LineString) {
-      return GEOMETRY_FACTORY.createMultiLineString(new LineString[] 
{(LineString) geometry});
+      return factory.createMultiLineString(new LineString[] {(LineString) 
geometry});
     } else if (geometry instanceof Point) {
-      return GEOMETRY_FACTORY.createMultiPoint(new Point[] {(Point) geometry});
+      return factory.createMultiPoint(new Point[] {(Point) geometry});
     } else if (geometry instanceof Polygon) {
-      return GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {(Polygon) 
geometry});
+      return factory.createMultiPolygon(new Polygon[] {(Polygon) geometry});
     } else {
-      return GEOMETRY_FACTORY.createGeometryCollection();
+      return factory.createGeometryCollection();
     }
   }
 
@@ -1540,10 +1542,16 @@ public class Functions {
     }
 
     Coordinate[] coords = coordinates.toArray(new Coordinate[0]);
-    return GEOMETRY_FACTORY.createLineString(coords);
+    GeometryFactory factory = (geoms.length > 0) ? geoms[0].getFactory() : new 
GeometryFactory();
+    return factory.createLineString(coords);
   }
 
   public static Geometry makePolygon(Geometry shell, Geometry[] holes) {
+    GeometryFactory factory = shell.getFactory();
+    return makePolygon(shell, holes, factory);
+  }
+
+  public static Geometry makePolygon(Geometry shell, Geometry[] holes, 
GeometryFactory factory) {
     try {
       if (holes != null) {
         LinearRing[] interiorRings =
@@ -1554,11 +1562,11 @@ public class Functions {
                             && !h.isEmpty()
                             && h instanceof LineString
                             && ((LineString) h).isClosed())
-                .map(h -> 
GEOMETRY_FACTORY.createLinearRing(h.getCoordinates()))
+                .map(h -> factory.createLinearRing(h.getCoordinates()))
                 .toArray(LinearRing[]::new);
         if (interiorRings.length != 0) {
-          return GEOMETRY_FACTORY.createPolygon(
-              GEOMETRY_FACTORY.createLinearRing(shell.getCoordinates()),
+          return factory.createPolygon(
+              factory.createLinearRing(shell.getCoordinates()),
               Arrays.stream(holes)
                   .filter(
                       h ->
@@ -1566,32 +1574,30 @@ public class Functions {
                               && !h.isEmpty()
                               && h instanceof LineString
                               && ((LineString) h).isClosed())
-                  .map(h -> 
GEOMETRY_FACTORY.createLinearRing(h.getCoordinates()))
+                  .map(h -> factory.createLinearRing(h.getCoordinates()))
                   .toArray(LinearRing[]::new));
         }
       }
-      return GEOMETRY_FACTORY.createPolygon(
-          GEOMETRY_FACTORY.createLinearRing(shell.getCoordinates()));
+      return 
factory.createPolygon(factory.createLinearRing(shell.getCoordinates()));
     } catch (IllegalArgumentException e) {
       return null;
     }
   }
 
   public static Geometry makepolygonWithSRID(Geometry lineString, Integer 
srid) {
-    Geometry geom = makePolygon(lineString, null);
-    if (geom != null) {
-      geom.setSRID(srid);
-    }
-    return geom;
+    GeometryFactory factory =
+        (srid != null) ? new GeometryFactory(new PrecisionModel(), srid) : 
lineString.getFactory();
+    return makePolygon(lineString, null, factory);
   }
 
   public static Geometry createMultiGeometry(Geometry[] geometries) {
     if (geometries.length > 1) {
-      return GEOMETRY_FACTORY.buildGeometry(Arrays.asList(geometries));
+      return 
geometries[0].getFactory().buildGeometry(Arrays.asList(geometries));
     } else if (geometries.length == 1) {
       return createMultiGeometryFromOneElement(geometries[0]);
     } else {
-      return GEOMETRY_FACTORY.createGeometryCollection();
+      GeometryFactory factory = new GeometryFactory();
+      return factory.createGeometryCollection();
     }
   }
 
@@ -1599,20 +1605,21 @@ public class Functions {
     if (geomType == null) {
       return collectionExtract(geometry);
     }
+    GeometryFactory factory = geometry.getFactory();
     Class<? extends Geometry> geomClass;
     GeometryCollection emptyResult;
     switch (geomType) {
       case 1:
         geomClass = Point.class;
-        emptyResult = GEOMETRY_FACTORY.createMultiPoint();
+        emptyResult = factory.createMultiPoint();
         break;
       case 2:
         geomClass = LineString.class;
-        emptyResult = GEOMETRY_FACTORY.createMultiLineString();
+        emptyResult = factory.createMultiLineString();
         break;
       case 3:
         geomClass = Polygon.class;
-        emptyResult = GEOMETRY_FACTORY.createMultiPolygon();
+        emptyResult = factory.createMultiPolygon();
         break;
       default:
         throw new IllegalArgumentException("Invalid geometry type");
@@ -1626,21 +1633,22 @@ public class Functions {
 
   public static Geometry collectionExtract(Geometry geometry) {
     List<Geometry> geometries = GeomUtils.extractGeometryCollection(geometry);
+    GeometryFactory factory = geometry.getFactory();
     Polygon[] polygons =
         geometries.stream().filter(g -> g instanceof 
Polygon).toArray(Polygon[]::new);
     if (polygons.length > 0) {
-      return GEOMETRY_FACTORY.createMultiPolygon(polygons);
+      return factory.createMultiPolygon(polygons);
     }
     LineString[] lines =
         geometries.stream().filter(g -> g instanceof 
LineString).toArray(LineString[]::new);
     if (lines.length > 0) {
-      return GEOMETRY_FACTORY.createMultiLineString(lines);
+      return factory.createMultiLineString(lines);
     }
     Point[] points = geometries.stream().filter(g -> g instanceof 
Point).toArray(Point[]::new);
     if (points.length > 0) {
-      return GEOMETRY_FACTORY.createMultiPoint(points);
+      return factory.createMultiPoint(points);
     }
-    return GEOMETRY_FACTORY.createGeometryCollection();
+    return factory.createGeometryCollection();
   }
 
   // ported from
@@ -1785,15 +1793,15 @@ public class Functions {
   }
 
   public static Geometry force3D(Geometry geometry, double zValue) {
-    return GeomUtils.get3DGeom(geometry, zValue);
+    return GeometryForce3DTransformer.transform(geometry, zValue);
   }
 
   public static Geometry force3D(Geometry geometry) {
-    return GeomUtils.get3DGeom(geometry, 0.0);
+    return GeometryForce3DTransformer.transform(geometry, 0.0);
   }
 
   public static Geometry forceCollection(Geometry geom) {
-    return new 
GeometryFactory().createGeometryCollection(convertGeometryToArray(geom));
+    return 
geom.getFactory().createGeometryCollection(convertGeometryToArray(geom));
   }
 
   private static Geometry[] convertGeometryToArray(Geometry geom) {
@@ -1872,13 +1880,14 @@ public class Functions {
   public static Geometry geometricMedian(
       Geometry geometry, double tolerance, int maxIter, boolean 
failIfNotConverged)
       throws Exception {
+    GeometryFactory factory = geometry.getFactory();
     String geometryType = geometry.getGeometryType();
     if (!(Geometry.TYPENAME_POINT.equals(geometryType)
         || Geometry.TYPENAME_MULTIPOINT.equals(geometryType))) {
       throw new Exception("Unsupported geometry type: " + geometryType);
     }
     Coordinate[] coordinates = extractCoordinates(geometry);
-    if (coordinates.length == 0) return new Point(null, GEOMETRY_FACTORY);
+    if (coordinates.length == 0) return new Point(null, factory);
     Coordinate median = initGuess(coordinates);
     double delta = Double.MAX_VALUE;
     double[] distances =
@@ -1891,10 +1900,7 @@ public class Functions {
               "Median failed to converge within %.1E after %d iterations.", 
tolerance, maxIter));
     boolean is3d = !Double.isNaN(geometry.getCoordinate().z);
     if (!is3d) median.z = Double.NaN;
-    Point point =
-        new Point(new CoordinateArraySequence(new Coordinate[] {median}), 
GEOMETRY_FACTORY);
-    point.setSRID(geometry.getSRID());
-    return point;
+    return factory.createPoint(median);
   }
 
   public static Geometry geometricMedian(Geometry geometry, double tolerance, 
int maxIter)
@@ -1923,8 +1929,9 @@ public class Functions {
   }
 
   public static Geometry boundingDiagonal(Geometry geometry) {
+    GeometryFactory factory = geometry.getFactory();
     if (geometry.isEmpty()) {
-      return GEOMETRY_FACTORY.createLineString();
+      return factory.createLineString();
     } else {
       Double startX = null, startY = null, startZ = null, endX = null, endY = 
null, endZ = null;
       boolean is3d = !Double.isNaN(geometry.getCoordinate().z);
@@ -1949,7 +1956,7 @@ public class Functions {
         startCoordinate = new Coordinate(startX, startY);
         endCoordinate = new Coordinate(endX, endY);
       }
-      return GEOMETRY_FACTORY.createLineString(new Coordinate[] 
{startCoordinate, endCoordinate});
+      return factory.createLineString(new Coordinate[] {startCoordinate, 
endCoordinate});
     }
   }
 
@@ -2064,8 +2071,9 @@ public class Functions {
    * @return A GeometryCollection containing the resultant polygons.
    */
   public static Geometry polygonize(Geometry geometry) {
+    GeometryFactory factory = (geometry != null) ? geometry.getFactory() : new 
GeometryFactory();
     if (geometry == null || geometry.isEmpty()) {
-      return GEOMETRY_FACTORY.createGeometryCollection(null);
+      return factory.createGeometryCollection(null);
     }
 
     if (geometry instanceof GeometryCollection) {
@@ -2078,9 +2086,9 @@ public class Functions {
       Collection polygons = polygonizer.getPolygons();
       Geometry[] polyArray = (Geometry[]) polygons.toArray(new Geometry[0]);
 
-      return GEOMETRY_FACTORY.createGeometryCollection(polyArray);
+      return factory.createGeometryCollection(polyArray);
     } else {
-      return GEOMETRY_FACTORY.createGeometryCollection(null);
+      return factory.createGeometryCollection(null);
     }
   }
 
@@ -2100,7 +2108,7 @@ public class Functions {
     Coordinate[] coordinates = geometry.getCoordinates();
 
     // Creating a MultiPoint from the extracted coordinates
-    return GEOMETRY_FACTORY.createMultiPointFromCoords(coordinates);
+    return geometry.getFactory().createMultiPointFromCoords(coordinates);
   }
 
   /**
diff --git 
a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java 
b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java
index 1691b79ae..28bf6b904 100644
--- a/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java
+++ b/common/src/main/java/org/apache/sedona/common/FunctionsGeoTools.java
@@ -18,24 +18,24 @@
  */
 package org.apache.sedona.common;
 
+import java.util.Set;
 import org.geotools.geometry.jts.JTS;
 import org.geotools.referencing.CRS;
 import org.geotools.referencing.ReferencingFactoryFinder;
 import org.geotools.util.factory.Hints;
 import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.operation.buffer.BufferOp;
 import org.locationtech.jts.operation.buffer.BufferParameters;
 import org.locationtech.jts.triangulate.VoronoiDiagramBuilder;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.NoSuchAuthorityCodeException;
+import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 
 public class FunctionsGeoTools {
-  private static final GeometryFactory GEOMETRY_FACTORY = new 
GeometryFactory();
 
   public static Geometry transform(Geometry geometry, String targetCRS)
       throws FactoryException, TransformException {
@@ -82,11 +82,39 @@ public class FunctionsGeoTools {
       }
     }
     CoordinateReferenceSystem sourceCRS = parseCRSString(sourceCRScode);
+    int targetSRID = crsToSRID(targetCRS);
     // If sourceCRS and targetCRS are equal, return the geometry unchanged
     if (!CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
       MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, 
lenient);
-      return JTS.transform(geometry, transform);
-    } else return geometry;
+      Geometry transformed = JTS.transform(geometry, transform);
+      transformed = Functions.setSRID(transformed, targetSRID);
+      transformed.setUserData(geometry.getUserData());
+      return transformed;
+    } else {
+      if (geometry.getSRID() != targetSRID) {
+        Geometry transformed = Functions.setSRID(geometry, targetSRID);
+        transformed.setUserData(geometry.getUserData());
+        return transformed;
+      } else {
+        return geometry;
+      }
+    }
+  }
+
+  /**
+   * Get the SRID of a CRS. We use the EPSG code of the CRS if available.
+   *
+   * @param crs CoordinateReferenceSystem
+   * @return SRID
+   */
+  public static int crsToSRID(CoordinateReferenceSystem crs) {
+    Set<ReferenceIdentifier> crsIds = crs.getIdentifiers();
+    for (ReferenceIdentifier crsId : crsIds) {
+      if ("EPSG".equals(crsId.getCodeSpace())) {
+        return Integer.parseInt(crsId.getCode());
+      }
+    }
+    return 0;
   }
 
   /**
@@ -138,7 +166,7 @@ public class FunctionsGeoTools {
       e.expandBy(Math.max(e.getWidth(), e.getHeight()));
       builder.setClipEnvelope(e);
     }
-    return builder.getDiagram(FunctionsGeoTools.GEOMETRY_FACTORY);
+    return builder.getDiagram(geom.getFactory());
   }
 
   public static Geometry bufferSpheroid(Geometry geometry, double radius, 
BufferParameters params)
@@ -152,7 +180,7 @@ public class FunctionsGeoTools {
     if (Functions.crossesDateLine(geometry)) {
       Functions.shiftLongitude(geometry);
     }
-    //        geometry = (Predicates.crossesDateLine(geometry)) ? 
Functions.shiftLongitude(geometry)
+    // geometry = (Predicates.crossesDateLine(geometry)) ? 
Functions.shiftLongitude(geometry)
     // : geometry;
 
     // If originalCRS is not set, use WGS84 as the originalCRS for 
transformation
@@ -169,7 +197,6 @@ public class FunctionsGeoTools {
       int backTransformCRSCode = (originalCRS == 0) ? WGS84CRS : originalCRS;
       Geometry bufferedResult =
           transform(bufferedGeometry, targetCRSCode, "EPSG:" + 
backTransformCRSCode);
-      bufferedResult.setSRID(backTransformCRSCode);
 
       // Normalize longitudes between -180 and 180
       Functions.normalizeLongitude(bufferedResult);
diff --git 
a/common/src/main/java/org/apache/sedona/common/geometryObjects/Circle.java 
b/common/src/main/java/org/apache/sedona/common/geometryObjects/Circle.java
index 702b4f811..05eb08e80 100644
--- a/common/src/main/java/org/apache/sedona/common/geometryObjects/Circle.java
+++ b/common/src/main/java/org/apache/sedona/common/geometryObjects/Circle.java
@@ -27,7 +27,6 @@ import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryCollection;
 import org.locationtech.jts.geom.GeometryComponentFilter;
-import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.GeometryFilter;
 import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.Point;
@@ -54,7 +53,7 @@ public class Circle extends Geometry {
    * @param givenRadius the given radius
    */
   public Circle(Geometry centerGeometry, Double givenRadius) {
-    super(new GeometryFactory(centerGeometry.getPrecisionModel()));
+    super(centerGeometry.getFactory());
     this.centerGeometry = centerGeometry;
     Envelope centerGeometryMBR = this.centerGeometry.getEnvelopeInternal();
     this.centerPoint =
diff --git 
a/common/src/main/java/org/apache/sedona/common/geometrySerde/GeometrySerializer.java
 
b/common/src/main/java/org/apache/sedona/common/geometrySerde/GeometrySerializer.java
index 5d337076e..508a62901 100644
--- 
a/common/src/main/java/org/apache/sedona/common/geometrySerde/GeometrySerializer.java
+++ 
b/common/src/main/java/org/apache/sedona/common/geometrySerde/GeometrySerializer.java
@@ -30,11 +30,12 @@ import org.locationtech.jts.geom.MultiPoint;
 import org.locationtech.jts.geom.MultiPolygon;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
+import org.locationtech.jts.geom.PrecisionModel;
 import org.locationtech.jts.io.WKBConstants;
 
 public class GeometrySerializer {
   private static final Coordinate NULL_COORDINATE = new Coordinate(Double.NaN, 
Double.NaN);
-  private static final GeometryFactory FACTORY = new GeometryFactory();
+  private static final PrecisionModel PRECISION_MODEL = new PrecisionModel();
 
   public static byte[] serialize(Geometry geometry) {
     GeometryBuffer buffer;
@@ -65,6 +66,10 @@ public class GeometrySerializer {
   }
 
   public static Geometry deserialize(GeometryBuffer buffer) {
+    return deserialize(buffer, null);
+  }
+
+  public static Geometry deserialize(GeometryBuffer buffer, GeometryFactory 
factory) {
     checkBufferSize(buffer, 8);
     int preambleByte = buffer.getByte(0) & 0xFF;
     int wkbType = preambleByte >> 4;
@@ -78,25 +83,28 @@ public class GeometrySerializer {
       int srid0 = buffer.getByte(3) & 0xFF;
       srid = (srid2 | srid1 | srid0);
     }
-    return deserialize(buffer, wkbType, srid);
+    if (factory == null) {
+      factory = createGeometryFactory(srid);
+    }
+    return deserialize(buffer, wkbType, factory);
   }
 
-  private static Geometry deserialize(GeometryBuffer buffer, int wkbType, int 
srid) {
+  private static Geometry deserialize(GeometryBuffer buffer, int wkbType, 
GeometryFactory factory) {
     switch (wkbType) {
       case WKBConstants.wkbPoint:
-        return deserializePoint(buffer, srid);
+        return deserializePoint(buffer, factory);
       case WKBConstants.wkbMultiPoint:
-        return deserializeMultiPoint(buffer, srid);
+        return deserializeMultiPoint(buffer, factory);
       case WKBConstants.wkbLineString:
-        return deserializeLineString(buffer, srid);
+        return deserializeLineString(buffer, factory);
       case WKBConstants.wkbMultiLineString:
-        return deserializeMultiLineString(buffer, srid);
+        return deserializeMultiLineString(buffer, factory);
       case WKBConstants.wkbPolygon:
-        return deserializePolygon(buffer, srid);
+        return deserializePolygon(buffer, factory);
       case WKBConstants.wkbMultiPolygon:
-        return deserializeMultiPolygon(buffer, srid);
+        return deserializeMultiPolygon(buffer, factory);
       case WKBConstants.wkbGeometryCollection:
-        return deserializeGeometryCollection(buffer, srid);
+        return deserializeGeometryCollection(buffer, factory);
       default:
         throw new IllegalArgumentException(
             "Cannot deserialize buffer containing unknown geometry type ID: " 
+ wkbType);
@@ -116,21 +124,20 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static Point deserializePoint(GeometryBuffer buffer, int srid) {
+  private static Point deserializePoint(GeometryBuffer buffer, GeometryFactory 
factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numCoordinates = getBoundedInt(buffer, 4);
     Point point;
     if (numCoordinates == 0) {
-      point = FACTORY.createPoint();
+      point = factory.createPoint();
       buffer.mark(8);
     } else {
       int bufferSize = 8 + coordType.bytes;
       checkBufferSize(buffer, bufferSize);
       CoordinateSequence coordinates = buffer.getCoordinate(8);
-      point = FACTORY.createPoint(coordinates);
+      point = factory.createPoint(coordinates);
       buffer.mark(bufferSize);
     }
-    point.setSRID(srid);
     return point;
   }
 
@@ -158,7 +165,7 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static MultiPoint deserializeMultiPoint(GeometryBuffer buffer, int 
srid) {
+  private static MultiPoint deserializeMultiPoint(GeometryBuffer buffer, 
GeometryFactory factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numPoints = getBoundedInt(buffer, 4);
     int bufferSize = 8 + numPoints * coordType.bytes;
@@ -168,16 +175,13 @@ public class GeometrySerializer {
       CoordinateSequence coordinates = buffer.getCoordinate(8 + i * 
coordType.bytes);
       Coordinate coordinate = coordinates.getCoordinate(0);
       if (Double.isNaN(coordinate.x)) {
-        points[i] = FACTORY.createPoint();
+        points[i] = factory.createPoint();
       } else {
-        points[i] = FACTORY.createPoint(coordinates);
+        points[i] = factory.createPoint(coordinates);
       }
-      points[i].setSRID(srid);
     }
     buffer.mark(bufferSize);
-    MultiPoint multiPoint = FACTORY.createMultiPoint(points);
-    multiPoint.setSRID(srid);
-    return multiPoint;
+    return factory.createMultiPoint(points);
   }
 
   private static GeometryBuffer serializeLineString(LineString lineString) {
@@ -200,16 +204,14 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static LineString deserializeLineString(GeometryBuffer buffer, int 
srid) {
+  private static LineString deserializeLineString(GeometryBuffer buffer, 
GeometryFactory factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numCoordinates = getBoundedInt(buffer, 4);
     int bufferSize = 8 + numCoordinates * coordType.bytes;
     checkBufferSize(buffer, bufferSize);
     CoordinateSequence coordinates = buffer.getCoordinates(8, numCoordinates);
     buffer.mark(bufferSize);
-    LineString lineString = FACTORY.createLineString(coordinates);
-    lineString.setSRID(srid);
-    return lineString;
+    return factory.createLineString(coordinates);
   }
 
   private static GeometryBuffer serializeMultiLineString(MultiLineString 
multiLineString) {
@@ -226,7 +228,8 @@ public class GeometrySerializer {
             multiLineString.getSRID(),
             bufferSize,
             numCoordinates);
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numOffset, 
multiLineString.getFactory());
     serializer.writeInt(numLineStrings);
     for (int k = 0; k < numLineStrings; k++) {
       LineString ls = (LineString) multiLineString.getGeometryN(k);
@@ -236,24 +239,23 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static MultiLineString deserializeMultiLineString(GeometryBuffer 
buffer, int srid) {
+  private static MultiLineString deserializeMultiLineString(
+      GeometryBuffer buffer, GeometryFactory factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numCoordinates = getBoundedInt(buffer, 4);
     int coordsOffset = 8;
     int numOffset = 8 + numCoordinates * coordType.bytes;
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numOffset, factory);
     int numLineStrings = serializer.checkedReadBoundedInt();
     serializer.checkRemainingIntsAtLeast(numLineStrings);
     LineString[] lineStrings = new LineString[numLineStrings];
     for (int k = 0; k < numLineStrings; k++) {
       LineString ls = serializer.readLineString();
-      ls.setSRID(srid);
       lineStrings[k] = ls;
     }
     serializer.markEndOfBuffer();
-    MultiLineString multiLineString = 
FACTORY.createMultiLineString(lineStrings);
-    multiLineString.setSRID(srid);
-    return multiLineString;
+    return factory.createMultiLineString(lineStrings);
   }
 
   private static GeometryBuffer serializePolygon(Polygon polygon) {
@@ -272,27 +274,26 @@ public class GeometrySerializer {
     GeometryBuffer buffer =
         createGeometryBuffer(
             WKBConstants.wkbPolygon, coordType, polygon.getSRID(), bufferSize, 
numCoordinates);
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numRingsOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numRingsOffset, 
polygon.getFactory());
     serializer.write(polygon);
     assert bufferSize == serializer.intsOffset;
     return buffer;
   }
 
-  private static Polygon deserializePolygon(GeometryBuffer buffer, int srid) {
+  private static Polygon deserializePolygon(GeometryBuffer buffer, 
GeometryFactory factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numCoordinates = getBoundedInt(buffer, 4);
     if (numCoordinates == 0) {
       buffer.mark(8);
-      Polygon polygon = FACTORY.createPolygon();
-      polygon.setSRID(srid);
-      return polygon;
+      return factory.createPolygon();
     }
     int coordsOffset = 8;
     int numRingsOffset = 8 + numCoordinates * coordType.bytes;
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numRingsOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numRingsOffset, factory);
     Polygon polygon = serializer.readPolygon();
     serializer.markEndOfBuffer();
-    polygon.setSRID(srid);
     return polygon;
   }
 
@@ -319,7 +320,8 @@ public class GeometrySerializer {
             multiPolygon.getSRID(),
             bufferSize,
             numCoordinates);
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numPolygonsOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numPolygonsOffset, 
multiPolygon.getFactory());
     serializer.writeInt(numPolygons);
     for (int k = 0; k < numPolygons; k++) {
       Polygon polygon = (Polygon) multiPolygon.getGeometryN(k);
@@ -329,23 +331,22 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static MultiPolygon deserializeMultiPolygon(GeometryBuffer buffer, 
int srid) {
+  private static MultiPolygon deserializeMultiPolygon(
+      GeometryBuffer buffer, GeometryFactory factory) {
     CoordinateType coordType = buffer.getCoordinateType();
     int numCoordinates = getBoundedInt(buffer, 4);
     int coordsOffset = 8;
     int numPolygonsOffset = 8 + numCoordinates * coordType.bytes;
-    GeomPartSerializer serializer = new GeomPartSerializer(buffer, 
coordsOffset, numPolygonsOffset);
+    GeomPartSerializer serializer =
+        new GeomPartSerializer(buffer, coordsOffset, numPolygonsOffset, 
factory);
     int numPolygons = serializer.checkedReadBoundedInt();
     Polygon[] polygons = new Polygon[numPolygons];
     for (int k = 0; k < numPolygons; k++) {
       Polygon polygon = serializer.readPolygon();
-      polygon.setSRID(srid);
       polygons[k] = polygon;
     }
     serializer.markEndOfBuffer();
-    MultiPolygon multiPolygon = FACTORY.createMultiPolygon(polygons);
-    multiPolygon.setSRID(srid);
-    return multiPolygon;
+    return factory.createMultiPolygon(polygons);
   }
 
   private static GeometryBuffer serializeGeometryCollection(GeometryCollection 
geometryCollection) {
@@ -383,28 +384,24 @@ public class GeometrySerializer {
     return buffer;
   }
 
-  private static GeometryCollection 
deserializeGeometryCollection(GeometryBuffer buffer, int srid) {
+  private static GeometryCollection deserializeGeometryCollection(
+      GeometryBuffer buffer, GeometryFactory factory) {
     int numGeometries = getBoundedInt(buffer, 4);
     if (numGeometries == 0) {
       buffer.mark(8);
-      GeometryCollection geometryCollection = 
FACTORY.createGeometryCollection();
-      geometryCollection.setSRID(srid);
-      return geometryCollection;
+      return factory.createGeometryCollection();
     }
     Geometry[] geometries = new Geometry[numGeometries];
     int offset = 8;
     for (int k = 0; k < numGeometries; k++) {
       GeometryBuffer geomBuffer = buffer.slice(offset);
-      Geometry geometry = deserialize(geomBuffer);
+      Geometry geometry = deserialize(geomBuffer, factory);
       int geomLength = alignedOffset(geomBuffer.getMark());
-      geometry.setSRID(srid);
       geometries[k] = geometry;
       offset += geomLength;
     }
     buffer.mark(offset);
-    GeometryCollection geometryCollection = 
FACTORY.createGeometryCollection(geometries);
-    geometryCollection.setSRID(srid);
-    return geometryCollection;
+    return factory.createGeometryCollection(geometries);
   }
 
   private static GeometryBuffer createGeometryBuffer(
@@ -475,33 +472,40 @@ public class GeometrySerializer {
     return (offset + 7) & ~7;
   }
 
+  private static GeometryFactory createGeometryFactory(int srid) {
+    return new GeometryFactory(PRECISION_MODEL, srid);
+  }
+
   static class GeomPartSerializer {
     final GeometryBuffer buffer;
     int coordsOffset;
     final int coordsEndOffset;
     int intsOffset;
+    final GeometryFactory factory;
 
-    GeomPartSerializer(GeometryBuffer buffer, int coordsOffset, int 
intsOffset) {
+    GeomPartSerializer(
+        GeometryBuffer buffer, int coordsOffset, int intsOffset, 
GeometryFactory factory) {
       this.buffer = buffer;
       this.coordsOffset = coordsOffset;
       this.coordsEndOffset = intsOffset;
       this.intsOffset = intsOffset;
+      this.factory = factory;
     }
 
     LineString readLineString() {
       CoordinateSequence coordinates = readCoordinates();
-      return FACTORY.createLineString(coordinates);
+      return factory.createLineString(coordinates);
     }
 
     LinearRing readRing() {
       CoordinateSequence coordinates = readCoordinates();
-      return FACTORY.createLinearRing(coordinates);
+      return factory.createLinearRing(coordinates);
     }
 
     Polygon readPolygon() {
       int numRings = checkedReadBoundedInt();
       if (numRings == 0) {
-        return FACTORY.createPolygon();
+        return factory.createPolygon();
       }
       checkRemainingIntsAtLeast(numRings);
       int numInteriorRings = numRings - 1;
@@ -510,7 +514,7 @@ public class GeometrySerializer {
       for (int k = 0; k < numInteriorRings; k++) {
         holes[k] = readRing();
       }
-      return FACTORY.createPolygon(shell, holes);
+      return factory.createPolygon(shell, holes);
     }
 
     CoordinateSequence readCoordinates() {
diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java 
b/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java
index 2d32fc93d..19e0ef1fd 100644
--- 
a/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java
+++ 
b/common/src/main/java/org/apache/sedona/common/raster/GeometryFunctions.java
@@ -24,7 +24,6 @@ import org.geotools.coverage.grid.GridCoordinates2D;
 import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.geometry.Envelope2D;
 import org.locationtech.jts.geom.*;
-import org.locationtech.jts.geom.impl.CoordinateArraySequence;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.operation.TransformException;
 
@@ -110,16 +109,13 @@ public class GeometryFunctions {
             raster, minX,
             maxY + 1); // need bottom left coordinate, which is upper left of 
y + 1 pixel
     return geometryFactory.createPolygon(
-        new LinearRing(
-            new CoordinateArraySequence(
-                new Coordinate[] {
-                  convertToCoordinate(worldUpperLeft),
-                  convertToCoordinate(worldUpperRight),
-                  convertToCoordinate(worldLowerRight),
-                  convertToCoordinate(worldLowerLeft),
-                  convertToCoordinate(worldUpperLeft)
-                }),
-            geometryFactory));
+        new Coordinate[] {
+          convertToCoordinate(worldUpperLeft),
+          convertToCoordinate(worldUpperRight),
+          convertToCoordinate(worldLowerRight),
+          convertToCoordinate(worldLowerLeft),
+          convertToCoordinate(worldUpperLeft)
+        });
   }
 
   private static Coordinate convertToCoordinate(Point2D point) {
diff --git 
a/common/src/main/java/org/apache/sedona/common/simplify/BaseSimplifier.java 
b/common/src/main/java/org/apache/sedona/common/simplify/BaseSimplifier.java
index e852b889e..4979efc71 100644
--- a/common/src/main/java/org/apache/sedona/common/simplify/BaseSimplifier.java
+++ b/common/src/main/java/org/apache/sedona/common/simplify/BaseSimplifier.java
@@ -18,8 +18,4 @@
  */
 package org.apache.sedona.common.simplify;
 
-import org.locationtech.jts.geom.GeometryFactory;
-
-public abstract class BaseSimplifier {
-  protected static GeometryFactory geometryFactory = new GeometryFactory();
-}
+public abstract class BaseSimplifier {}
diff --git 
a/common/src/main/java/org/apache/sedona/common/simplify/GeometryCollectionSimplifier.java
 
b/common/src/main/java/org/apache/sedona/common/simplify/GeometryCollectionSimplifier.java
index be40d3597..914b6e5dd 100644
--- 
a/common/src/main/java/org/apache/sedona/common/simplify/GeometryCollectionSimplifier.java
+++ 
b/common/src/main/java/org/apache/sedona/common/simplify/GeometryCollectionSimplifier.java
@@ -57,6 +57,7 @@ public class GeometryCollectionSimplifier extends 
BaseSimplifier {
             .map(Geometry::getGeometryType)
             .distinct()
             .toArray(String[]::new);
+    GeometryFactory geometryFactory = geom.getFactory();
     if (distinctGeometries.length == 1) {
       switch (distinctGeometries[0]) {
         case Geometry.TYPENAME_LINESTRING:
diff --git 
a/common/src/main/java/org/apache/sedona/common/simplify/LineStringSimplifier.java
 
b/common/src/main/java/org/apache/sedona/common/simplify/LineStringSimplifier.java
index 4c49ed0d2..7d9cb64cb 100644
--- 
a/common/src/main/java/org/apache/sedona/common/simplify/LineStringSimplifier.java
+++ 
b/common/src/main/java/org/apache/sedona/common/simplify/LineStringSimplifier.java
@@ -21,6 +21,7 @@ package org.apache.sedona.common.simplify;
 import org.apache.commons.lang3.ArrayUtils;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
 
 public class LineStringSimplifier extends BaseSimplifier {
 
@@ -28,6 +29,7 @@ public class LineStringSimplifier extends BaseSimplifier {
     Coordinate[] simplified =
         CoordinatesSimplifier.simplifyInPlace(geom.getCoordinates(), epsilon, 
2);
 
+    GeometryFactory geometryFactory = geom.getFactory();
     if (simplified.length == 1) {
       if (preserveCollapsed)
         return geometryFactory.createLineString(ArrayUtils.addAll(simplified, 
simplified));
diff --git 
a/common/src/main/java/org/apache/sedona/common/simplify/PolygonSimplifier.java 
b/common/src/main/java/org/apache/sedona/common/simplify/PolygonSimplifier.java
index ed2d560a6..593d3376b 100644
--- 
a/common/src/main/java/org/apache/sedona/common/simplify/PolygonSimplifier.java
+++ 
b/common/src/main/java/org/apache/sedona/common/simplify/PolygonSimplifier.java
@@ -18,12 +18,11 @@
  */
 package org.apache.sedona.common.simplify;
 
-import static org.apache.sedona.common.simplify.BaseSimplifier.geometryFactory;
-
 import java.util.ArrayList;
 import java.util.List;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.LinearRing;
 import org.locationtech.jts.geom.Polygon;
 
@@ -32,6 +31,7 @@ public class PolygonSimplifier {
     LinearRing exteriorRing = geom.getExteriorRing();
     int minPointsExternal = preserveCollapsed ? 4 : 0;
 
+    GeometryFactory geometryFactory = geom.getFactory();
     LinearRing simplifiedExterior =
         geometryFactory.createLinearRing(
             CoordinatesSimplifier.simplifyInPlace(
diff --git 
a/common/src/main/java/org/apache/sedona/common/subDivide/GeometrySubDivider.java
 
b/common/src/main/java/org/apache/sedona/common/subDivide/GeometrySubDivider.java
index ad9399ebf..c614e65f5 100644
--- 
a/common/src/main/java/org/apache/sedona/common/subDivide/GeometrySubDivider.java
+++ 
b/common/src/main/java/org/apache/sedona/common/subDivide/GeometrySubDivider.java
@@ -27,7 +27,6 @@ import org.geotools.geometry.jts.JTS;
 import org.locationtech.jts.geom.*;
 
 public class GeometrySubDivider {
-  private static GeometryFactory geometryFactory = new GeometryFactory();
 
   private static final double FP_TOLERANCE = 1e-12;
 
diff --git 
a/common/src/main/java/org/apache/sedona/common/subDivide/PivotFinder.java 
b/common/src/main/java/org/apache/sedona/common/subDivide/PivotFinder.java
index f3b71ba91..0bcf1a8c8 100644
--- a/common/src/main/java/org/apache/sedona/common/subDivide/PivotFinder.java
+++ b/common/src/main/java/org/apache/sedona/common/subDivide/PivotFinder.java
@@ -27,8 +27,6 @@ public class PivotFinder {
 
   private static final double DBL_MAX = Double.MAX_VALUE;
 
-  private static GeometryFactory geometryFactory = new GeometryFactory();
-
   public static double findPivot(
       Geometry geom, boolean splitOrdinate, double center, int 
numberOfVertices) {
     double pivot = DBL_MAX;
@@ -41,6 +39,7 @@ public class PivotFinder {
       // if the shell is too small, use the largest hole
       if (numberOfVertices >= 2 * lwPoly.getExteriorRing().getNumPoints()) {
         // find the hole with the largest area and assign to ringtotrim
+        GeometryFactory geometryFactory = geom.getFactory();
         double maxArea = 
geometryFactory.createPolygon(lwPoly.getExteriorRing()).getArea();
         for (int i = 0; i < lwPoly.getNumInteriorRing(); i++) {
           LinearRing curHole = lwPoly.getInteriorRingN(i);
diff --git 
a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java 
b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
index 2bdc4bde5..494f97bbb 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/FormatUtils.java
@@ -22,6 +22,7 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.util.*;
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.enums.FileDataSplitter;
 import org.apache.sedona.common.enums.GeometryType;
 import org.locationtech.jts.geom.*;
@@ -208,7 +209,12 @@ public class FormatUtils<T extends Geometry> implements 
Serializable {
     // For some unknown reasons, the wkb reader cannot be used in transient 
variable like the wkt
     // reader.
     WKBReader wkbReader = new WKBReader();
-    final Geometry geometry = wkbReader.read(aux);
+    Geometry geometry = wkbReader.read(aux);
+    if (geometry.getSRID() != geometry.getFactory().getSRID()) {
+      // Make sure that the geometry factory has the correct SRID when the 
parsed WKB
+      // contains a non-zero SRID (EWKB)
+      geometry = Functions.setSRID(geometry, geometry.getSRID());
+    }
     handleNonSpatialDataToGeometry(geometry, Arrays.asList(columns));
 
     return geometry;
diff --git a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java 
b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
index 69f2dc04a..9ec7d4739 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/GeomUtils.java
@@ -22,11 +22,11 @@ import static 
org.locationtech.jts.geom.Coordinate.NULL_ORDINATE;
 
 import java.nio.ByteOrder;
 import java.util.*;
+import org.apache.sedona.common.Functions;
 import org.locationtech.jts.algorithm.Angle;
 import org.locationtech.jts.algorithm.distance.DiscreteFrechetDistance;
 import org.locationtech.jts.algorithm.distance.DiscreteHausdorffDistance;
 import org.locationtech.jts.geom.*;
-import org.locationtech.jts.geom.impl.CoordinateArraySequence;
 import org.locationtech.jts.io.ByteOrderValues;
 import org.locationtech.jts.io.WKBWriter;
 import org.locationtech.jts.io.WKTWriter;
@@ -147,13 +147,13 @@ public class GeomUtils {
       return null;
     }
 
-    Coordinate[] nthCoordinate = new Coordinate[1];
+    Coordinate coordinate;
     if (n > 0) {
-      nthCoordinate[0] = lineString.getCoordinates()[n - 1];
+      coordinate = lineString.getCoordinates()[n - 1];
     } else {
-      nthCoordinate[0] = lineString.getCoordinates()[p + n];
+      coordinate = lineString.getCoordinates()[p + n];
     }
-    return new Point(new CoordinateArraySequence(nthCoordinate), 
lineString.getFactory());
+    return lineString.getFactory().createPoint(coordinate);
   }
 
   public static Geometry getExteriorRing(Geometry geometry) {
@@ -218,7 +218,7 @@ public class GeomUtils {
 
   public static Geometry get2dGeom(Geometry geom) {
     Coordinate[] coordinates = geom.getCoordinates();
-    GeometryFactory geometryFactory = new GeometryFactory();
+    GeometryFactory geometryFactory = geom.getFactory();
     CoordinateSequence sequence =
         geometryFactory.getCoordinateSequenceFactory().create(coordinates);
     if (sequence.getDimension() > 2) {
@@ -260,7 +260,9 @@ public class GeomUtils {
     Geometry outputGeom = unaryUnionOp.union();
     if (outputGeom != null) {
       outputGeom.normalize();
-      outputGeom.setSRID(srid);
+      if (outputGeom.getSRID() != srid) {
+        outputGeom = Functions.setSRID(outputGeom, srid);
+      }
     }
     return outputGeom;
   }
@@ -446,19 +448,6 @@ public class GeomUtils {
     return geometries;
   }
 
-  public static Geometry get3DGeom(Geometry geometry, double zValue) {
-    Coordinate[] coordinates = geometry.getCoordinates();
-    if (coordinates.length == 0) return geometry;
-    for (int i = 0; i < coordinates.length; i++) {
-      boolean is3d = !Double.isNaN(coordinates[i].z);
-      if (!is3d) {
-        coordinates[i].setZ(zValue);
-      }
-    }
-    geometry.geometryChanged();
-    return geometry;
-  }
-
   public static int getPolygonNumRings(Polygon polygon) {
     LinearRing shell = polygon.getExteriorRing();
     if (shell == null || shell.isEmpty()) {
diff --git 
a/common/src/main/java/org/apache/sedona/common/utils/GeometryForce3DTransformer.java
 
b/common/src/main/java/org/apache/sedona/common/utils/GeometryForce3DTransformer.java
new file mode 100644
index 000000000..ab8b47767
--- /dev/null
+++ 
b/common/src/main/java/org/apache/sedona/common/utils/GeometryForce3DTransformer.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.common.utils;
+
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+public class GeometryForce3DTransformer extends GeometryTransformer {
+
+  private final double zValue;
+
+  public GeometryForce3DTransformer(double zValue) {
+    this.zValue = zValue;
+  }
+
+  @Override
+  protected CoordinateSequence transformCoordinates(CoordinateSequence coords, 
Geometry parent) {
+    Coordinate[] newCoords = new Coordinate[coords.size()];
+    for (int i = 0; i < coords.size(); i++) {
+      Coordinate coordinate = coords.getCoordinate(i);
+      double z = coordinate.getZ();
+      if (Double.isNaN(z)) {
+        z = zValue;
+      }
+      newCoords[i] = new Coordinate(coordinate.getX(), coordinate.getY(), z);
+    }
+
+    return createCoordinateSequence(newCoords);
+  }
+
+  public static Geometry transform(Geometry geometry, double zValue) {
+    if (geometry.getCoordinates().length == 0) return geometry;
+    GeometryForce3DTransformer transformer = new 
GeometryForce3DTransformer(zValue);
+    return transformer.transform(geometry);
+  }
+}
diff --git a/common/src/main/java/org/apache/sedona/common/utils/H3Utils.java 
b/common/src/main/java/org/apache/sedona/common/utils/H3Utils.java
index d6fe9d4de..329d9a437 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/H3Utils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/H3Utils.java
@@ -32,7 +32,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.locationtech.jts.geom.Coordinate;
-import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.Polygon;
 
@@ -265,7 +264,7 @@ public class H3Utils {
             cells.addAll(
                 polygonToCells(
                     (Polygon)
-                        (new GeometryFactory()
+                        (line.getFactory()
                             .createLineString(new Coordinate[] {cs, ce})
                             .getEnvelope()),
                     level,
diff --git 
a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java 
b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
index ce8bb5c43..9619b3ba7 100644
--- a/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/ConstructorsTest.java
@@ -22,9 +22,12 @@ import static org.junit.Assert.*;
 
 import org.apache.sedona.common.utils.GeomUtils;
 import org.junit.Test;
+import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.io.ParseException;
+import org.locationtech.jts.io.WKBWriter;
 
 public class ConstructorsTest {
 
@@ -66,6 +69,45 @@ public class ConstructorsTest {
     assertEquals("Unknown geometry type: NOT (line 1)", invalid.getMessage());
   }
 
+  @Test
+  public void geomFromWKB() throws ParseException {
+    GeometryFactory factory = new GeometryFactory();
+    Geometry geom = factory.createPoint(new Coordinate(1, 2));
+
+    // Test WKB without SRID
+    WKBWriter wkbWriter = new WKBWriter();
+    byte[] wkb = wkbWriter.write(geom);
+    Geometry result = Constructors.geomFromWKB(wkb);
+    assertEquals(geom, result);
+    assertEquals(0, result.getSRID());
+    assertEquals(0, result.getFactory().getSRID());
+
+    // Test specifying SRID
+    result = Constructors.geomFromWKB(wkb, 1000);
+    assertEquals(geom, result);
+    assertEquals(1000, result.getSRID());
+    assertEquals(1000, result.getFactory().getSRID());
+
+    // Test EWKB with SRID
+    wkbWriter = new WKBWriter(2, true);
+    geom.setSRID(2000);
+    wkb = wkbWriter.write(geom);
+    result = Constructors.geomFromWKB(wkb);
+    assertEquals(geom, result);
+    assertEquals(2000, result.getSRID());
+    assertEquals(2000, result.getFactory().getSRID());
+
+    // Test overriding SRID
+    result = Constructors.geomFromWKB(wkb, 3000);
+    assertEquals(geom, result);
+    assertEquals(3000, result.getSRID());
+    assertEquals(3000, result.getFactory().getSRID());
+    result = Constructors.geomFromWKB(wkb, 0);
+    assertEquals(geom, result);
+    assertEquals(0, result.getSRID());
+    assertEquals(0, result.getFactory().getSRID());
+  }
+
   @Test
   public void mLineFromWKT() throws ParseException {
     assertNull(Constructors.mLineFromText(null, 0));
diff --git a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java 
b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
index aaa5f6e3b..3868b4539 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -627,13 +627,34 @@ public class FunctionsTest extends TestBase {
     return !copy.isEmpty();
   }
 
+  @Test
+  public void convexAndConcaveHullSRID() throws ParseException {
+    Geometry geom =
+        Constructors.geomFromWKT(
+            "POLYGON ((-92.8125 37.857507, -89.362793 36.013561, -92.548828 
35.782171, -92.197266 34.669359, -94.614258 36.4036, -92.834473 36.580247, 
-92.8125 37.857507))",
+            4326);
+    Geometry convex = Functions.convexHull(geom);
+    Geometry concave = Functions.concaveHull(geom, 1, false);
+    assertEquals(4326, convex.getSRID());
+    assertEquals(4326, concave.getSRID());
+  }
+
+  @Test
+  public void envelopeAndCentroidSRID() throws ParseException {
+    Geometry geom = Constructors.geomFromWKT("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 
0))", 3857);
+    Geometry envelope = Functions.envelope(geom);
+    assertEquals(3857, envelope.getSRID());
+    Geometry centroid = Functions.getCentroid(geom);
+    assertEquals(3857, centroid.getSRID());
+  }
+
   @Test
   public void getGoogleS2CellIDsPoint() {
     Point point = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 2));
     Long[] cid = Functions.s2CellIDs(point, 30);
     Polygon reversedPolygon = S2Utils.toJTSPolygon(new S2CellId(cid[0]));
     // cast the cell to a rectangle, it must be able to cover the points
-    assert (reversedPolygon.contains(point));
+    assertTrue(reversedPolygon.contains(point));
   }
 
   @Test
@@ -1648,9 +1669,9 @@ public class FunctionsTest extends TestBase {
   @Test
   public void force3DObject2DDefaultValue() {
     int expectedDims = 3;
-    Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 90, 
0, 0));
+    Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray(0, 0, 0, 90, 
90, 90, 0, 0));
     Polygon expectedPolygon =
-        GEOMETRY_FACTORY.createPolygon(coordArray3d(0, 0, 0, 0, 90, 0, 0, 0, 
0));
+        GEOMETRY_FACTORY.createPolygon(coordArray3d(0, 0, 0, 0, 90, 0, 90, 90, 
0, 0, 0, 0));
     Geometry forcedPolygon = Functions.force3D(polygon);
     WKTWriter wktWriter = new 
WKTWriter(GeomUtils.getDimension(expectedPolygon));
     assertEquals(wktWriter.write(expectedPolygon), 
wktWriter.write(forcedPolygon));
@@ -1846,7 +1867,8 @@ public class FunctionsTest extends TestBase {
   @Test
   public void force3DObject3DDefaultValue() {
     int expectedDims = 3;
-    Polygon polygon = GEOMETRY_FACTORY.createPolygon(coordArray3d(0, 0, 0, 90, 
0, 0, 0, 0, 0));
+    Polygon polygon =
+        GEOMETRY_FACTORY.createPolygon(coordArray3d(0, 0, 0, 90, 0, 0, 90, 90, 
0, 0, 0, 0));
     Geometry forcedPolygon = Functions.force3D(polygon);
     WKTWriter wktWriter = new WKTWriter(GeomUtils.getDimension(polygon));
     assertEquals(wktWriter.write(polygon), wktWriter.write(forcedPolygon));
@@ -1876,12 +1898,11 @@ public class FunctionsTest extends TestBase {
         GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {polygon3D, 
polygon});
     Point point3D = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1, 1));
     LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(1, 0, 
1, 1, 1, 2));
-    LineString emptyLineString = GEOMETRY_FACTORY.createLineString();
     Geometry geomCollection =
         GEOMETRY_FACTORY.createGeometryCollection(
             new Geometry[] {
               GEOMETRY_FACTORY.createGeometryCollection(
-                  new Geometry[] {multiPolygon, point3D, emptyLineString, 
lineString})
+                  new Geometry[] {multiPolygon, point3D, lineString})
             });
     Polygon expectedPolygon3D =
         GEOMETRY_FACTORY.createPolygon(coordArray3d(1, 0, 2, 1, 1, 2, 2, 1, 2, 
2, 0, 2, 1, 0, 2));
@@ -1900,12 +1921,9 @@ public class FunctionsTest extends TestBase {
     assertEquals(
         wktWriter3D.write(point3D),
         
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(1)));
-    assertEquals(
-        emptyLineString.toText(),
-        actualGeometryCollection.getGeometryN(0).getGeometryN(2).toText());
     assertEquals(
         wktWriter3D.write(expectedLineString3D),
-        
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(3)));
+        
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(2)));
   }
 
   @Test
@@ -1917,12 +1935,11 @@ public class FunctionsTest extends TestBase {
         GEOMETRY_FACTORY.createMultiPolygon(new Polygon[] {polygon3D, 
polygon});
     Point point3D = GEOMETRY_FACTORY.createPoint(new Coordinate(1, 1, 1));
     LineString lineString = GEOMETRY_FACTORY.createLineString(coordArray(1, 0, 
1, 1, 1, 2));
-    LineString emptyLineString = GEOMETRY_FACTORY.createLineString();
     Geometry geomCollection =
         GEOMETRY_FACTORY.createGeometryCollection(
             new Geometry[] {
               GEOMETRY_FACTORY.createGeometryCollection(
-                  new Geometry[] {multiPolygon, point3D, emptyLineString, 
lineString})
+                  new Geometry[] {multiPolygon, point3D, lineString})
             });
     Polygon expectedPolygon3D =
         GEOMETRY_FACTORY.createPolygon(coordArray3d(1, 0, 0, 1, 1, 0, 2, 1, 0, 
2, 0, 0, 1, 0, 0));
@@ -1941,12 +1958,9 @@ public class FunctionsTest extends TestBase {
     assertEquals(
         wktWriter3D.write(point3D),
         
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(1)));
-    assertEquals(
-        emptyLineString.toText(),
-        actualGeometryCollection.getGeometryN(0).getGeometryN(2).toText());
     assertEquals(
         wktWriter3D.write(expectedLineString3D),
-        
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(3)));
+        
wktWriter3D.write(actualGeometryCollection.getGeometryN(0).getGeometryN(2)));
   }
 
   @Test
@@ -2228,6 +2242,19 @@ public class FunctionsTest extends TestBase {
     assertEquals(expected, actual);
   }
 
+  @Test
+  public void testBufferSRID() throws ParseException {
+    Geometry geom = geomFromWKT("POINT (10 20)", 3857);
+    Geometry buffered = Functions.buffer(geom, 1);
+    assertEquals(3857, buffered.getSRID());
+    assertEquals(3857, buffered.getFactory().getSRID());
+
+    geom = geomFromWKT("POINT (10 20)", 4326);
+    buffered = Functions.buffer(geom, 1, true);
+    assertEquals(4326, buffered.getSRID());
+    assertEquals(4326, buffered.getFactory().getSRID());
+  }
+
   @Test
   public void testSnap() throws ParseException {
     Geometry poly =
@@ -3309,11 +3336,15 @@ public class FunctionsTest extends TestBase {
     Geometry geomActual = FunctionsGeoTools.transform(geomExpected, 
"EPSG:4326", "EPSG:4326");
     assertEquals(geomExpected.getCoordinate().x, geomActual.getCoordinate().x, 
FP_TOLERANCE);
     assertEquals(geomExpected.getCoordinate().y, geomActual.getCoordinate().y, 
FP_TOLERANCE);
+    assertEquals(4326, geomActual.getSRID());
+    assertEquals(4326, geomActual.getFactory().getSRID());
 
     // The source and target CRS are different
     geomActual = FunctionsGeoTools.transform(geomExpected, "EPSG:4326", 
"EPSG:3857");
     assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, 
FP_TOLERANCE);
     assertEquals(8399737.889818355, geomActual.getCoordinate().y, 
FP_TOLERANCE);
+    assertEquals(3857, geomActual.getSRID());
+    assertEquals(3857, geomActual.getFactory().getSRID());
 
     // The source CRS is not specified and the geometry has no SRID
     Exception e =
@@ -3334,12 +3365,16 @@ public class FunctionsTest extends TestBase {
     geomActual = FunctionsGeoTools.transform(geomExpected, crsWkt, 
"EPSG:3857");
     assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, 
FP_TOLERANCE);
     assertEquals(8399737.889818355, geomActual.getCoordinate().y, 
FP_TOLERANCE);
+    assertEquals(3857, geomActual.getSRID());
+    assertEquals(3857, geomActual.getFactory().getSRID());
 
     // The source CRS is not specified but the geometry has a valid SRID
     geomExpected.setSRID(4326);
     geomActual = FunctionsGeoTools.transform(geomExpected, "EPSG:3857");
     assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, 
FP_TOLERANCE);
     assertEquals(8399737.889818355, geomActual.getCoordinate().y, 
FP_TOLERANCE);
+    assertEquals(3857, geomActual.getSRID());
+    assertEquals(3857, geomActual.getFactory().getSRID());
 
     // The source and target CRS are different, and latitude is out of range
     Point geometryWrong = GEOMETRY_FACTORY.createPoint(new Coordinate(60, 
120));
diff --git a/docs/api/sql/Constructor.md b/docs/api/sql/Constructor.md
index c1417cf27..c69f15d19 100644
--- a/docs/api/sql/Constructor.md
+++ b/docs/api/sql/Constructor.md
@@ -303,42 +303,6 @@ Output:
 POINT(40.7128 -74.006)
 ```
 
-## ST_GeomFromEWKB
-
-Introduction: Construct a Geometry from EWKB string or Binary. This function 
is an alias of [ST_GeomFromWKB](#st_geomfromwkb).
-
-Format:
-
-`ST_GeomFromEWKB (Wkb: String)`
-
-`ST_GeomFromEWKB (Wkb: Binary)`
-
-Since: `v1.6.1`
-
-SQL Example
-
-```sql
-SELECT ST_GeomFromEWKB([01 02 00 00 00 02 00 00 00 00 00 00 00 84 D6 00 C0 00 
00 00 00 80 B5 D6 BF 00 00 00 60 E1 EF F7 BF 00 00 00 80 07 5D E5 BF])
-```
-
-Output:
-
-```
-LINESTRING (-2.1047439575195312 -0.354827880859375, -1.49606454372406 
-0.6676061153411865)
-```
-
-SQL Example
-
-```sql
-SELECT 
ST_asEWKT(ST_GeomFromEWKB('01010000a0e6100000000000000000f03f000000000000f03f000000000000f03f'))
-```
-
-Output:
-
-```
-SRID=4326;POINT Z(1 1 1)
-```
-
 ## ST_GeomFromWKB
 
 Introduction: Construct a Geometry from WKB string or Binary. This function 
also supports EWKB format.
diff --git 
a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java 
b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
index 59501155e..d7732a876 100644
--- a/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
+++ b/flink/src/main/java/org/apache/sedona/flink/expressions/Constructors.java
@@ -20,13 +20,13 @@ package org.apache.sedona.flink.expressions;
 
 import org.apache.flink.table.annotation.DataTypeHint;
 import org.apache.flink.table.functions.ScalarFunction;
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.enums.FileDataSplitter;
 import org.apache.sedona.common.enums.GeometryType;
 import org.apache.sedona.common.utils.FormatUtils;
 import org.apache.sedona.common.utils.GeoHashDecoder;
 import org.locationtech.jts.geom.*;
 import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKBReader;
 import org.locationtech.jts.io.gml2.GMLReader;
 import org.locationtech.jts.io.kml.KMLReader;
 
@@ -279,8 +279,7 @@ public class Constructors {
 
     @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
     public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws 
ParseException {
-      WKBReader wkbReader = new WKBReader();
-      return wkbReader.read(wkb);
+      return org.apache.sedona.common.Constructors.geomFromWKB(wkb);
     }
   }
 
@@ -292,8 +291,7 @@ public class Constructors {
 
     @DataTypeHint(value = "RAW", bridgedTo = 
org.locationtech.jts.geom.Geometry.class)
     public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws 
ParseException {
-      WKBReader wkbReader = new WKBReader();
-      return wkbReader.read(wkb);
+      return org.apache.sedona.common.Constructors.geomFromWKB(wkb);
     }
   }
 
@@ -302,7 +300,6 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString) throws 
ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof Point) {
-        geometry.setSRID(0);
         return geometry;
       }
       return null; // Return null if geometry is not a Point
@@ -312,7 +309,7 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString, int srid) 
throws ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof Point) {
-        geometry.setSRID(srid);
+        geometry = Functions.setSRID(geometry, srid);
         return geometry;
       }
       return null; // Return null if geometry is not a Point
@@ -320,7 +317,7 @@ public class Constructors {
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
     public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws 
ParseException {
-      return org.apache.sedona.common.Constructors.pointFromWKB(wkb, 0);
+      return org.apache.sedona.common.Constructors.pointFromWKB(wkb);
     }
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
@@ -334,7 +331,6 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString) throws 
ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof LineString) {
-        geometry.setSRID(0);
         return geometry;
       }
       return null; // Return null if geometry is not a Point
@@ -344,7 +340,7 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString, int srid) 
throws ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof LineString) {
-        geometry.setSRID(srid);
+        geometry = Functions.setSRID(geometry, srid);
         return geometry;
       }
       return null; // Return null if geometry is not a Linestring
@@ -352,7 +348,7 @@ public class Constructors {
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
     public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws 
ParseException {
-      return org.apache.sedona.common.Constructors.lineFromWKB(wkb, 0);
+      return org.apache.sedona.common.Constructors.lineFromWKB(wkb);
     }
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
@@ -366,7 +362,6 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString) throws 
ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof LineString) {
-        geometry.setSRID(0);
         return geometry;
       }
       return null; // Return null if geometry is not a Linestring
@@ -376,7 +371,7 @@ public class Constructors {
     public Geometry eval(@DataTypeHint("String") String wkbString, int srid) 
throws ParseException {
       Geometry geometry = getGeometryByFileData(wkbString, 
FileDataSplitter.WKB);
       if (geometry instanceof LineString) {
-        geometry.setSRID(srid);
+        geometry = Functions.setSRID(geometry, srid);
         return geometry;
       }
       return null; // Return null if geometry is not a Linestring
@@ -384,7 +379,7 @@ public class Constructors {
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
     public Geometry eval(@DataTypeHint("Bytes") byte[] wkb) throws 
ParseException {
-      return org.apache.sedona.common.Constructors.lineFromWKB(wkb, 0);
+      return org.apache.sedona.common.Constructors.lineFromWKB(wkb);
     }
 
     @DataTypeHint(value = "RAW", bridgedTo = Geometry.class)
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeometrySerde.java
 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeometrySerde.java
index a395d61a1..73002d5bd 100644
--- 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeometrySerde.java
+++ 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/GeometrySerde.java
@@ -19,6 +19,7 @@
 package org.apache.sedona.snowflake.snowsql;
 
 import java.util.Arrays;
+import org.apache.sedona.common.Constructors;
 import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.enums.FileDataSplitter;
 import org.apache.sedona.common.utils.FormatUtils;
@@ -27,7 +28,6 @@ import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryCollection;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.io.ParseException;
-import org.locationtech.jts.io.WKBReader;
 
 public class GeometrySerde {
 
@@ -51,7 +51,7 @@ public class GeometrySerde {
 
   public static Geometry deserialize(byte[] bytes) {
     try {
-      return new WKBReader().read(bytes);
+      return Constructors.geomFromWKB(bytes);
     } catch (ParseException e) {
       String msg =
           String.format(
diff --git 
a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java 
b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
index 601c9cc06..7985017cb 100644
--- a/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
+++ b/snowflake/src/main/java/org/apache/sedona/snowflake/snowsql/UDFs.java
@@ -789,7 +789,7 @@ public class UDFs {
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb"})
   public static byte[] ST_PointFromWKB(byte[] wkb) throws ParseException {
-    return GeometrySerde.serialize(Constructors.pointFromWKB(wkb, 0));
+    return GeometrySerde.serialize(Constructors.pointFromWKB(wkb));
   }
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb", "srid"})
@@ -799,7 +799,7 @@ public class UDFs {
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb"})
   public static byte[] ST_LineFromWKB(byte[] wkb) throws ParseException {
-    return GeometrySerde.serialize(Constructors.lineFromWKB(wkb, 0));
+    return GeometrySerde.serialize(Constructors.lineFromWKB(wkb));
   }
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb", "srid"})
@@ -809,7 +809,7 @@ public class UDFs {
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb"})
   public static byte[] ST_LinestringFromWKB(byte[] wkb) throws ParseException {
-    return GeometrySerde.serialize(Constructors.lineFromWKB(wkb, 0));
+    return GeometrySerde.serialize(Constructors.lineFromWKB(wkb));
   }
 
   @UDFAnnotations.ParamMeta(argNames = {"wkb", "srid"})
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
index c28369754..b1a82ba10 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/Constructors.scala
@@ -18,7 +18,7 @@
  */
 package org.apache.spark.sql.sedona_sql.expressions
 
-import org.apache.sedona.common.Constructors
+import org.apache.sedona.common.{Constructors, Functions}
 import org.apache.sedona.common.enums.FileDataSplitter
 import org.apache.sedona.sql.utils.GeometrySerializer
 import org.apache.spark.sql.catalyst.InternalRow
@@ -234,22 +234,23 @@ case class ST_LineFromWKB(inputExpressions: 
Seq[Expression])
 
   override def eval(inputRow: InternalRow): Any = {
     val wkb = inputExpressions.head.eval(inputRow)
-    val srid = if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow) else 0
+    val srid =
+      if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow).asInstanceOf[Int]
+      else -1
 
     wkb match {
       case geomString: UTF8String =>
         // Parse UTF-8 encoded WKB string
         val geom = Constructors.lineStringFromText(geomString.toString, "wkb")
         if (geom.getGeometryType == "LineString") {
-          geom.setSRID(srid.asInstanceOf[Int])
-          geom.toGenericArrayData
+          (if (srid != -1) Functions.setSRID(geom, srid) else 
geom).toGenericArrayData
         } else {
           null
         }
 
       case wkbArray: Array[Byte] =>
         // Convert raw WKB byte array to geometry
-        Constructors.lineFromWKB(wkbArray, 
srid.asInstanceOf[Int]).toGenericArrayData
+        Constructors.lineFromWKB(wkbArray, srid).toGenericArrayData
 
       case _ => null
     }
@@ -282,22 +283,23 @@ case class ST_LinestringFromWKB(inputExpressions: 
Seq[Expression])
 
   override def eval(inputRow: InternalRow): Any = {
     val wkb = inputExpressions.head.eval(inputRow)
-    val srid = if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow) else 0
+    val srid =
+      if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow).asInstanceOf[Int]
+      else -1
 
     wkb match {
       case geomString: UTF8String =>
         // Parse UTF-8 encoded WKB string
         val geom = Constructors.lineStringFromText(geomString.toString, "wkb")
         if (geom.getGeometryType == "LineString") {
-          geom.setSRID(srid.asInstanceOf[Int])
-          geom.toGenericArrayData
+          (if (srid != -1) Functions.setSRID(geom, srid) else 
geom).toGenericArrayData
         } else {
           null
         }
 
       case wkbArray: Array[Byte] =>
         // Convert raw WKB byte array to geometry
-        Constructors.lineFromWKB(wkbArray, 
srid.asInstanceOf[Int]).toGenericArrayData
+        Constructors.lineFromWKB(wkbArray, srid).toGenericArrayData
 
       case _ => null
     }
@@ -330,22 +332,23 @@ case class ST_PointFromWKB(inputExpressions: 
Seq[Expression])
 
   override def eval(inputRow: InternalRow): Any = {
     val wkb = inputExpressions.head.eval(inputRow)
-    val srid = if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow) else 0
+    val srid =
+      if (inputExpressions.length > 1) 
inputExpressions(1).eval(inputRow).asInstanceOf[Int]
+      else -1
 
     wkb match {
       case geomString: UTF8String =>
         // Parse UTF-8 encoded WKB string
         val geom = Constructors.pointFromText(geomString.toString, "wkb")
         if (geom.getGeometryType == "Point") {
-          geom.setSRID(srid.asInstanceOf[Int])
-          geom.toGenericArrayData
+          (if (srid != -1) Functions.setSRID(geom, srid) else 
geom).toGenericArrayData
         } else {
           null
         }
 
       case wkbArray: Array[Byte] =>
         // Convert raw WKB byte array to geometry
-        Constructors.pointFromWKB(wkbArray, 
srid.asInstanceOf[Int]).toGenericArrayData
+        Constructors.pointFromWKB(wkbArray, srid).toGenericArrayData
 
       case _ => null
     }
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 16c325714..47711716a 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
@@ -606,8 +606,6 @@ case class ST_MinimumBoundingRadius(inputExpressions: 
Seq[Expression])
 
   override def nullable: Boolean = true
 
-  private val geometryFactory = new GeometryFactory()
-
   override def eval(input: InternalRow): Any = {
     val expr = inputExpressions(0)
     val geometry = expr match {
@@ -623,7 +621,7 @@ case class ST_MinimumBoundingRadius(inputExpressions: 
Seq[Expression])
 
   private def getMinimumBoundingRadius(geom: Geometry): InternalRow = {
     val minimumBoundingCircle = new MinimumBoundingCircle(geom)
-    val centerPoint = 
geometryFactory.createPoint(minimumBoundingCircle.getCentre)
+    val centerPoint = 
geom.getFactory.createPoint(minimumBoundingCircle.getCentre)
     InternalRow(centerPoint.toGenericArrayData, 
minimumBoundingCircle.getRadius)
   }
 
diff --git 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
index ca22543b4..890ac1039 100644
--- 
a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
+++ 
b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/expressions/st_constructors.scala
@@ -143,22 +143,22 @@ object st_constructors extends DataFrameAPI {
   def ST_PointZM(x: Double, y: Double, z: Double, m: Double, srid: Int): 
Column =
     wrapExpression[ST_PointZM](x, y, z, m, srid)
 
-  def ST_PointFromWKB(wkb: Column): Column = 
wrapExpression[ST_PointFromWKB](wkb, 0)
-  def ST_PointFromWKB(wkb: String): Column = 
wrapExpression[ST_PointFromWKB](wkb, 0)
+  def ST_PointFromWKB(wkb: Column): Column = 
wrapExpression[ST_PointFromWKB](wkb)
+  def ST_PointFromWKB(wkb: String): Column = 
wrapExpression[ST_PointFromWKB](wkb)
 
   def ST_PointFromWKB(wkb: Column, srid: Column): Column =
     wrapExpression[ST_PointFromWKB](wkb, srid)
   def ST_PointFromWKB(wkb: String, srid: Int): Column = 
wrapExpression[ST_PointFromWKB](wkb, srid)
 
-  def ST_LineFromWKB(wkb: Column): Column = 
wrapExpression[ST_LineFromWKB](wkb, 0)
-  def ST_LineFromWKB(wkb: String): Column = 
wrapExpression[ST_LineFromWKB](wkb, 0)
+  def ST_LineFromWKB(wkb: Column): Column = wrapExpression[ST_LineFromWKB](wkb)
+  def ST_LineFromWKB(wkb: String): Column = wrapExpression[ST_LineFromWKB](wkb)
 
   def ST_LineFromWKB(wkb: Column, srid: Column): Column =
     wrapExpression[ST_LineFromWKB](wkb, srid)
   def ST_LineFromWKB(wkb: String, srid: Int): Column = 
wrapExpression[ST_LineFromWKB](wkb, srid)
 
-  def ST_LinestringFromWKB(wkb: Column): Column = 
wrapExpression[ST_LinestringFromWKB](wkb, 0)
-  def ST_LinestringFromWKB(wkb: String): Column = 
wrapExpression[ST_LinestringFromWKB](wkb, 0)
+  def ST_LinestringFromWKB(wkb: Column): Column = 
wrapExpression[ST_LinestringFromWKB](wkb)
+  def ST_LinestringFromWKB(wkb: String): Column = 
wrapExpression[ST_LinestringFromWKB](wkb)
   def ST_LinestringFromWKB(wkb: Column, srid: Column): Column =
     wrapExpression[ST_LinestringFromWKB](wkb, srid)
   def ST_LinestringFromWKB(wkb: String, srid: Int): Column =
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
new file mode 100644
index 000000000..1d4cc9e8b
--- /dev/null
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/PreserveSRIDSuite.scala
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sedona.sql
+
+import org.apache.sedona.common.Constructors
+import org.apache.spark.sql.{DataFrame, Row}
+import org.apache.spark.sql.sedona_sql.UDT.GeometryUDT
+import org.apache.spark.sql.types.{StructField, StructType}
+import org.locationtech.jts.geom.Geometry
+import org.scalatest.prop.TableDrivenPropertyChecks
+
+import scala.collection.mutable
+
+class PreserveSRIDSuite extends TestBaseScala with TableDrivenPropertyChecks {
+  private var testDf: DataFrame = _
+
+  override def beforeAll(): Unit = {
+    super.beforeAll()
+    testDf = prepareTestDataFrame()
+  }
+
+  describe("Preserve SRID") {
+    val testCases = Table(
+      "test case",
+      ("ST_ConcaveHull(geom1, 1, false)", 1000),
+      ("ST_ConcaveHull(geom1, 1, true)", 1000),
+      ("ST_ConvexHull(geom1)", 1000),
+      ("ST_Buffer(geom1, 1)", 1000),
+      ("ST_ShiftLongitude(geom1)", 1000),
+      ("ST_Envelope(geom1)", 1000),
+      ("ST_Centroid(geom1)", 1000),
+      ("ST_Transform(geom1, 'EPSG:4326', 'EPSG:3857')", 3857),
+      ("ST_Intersection(geom1, ST_Point(0, 1))", 1000),
+      ("ST_MakeValid(geom1)", 1000),
+      ("ST_ReducePrecision(geom1, 6)", 1000),
+      ("ST_SimplifyVW(geom1, 0.1)", 1000),
+      ("ST_SimplifyPolygonHull(geom1, 0.5)", 1000),
+      ("ST_SetSRID(geom1, 2000)", 2000),
+      ("ST_LineMerge(geom2)", 1000),
+      ("ST_StartPoint(geom3)", 1000),
+      ("ST_Snap(geom3, geom3, 0.1)", 1000),
+      ("ST_Boundary(geom1)", 1000),
+      ("ST_LineSubstring(geom3, 0.1, 0.9)", 1000),
+      ("ST_LineInterpolatePoint(geom3, 0.1)", 1000),
+      ("ST_EndPoint(geom3)", 1000),
+      ("ST_ExteriorRing(geom1)", 1000),
+      ("ST_GeometryN(geom2, 2)", 1000),
+      ("ST_InteriorRingN(geom4, 0)", 1000),
+      ("ST_Dump(geom2)", 1000),
+      ("ST_DumpPoints(geom2)", 1000),
+      ("ST_AddMeasure(geom3, 0, 1)", 1000),
+      ("ST_AddPoint(geom3, ST_Point(0.5, 0.5), 1)", 1000),
+      ("ST_RemovePoint(geom3, 1)", 1000),
+      ("ST_SetPoint(geom3, 1, ST_Point(0.5, 0.5))", 1000),
+      ("ST_ClosestPoint(geom1, geom2)", 1000),
+      ("ST_FlipCoordinates(geom1)", 1000),
+      ("ST_SubDivide(geom4, 4)", 1000),
+      ("ST_MakeLine(geom3, geom3)", 1000),
+      ("ST_Points(geom1)", 1000),
+      ("ST_Polygon(ST_InteriorRingN(geom4, 0), 2000)", 2000),
+      ("ST_Polygonize(geom5)", 1000),
+      ("ST_MakePolygon(ST_ExteriorRing(geom4), ARRAY(ST_InteriorRingN(geom4, 
0)))", 1000),
+      ("ST_Difference(geom1, geom2)", 1000),
+      ("ST_SymDifference(geom1, geom2)", 1000),
+      ("ST_UnaryUnion(geom5)", 1000),
+      ("ST_Union(geom1, geom2)", 1000),
+      ("ST_Union(ARRAY(geom1, geom2))", 1000),
+      ("ST_Multi(geom1)", 1000),
+      ("ST_PointOnSurface(geom1)", 1000),
+      ("ST_Reverse(geom1)", 1000),
+      ("ST_PointN(geom3, 1)", 1000),
+      ("ST_Force_2D(ST_AddMeasure(geom3, 0, 1))", 1000),
+      ("ST_BuildArea(geom5)", 1000),
+      ("ST_Normalize(geom5)", 1000),
+      ("ST_LineFromMultiPoint(ST_Points(geom1))", 1000),
+      ("ST_Split(geom1, ST_MakeLine(ST_Point(0.5, -10), ST_Point(0.5, 10)))", 
1000),
+      ("ST_CollectionExtract(geom5, 2)", 1000),
+      ("ST_GeometricMedian(ST_Points(geom5))", 1000),
+      ("ST_LocateAlong(ST_AddMeasure(geom3, 0, 1), 0.25)", 1000),
+      ("ST_LongestLine(geom1, geom2)", 1000),
+      ("ST_Force3D(geom1, 10)", 1000),
+      ("ST_Force3DZ(geom1, 10)", 1000),
+      ("ST_Force3DM(geom1, 10)", 1000),
+      ("ST_Force4D(geom1, 5, 10)", 1000),
+      ("ST_ForceCollection(geom1)", 1000),
+      ("ST_ForcePolygonCW(ST_ForcePolygonCCW(geom1))", 1000),
+      ("ST_ForcePolygonCCW(ST_ForcePolygonCW(geom1))", 1000),
+      ("ST_Translate(geom1, 1, 2, 3)", 1000),
+      ("ST_TriangulatePolygon(geom4)", 1000),
+      ("ST_VoronoiPolygons(geom4)", 1000),
+      ("ST_Affine(geom1, 1, 2, 1, 2, 1, 2)", 1000),
+      ("ST_BoundingDiagonal(geom1)", 1000),
+      ("ST_DelaunayTriangles(geom4)", 1000),
+      ("ST_Rotate(geom1, 10)", 1000),
+      ("ST_Collect(geom1, geom2, geom3)", 1000))
+
+    forAll(testCases) { case (expression: String, srid: Int) =>
+      it(s"$expression") {
+        testDf.selectExpr(expression).collect().foreach { row =>
+          val value = row.getAs[AnyRef](0)
+          value match {
+            case geom: Geometry => assert(geom.getSRID == srid)
+            case geoms: mutable.WrappedArray[Geometry] =>
+              geoms.foreach(geom => assert(geom.getSRID == srid))
+            case _ => fail(s"Unexpected result: $value")
+          }
+        }
+      }
+    }
+  }
+
+  private def prepareTestDataFrame(): DataFrame = {
+    import scala.collection.JavaConverters._
+
+    val schema = StructType(
+      Seq(
+        StructField("geom1", GeometryUDT),
+        StructField("geom2", GeometryUDT),
+        StructField("geom3", GeometryUDT),
+        StructField("geom4", GeometryUDT),
+        StructField("geom5", GeometryUDT)))
+    val geom1 = Constructors.geomFromWKT("POLYGON ((0 0, 1 0, 0.5 0.5, 1 1, 0 
1, 0 0))", 1000)
+    val geom2 =
+      Constructors.geomFromWKT("MULTILINESTRING ((0 0, 0 1), (0 1, 1 1), (1 1, 
1 0))", 1000)
+    val geom3 = Constructors.geomFromWKT("LINESTRING (0 0, 0 1, 1 1, 1 0)", 
1000)
+    val geom4 = Constructors.geomFromWKT(
+      "POLYGON (( 30 10, 40 40, 20 40, 10 20, 30 10 ), ( 20 30, 35 35, 30 20, 
20 30 ))",
+      1000)
+    val geom5 = Constructors.geomFromWKT(
+      """GEOMETRYCOLLECTION (LINESTRING (2 0, 2 1, 2 2),
+        |LINESTRING (2 2, 2 3, 2 4), LINESTRING (0 2, 1 2, 2 2),
+        |LINESTRING (2 2, 3 2, 4 2), LINESTRING (0 2, 1 3, 2 4),
+        |LINESTRING (2 4, 3 3, 4 2))""".stripMargin,
+      1000)
+    val rows = Seq(Row(geom1, geom2, geom3, geom4, geom5))
+    sparkSession.createDataFrame(rows.asJava, schema)
+  }
+}
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
index 8f57fc4bc..8bf503927 100644
--- 
a/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
+++ 
b/spark/common/src/test/scala/org/apache/sedona/sql/constructorTestScala.scala
@@ -177,6 +177,17 @@ class constructorTestScala extends TestBaseScala {
         }
       }
 
+      val ewkb = "0020000001000007D03FF00000000000004000000000000000"
+      var geom = sparkSession.sql(s"SELECT 
ST_PointFromWKB('$ewkb')").first().getAs[Geometry](0)
+      assert(geom.toString == "POINT (1 2)")
+      assert(geom.getSRID == 2000)
+      geom = sparkSession.sql(s"SELECT ST_PointFromWKB('$ewkb', 
1000)").first().getAs[Geometry](0)
+      assert(geom.toString == "POINT (1 2)")
+      assert(geom.getSRID == 1000)
+      geom = sparkSession.sql(s"SELECT ST_PointFromWKB('$ewkb', 
0)").first().getAs[Geometry](0)
+      assert(geom.toString == "POINT (1 2)")
+      assert(geom.getSRID == 0)
+
       intercept[Exception] {
         sparkSession.sql("SELECT ST_PointFromWKB('invalid')").collect()
       }
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 33054a39c..ea399f4f1 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -554,7 +554,7 @@ class functionTestScala
     it("Passed ST_MMax") {
       var baseDf = sparkSession.sql(
         "SELECT ST_GeomFromWKT('LINESTRING ZM(1 1 1 1, 2 2 2 2, 3 3 3 3, -1 -1 
-1 -1)') as line")
-      val actual = baseDf.selectExpr("ST_MMax(line)").first().getDouble(0)
+      var actual = baseDf.selectExpr("ST_MMax(line)").first().getDouble(0)
       assert(actual == 3.0)
 
       baseDf =
@@ -562,11 +562,11 @@ class functionTestScala
       val actualNull = baseDf.selectExpr("ST_MMax(line)").first().get(0)
       assert(actualNull == null)
 
-      print(
-        sparkSession
-          .sql("SELECT ST_MMax(ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 
10 2, 20 40 15 3, 10 20 20 4, 30 10 5 1))'))")
-          .first()
-          .get(0))
+      actual = sparkSession
+        .sql("SELECT ST_MMax(ST_GeomFromWKT('POLYGON ZM ((30 10 5 1, 40 40 10 
2, 20 40 15 3, 10 20 20 4, 30 10 5 1))'))")
+        .first()
+        .getDouble(0)
+      assert(actual == 4.0)
     }
 
     it("Passed ST_MakeLine") {
@@ -2827,7 +2827,7 @@ class functionTestScala
     }
   }
 
-  it("Should pass ST_'Force3DZ'") {
+  it("Should pass ST_Force3DZ") {
     val geomTestCases = Map(
       ("'LINESTRING (0 1, 1 0, 2 0)'") -> ("'LINESTRING Z(0 1 1, 1 0 1, 2 0 
1)'", "'LINESTRING Z(0 1 0, 1 0 0, 2 0 0)'"),
       ("'LINESTRING Z(0 1 3, 1 0 3, 2 0 3)'") -> ("'LINESTRING Z(0 1 3, 1 0 3, 
2 0 3)'", "'LINESTRING Z(0 1 3, 1 0 3, 2 0 3)'"),

Reply via email to