You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2023/08/14 08:02:46 UTC

[sedona] 01/01: Use lon lat order

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

jiayu pushed a commit to branch use-lon-lat
in repository https://gitbox.apache.org/repos/asf/sedona.git

commit e44d4c33999d8e15f667f15bd5978c461c4f537d
Author: Jia Yu <ji...@apache.org>
AuthorDate: Mon Aug 14 01:02:36 2023 -0700

    Use lon lat order
---
 R/R/spatial_rdd.R                                  |   2 +-
 R/tests/testthat/test-crs-transform.R              |  10 +-
 R/vignettes/articles/apache-sedona.Rmd             |   8 +-
 .../java/org/apache/sedona/common/Functions.java   |  78 ++++++++--
 .../sedona/common/raster/RasterConstructors.java   |   9 +-
 .../apache/sedona/common/raster/RasterEditors.java |   4 +-
 .../apache/sedona/common/utils/RasterUtils.java    |  16 +-
 .../org/apache/sedona/common/FunctionsTest.java    |  44 ++++++
 .../sedona/common/raster/RasterAccessorsTest.java  |   2 +-
 .../sedona/common/raster/RasterPredicatesTest.java |   6 +-
 .../sedona/common/raster/RasterTestBase.java       |   2 +-
 .../sedona/core/spatialOperator/KNNQuery.java      |  11 +-
 .../sedona/core/spatialOperator/RangeQuery.java    |   4 +-
 .../apache/sedona/core/spatialRDD/SpatialRDD.java  |  29 +---
 .../sedona/core/utils/CRSTransformation.java       | 165 ---------------------
 .../sedona/core/utils/CRSTransformationTest.java   |   3 +-
 docs/api/flink/Function.md                         |  10 +-
 docs/api/sql/Function.md                           |  10 +-
 docs/api/sql/Raster-loader.md                      |   2 +
 docs/tutorial/rdd.md                               |   2 +
 docs/tutorial/sql.md                               |   2 +
 .../java/org/apache/sedona/flink/FunctionTest.java |   4 +-
 python/sedona/core/SpatialRDD/spatial_rdd.py       |   3 +
 python/tests/properties/crs_transform.py           |   2 +-
 python/tests/sql/test_dataframe_api.py             |   2 +-
 python/tests/sql/test_function.py                  |   2 +-
 .../streaming/spark/test_constructor_functions.py  |   4 +-
 python/tests/utils/test_crs_transformation.py      |   1 +
 .../scala/org/apache/sedona/sql/UDF/Catalog.scala  |   2 +-
 .../org/apache/sedona/sql/functionTestScala.scala  |  61 ++++----
 .../org/apache/sedona/sql/rasteralgebraTest.scala  |   4 +-
 .../sedona/viz/sql/standardVizOperatorTest.scala   |   4 +-
 32 files changed, 222 insertions(+), 286 deletions(-)

diff --git a/R/R/spatial_rdd.R b/R/R/spatial_rdd.R
index 7c26ddb82..bd0f2eed3 100644
--- a/R/R/spatial_rdd.R
+++ b/R/R/spatial_rdd.R
@@ -135,7 +135,7 @@ approx_count <- function(x) {
 #' Perform a CRS transformation.
 #'
 #' Transform data within a spatial RDD from one coordinate reference system to
-#' another.
+#' another. This uses the lon/lat order since v1.5.0. Before, it used lat/lon
 #'
 #' @param x The spatial RDD to be processed.
 #' @param src_epsg_crs_code Coordinate reference system to transform from
diff --git a/R/tests/testthat/test-crs-transform.R b/R/tests/testthat/test-crs-transform.R
index 1c65e12ab..c0c03c626 100644
--- a/R/tests/testthat/test-crs-transform.R
+++ b/R/tests/testthat/test-crs-transform.R
@@ -36,11 +36,11 @@ test_that("crs_transform() works as expected", {
       collect(),
     tibble::tribble(
       ~x, ~y,
-      -9833016.710450118, 3805934.914254189,
-      -9815699.961781807, 3810760.096874278,
-      -9839413.351030082, 3810273.8140140832,
-      -9820728.151861448, 3809444.5432437807,
-      -9832182.148227641, 3888758.1926142,
+      3805934.914254189,-9833016.710450118
+      3810760.096874278,-9815699.961781807
+      3810273.8140140832,-9839413.351030082
+      3809444.5432437807,-9820728.151861448
+      3888758.1926142,-9832182.148227641
     )
   )
 })
diff --git a/R/vignettes/articles/apache-sedona.Rmd b/R/vignettes/articles/apache-sedona.Rmd
index db03b7c09..350f153c3 100644
--- a/R/vignettes/articles/apache-sedona.Rmd
+++ b/R/vignettes/articles/apache-sedona.Rmd
@@ -190,13 +190,13 @@ The dbplyr interface transparently translates dbplyr workflows into SQL, and giv
 Results are then collected back into R with `collect`.
 
 ```{r}
-## ! ST_FlipCoordinates needs to be called before st_transform as by default, this function uses lat/lon order
+## ! ST_transform uses lon/lat order since v1.5.0. Before, it used lat/lon order.
 data_tbl %>% 
   mutate(
     ALAND = ALAND %>% as.numeric(),
     AWATER = AWATER %>% as.numeric(),
     area = ALAND + AWATER,
-    geometry_proj = st_transform(ST_FlipCoordinates(geometry), "epsg:4326", "epsg:5070", TRUE),
+    geometry_proj = st_transform(geometry, "epsg:4326", "epsg:5070", TRUE),
     area_geom = st_area(geometry_proj)
     ) %>% 
   select(STATEFP, COUNTYFP, area, area_geom) %>% 
@@ -209,7 +209,7 @@ Geometries need to be converted to a serializable (text or binary) format before
 ## Setting the CRS in R post-collect
 data_tbl %>% 
   mutate(
-    area = st_area(st_transform(ST_FlipCoordinates(geometry), "epsg:4326", "epsg:5070", TRUE)),
+    area = st_area(st_transform(geometry, "epsg:4326", "epsg:5070", TRUE)),
     geometry_wkb = geometry %>% st_asBinary()
     ) %>% 
   select(COUNTYFP, geometry_wkb) %>% 
@@ -223,7 +223,7 @@ data_tbl %>%
 ## Setting the CRS in Spark (and using EWKT to keep it)
 data_tbl %>% 
   mutate(
-    area = st_area(st_transform(ST_FlipCoordinates(geometry), "epsg:4326", "epsg:5070", TRUE)),
+    area = st_area(st_transform(geometry, "epsg:4326", "epsg:5070", TRUE)),
     geometry_ewkt = geometry %>% st_setsrid(4326) %>% st_asewkt()
     ) %>% 
   select(COUNTYFP, geometry_ewkt) %>% 
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 71101ffa9..287296f00 100644
--- a/common/src/main/java/org/apache/sedona/common/Functions.java
+++ b/common/src/main/java/org/apache/sedona/common/Functions.java
@@ -14,7 +14,6 @@
 package org.apache.sedona.common;
 
 import com.google.common.geometry.S2CellId;
-
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.sedona.common.geometryObjects.Circle;
 import org.apache.sedona.common.subDivide.GeometrySubDivider;
@@ -24,9 +23,22 @@ import org.apache.sedona.common.utils.GeometrySplitter;
 import org.apache.sedona.common.utils.S2Utils;
 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.algorithm.MinimumBoundingCircle;
 import org.locationtech.jts.algorithm.hull.ConcaveHull;
-import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+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.geom.impl.CoordinateArraySequence;
 import org.locationtech.jts.geom.util.GeometryFixer;
 import org.locationtech.jts.io.gml2.GMLWriter;
@@ -46,7 +58,10 @@ import org.opengis.referencing.operation.MathTransform;
 import org.opengis.referencing.operation.TransformException;
 import org.wololo.jts2geojson.GeoJSONWriter;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import static com.google.common.geometry.S2.DBL_EPSILON;
@@ -184,29 +199,70 @@ public class Functions {
         return min == Double.MAX_VALUE ? null : min;
     }
 
+    public static Geometry transform(Geometry geometry, String targetCRS)
+            throws FactoryException, TransformException {
+        return transform(geometry, null, targetCRS, true);
+    }
+
     public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS)
         throws FactoryException, TransformException {
-        return transform(geometry, sourceCRS, targetCRS, false);
+        return transform(geometry, sourceCRS, targetCRS, true);
     }
 
-
-    public static Geometry transform(Geometry geometry, String sourceCRS, String targetCRS, boolean lenient)
+    public static Geometry transform(Geometry geometry, String sourceCRScode, String targetCRScode, boolean lenient)
         throws FactoryException, TransformException {
-        CoordinateReferenceSystem sourceCRSCode = parseCRSString(sourceCRS);
-        CoordinateReferenceSystem targetCRScode = parseCRSString(targetCRS);
-        return GeomUtils.transform(geometry, sourceCRSCode, targetCRScode, lenient);
+        CoordinateReferenceSystem targetCRS = parseCRSString(targetCRScode);
+        return transformToGivenTarget(geometry, sourceCRScode, targetCRS, lenient);
     }
 
+    /**
+     * Transform a geometry from one CRS to another. If sourceCRS is not specified, it will be
+     * extracted from the geometry. If lenient is true, the transformation will be lenient.
+     * This function is used by the implicit CRS transformation in Sedona rasters.
+     * @param geometry
+     * @param sourceCRScode
+     * @param targetCRS
+     * @param lenient
+     * @return
+     * @throws FactoryException
+     * @throws TransformException
+     */
+    public static Geometry transformToGivenTarget(Geometry geometry, String sourceCRScode, CoordinateReferenceSystem targetCRS, boolean lenient)
+            throws FactoryException, TransformException
+    {
+        // If sourceCRS is not specified, try to get it from the geometry
+        if (sourceCRScode == null) {
+            int srid = geometry.getSRID();
+            if (srid != 0) {
+                sourceCRScode = "epsg:" + srid;
+            }
+            else {
+                // If SRID is not set, throw an exception
+                throw new IllegalArgumentException("Source CRS must be specified. No SRID found on geometry.");
+            }
+        }
+        CoordinateReferenceSystem sourceCRS = parseCRSString(sourceCRScode);
+        // 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;
+    }
 
     private static CoordinateReferenceSystem parseCRSString(String CRSString)
             throws FactoryException
     {
         try {
-            return CRS.decode(CRSString);
+            // Try to parse as a well-known CRS code
+            // Longitude first, then latitude
+            return CRS.decode(CRSString, true);
         }
         catch (NoSuchAuthorityCodeException e) {
             try {
-                return CRS.parseWKT(CRSString);
+                // Try to parse as a WKT CRS string, longitude first
+                Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
+                return ReferencingFactoryFinder.getCRSFactory(hints).createFromWKT(CRSString);
             }
             catch (FactoryException ex) {
                 throw new FactoryException("First failed to read as a well-known CRS code: \n" + e.getMessage() + "\nThen failed to read as a WKT CRS string: \n" + ex.getMessage());
diff --git a/common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java b/common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java
index 0d6f5405f..1bfb48db7 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterConstructors.java
@@ -23,6 +23,7 @@ import org.geotools.gce.geotiff.GeoTiffReader;
 import org.geotools.referencing.CRS;
 import org.geotools.referencing.crs.DefaultEngineeringCRS;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
+import org.geotools.util.factory.Hints;
 import org.opengis.referencing.FactoryException;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.datum.PixelInCell;
@@ -36,12 +37,12 @@ import java.io.IOException;
 public class RasterConstructors
 {
     public static GridCoverage2D fromArcInfoAsciiGrid(byte[] bytes) throws IOException {
-        ArcGridReader reader = new ArcGridReader(new ByteArrayImageInputStream(bytes));
+        ArcGridReader reader = new ArcGridReader(new ByteArrayImageInputStream(bytes), new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE));
         return reader.read(null);
     }
 
     public static GridCoverage2D fromGeoTiff(byte[] bytes) throws IOException {
-        GeoTiffReader geoTiffReader = new GeoTiffReader(new ByteArrayImageInputStream(bytes));
+        GeoTiffReader geoTiffReader = new GeoTiffReader(new ByteArrayImageInputStream(bytes), new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE));
         return geoTiffReader.read(null);
     }
 
@@ -134,7 +135,9 @@ public class RasterConstructors
         if (srid == 0) {
             crs = DefaultEngineeringCRS.GENERIC_2D;
         } else {
-            crs = CRS.decode("EPSG:" + srid);
+            // Create the CRS from the srid
+            // Longitude first, Latitude second
+            crs = CRS.decode("EPSG:" + srid, true);
         }
 
         // Create a new empty raster
diff --git a/common/src/main/java/org/apache/sedona/common/raster/RasterEditors.java b/common/src/main/java/org/apache/sedona/common/raster/RasterEditors.java
index 4093973b9..bdbc20d85 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterEditors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterEditors.java
@@ -36,9 +36,9 @@ public class RasterEditors
     {
         CoordinateReferenceSystem crs;
         if (srid == 0) {
-            crs = DefaultEngineeringCRS.CARTESIAN_2D;
+            crs = DefaultEngineeringCRS.GENERIC_2D;
         } else {
-            crs = CRS.decode("EPSG:" + srid);
+            crs = CRS.decode("EPSG:" + srid, true);
         }
 
         GridCoverageFactory gridCoverageFactory = CoverageFactoryFinder.getGridCoverageFactory(null);
diff --git a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
index cc24f1e79..e829b62a4 100644
--- a/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
+++ b/common/src/main/java/org/apache/sedona/common/utils/RasterUtils.java
@@ -27,8 +27,6 @@ import org.geotools.coverage.grid.GridCoverage2D;
 import org.geotools.coverage.grid.GridCoverageFactory;
 import org.geotools.coverage.grid.GridGeometry2D;
 import org.geotools.geometry.DirectPosition2D;
-import org.geotools.geometry.jts.JTS;
-import org.geotools.referencing.CRS;
 import org.geotools.referencing.crs.DefaultEngineeringCRS;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
 import org.locationtech.jts.geom.Geometry;
@@ -127,14 +125,16 @@ public class RasterUtils {
         return gridCoords;
     }
 
-    public static Geometry convertCRSIfNeeded(Geometry geometry, CoordinateReferenceSystem rasterCRS) {
+    public static Geometry convertCRSIfNeeded(Geometry geometry, CoordinateReferenceSystem targetCRS) {
         int geomSRID = geometry.getSRID();
-        if (rasterCRS != null && !(rasterCRS instanceof DefaultEngineeringCRS) && geomSRID > 0) {
+        // If the geometry has a SRID and it is not the same as the raster CRS, we need to transform the geometry
+        // to the raster CRS.
+        // Note that:
+        // In Sedona vector, we do not perform implicit CRS transform. Everything must be done explicitly via ST_Transform
+        // In Sedona raster, we do implicit CRS transform if the geometry has a SRID and the raster has a CRS
+        if (targetCRS != null && !(targetCRS instanceof DefaultEngineeringCRS) && geomSRID > 0) {
             try {
-                CoordinateReferenceSystem queryWindowCRS = CRS.decode("EPSG:" + geomSRID);
-                if (!CRS.equalsIgnoreMetadata(rasterCRS, queryWindowCRS)) {
-                    geometry = GeomUtils.transform(geometry, queryWindowCRS, rasterCRS, true);
-                }
+                geometry = Functions.transformToGivenTarget(geometry, null, targetCRS, true);
             } catch (FactoryException | TransformException e) {
                 throw new RuntimeException("Cannot transform CRS of query window", e);
             }
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 dad8f9771..ce7667462 100644
--- a/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/FunctionsTest.java
@@ -19,10 +19,14 @@ import org.apache.sedona.common.sphere.Haversine;
 import org.apache.sedona.common.sphere.Spheroid;
 import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.common.utils.S2Utils;
+import org.geotools.referencing.CRS;
+import org.geotools.referencing.operation.projection.ProjectionException;
 import org.junit.Test;
 import org.locationtech.jts.geom.*;
 import org.locationtech.jts.io.WKTReader;
 import org.locationtech.jts.io.WKTWriter;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
 
 import java.util.Arrays;
 import java.util.HashSet;
@@ -1622,4 +1626,44 @@ public class FunctionsTest {
         Double actual = Functions.hausdorffDistance(polygon, emptyLineString);
         assertEquals(expected, actual);
     }
+
+    @Test
+    public void transform()
+            throws FactoryException, TransformException
+    {
+        // The source and target CRS are the same
+        Point geomExpected = GEOMETRY_FACTORY.createPoint(new Coordinate(120, 60));
+        Geometry geomActual = Functions.transform(geomExpected, "EPSG:4326", "EPSG:4326");
+        assertEquals(geomExpected.getCoordinate().x, geomActual.getCoordinate().x, FP_TOLERANCE);
+        assertEquals(geomExpected.getCoordinate().y, geomActual.getCoordinate().y, FP_TOLERANCE);
+
+        // The source and target CRS are different
+        geomActual = Functions.transform(geomExpected, "EPSG:4326", "EPSG:3857");
+        assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE);
+        assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE);
+
+        // The source CRS is not specified and the geometry has no SRID
+        Exception e = assertThrows(IllegalArgumentException.class, () -> Functions.transform(geomExpected,"EPSG:3857"));
+        assertEquals("Source CRS must be specified. No SRID found on geometry.", e.getMessage());
+
+        // The source CRS is an invalid SRID
+        e = assertThrows(FactoryException.class, () -> Functions.transform(geomExpected, "abcde", "EPSG:3857"));
+        assertTrue(e.getMessage().contains("First failed to read as a well-known CRS code"));
+
+        // The source CRS is a WKT CRS string
+        String crsWkt = CRS.decode("EPSG:4326", true).toWKT();
+        geomActual = Functions.transform(geomExpected, crsWkt, "EPSG:3857");
+        assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE);
+        assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE);
+
+        // The source CRS is not specified but the geometry has a valid SRID
+        geomExpected.setSRID(4326);
+        geomActual = Functions.transform(geomExpected, "EPSG:3857");
+        assertEquals(1.3358338895192828E7, geomActual.getCoordinate().x, FP_TOLERANCE);
+        assertEquals(8399737.889818355, geomActual.getCoordinate().y, FP_TOLERANCE);
+
+        // The source and target CRS are different, and latitude is out of range
+        Point geometryWrong = GEOMETRY_FACTORY.createPoint(new Coordinate(60, 120));
+        assertThrows(ProjectionException.class, () -> Functions.transform(geometryWrong, "EPSG:4326", "EPSG:3857"));
+    }
 }
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java b/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
index 3a2a677b5..f07115e9b 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterAccessorsTest.java
@@ -174,7 +174,7 @@ public class RasterAccessorsTest extends RasterTestBase
         GridCoverage2D emptyRaster = RasterConstructors.makeEmptyRaster(1, 5, 5, -53, 51, 1, -1, 0, 0, srid);
         GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
         Geometry point = geometryFactory.createPoint(new Coordinate(longitude, latitude));
-        point = JTS.transform(point, CRS.findMathTransform(emptyRaster.getCoordinateReferenceSystem(), CRS.decode("EPSG:3857")));
+        point = JTS.transform(point, CRS.findMathTransform(emptyRaster.getCoordinateReferenceSystem(), CRS.decode("EPSG:3857", true)));
         point.setSRID(3857);
         Coordinate coords = RasterAccessors.getGridCoord(emptyRaster, point).getCoordinate();
         assertEquals(7, coords.getX(), 1e-9);
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterPredicatesTest.java b/common/src/test/java/org/apache/sedona/common/raster/RasterPredicatesTest.java
index 22ecde400..d77e3c423 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterPredicatesTest.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterPredicatesTest.java
@@ -181,14 +181,12 @@ public class RasterPredicatesTest extends RasterTestBase {
         GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 3857);
         Geometry geometry = geometryFactory.toGeometry(new Envelope(5, 10, 5, 10));
         GridCoverage2D raster = RasterConstructors.makeEmptyRaster(1, 20, 20, 2, 22, 1, -1, 0, 0, 3857);
-        geometry = JTS.transform(geometry, CRS.findMathTransform(raster.getCoordinateReferenceSystem(), CRS.decode("EPSG:4326")));
-        geometry.setSRID(4326);
         boolean result = RasterPredicates.rsContains(raster, geometry);
         Assert.assertTrue(result);
 
         //geometry protruding out of the raster envelope
         geometry = geometryFactory.toGeometry(new Envelope(2, 20, 2, 25));
-        geometry = JTS.transform(geometry, CRS.findMathTransform(raster.getCoordinateReferenceSystem(), CRS.decode("EPSG:4326")));
+        geometry = JTS.transform(geometry, CRS.findMathTransform(raster.getCoordinateReferenceSystem(), CRS.decode("EPSG:4326", true)));
         geometry.setSRID(4326);
         result = RasterPredicates.rsContains(raster, geometry);
         Assert.assertFalse(result);
@@ -274,7 +272,7 @@ public class RasterPredicatesTest extends RasterTestBase {
         GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
         Geometry geometry = geometryFactory.toGeometry(new Envelope(30, 60, 10, 50));
         GridCoverage2D raster = RasterConstructors.makeEmptyRaster(1, 20, 20, 32, 35, 1, -1, 0, 0, 4326);
-        geometry = JTS.transform(geometry, CRS.findMathTransform(raster.getCoordinateReferenceSystem(), CRS.decode("EPSG:3857")));
+        geometry = JTS.transform(geometry, CRS.findMathTransform(raster.getCoordinateReferenceSystem(), CRS.decode("EPSG:3857", true)));
         geometry.setSRID(3857);
         boolean result = RasterPredicates.rsWithin(raster, geometry);
         Assert.assertTrue(result);
diff --git a/common/src/test/java/org/apache/sedona/common/raster/RasterTestBase.java b/common/src/test/java/org/apache/sedona/common/raster/RasterTestBase.java
index a8a49dbde..fed723bca 100644
--- a/common/src/test/java/org/apache/sedona/common/raster/RasterTestBase.java
+++ b/common/src/test/java/org/apache/sedona/common/raster/RasterTestBase.java
@@ -81,7 +81,7 @@ public class RasterTestBase {
         CoordinateReferenceSystem crs = null;
         if (crsCode != null && !crsCode.isEmpty()) {
             try {
-                crs = CRS.decode(crsCode);
+                crs = CRS.decode(crsCode, true);
             } catch (FactoryException e) {
                 throw new RuntimeException(e);
             }
diff --git a/core/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java b/core/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java
index 68948ab09..8e986f4ce 100644
--- a/core/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java
+++ b/core/src/main/java/org/apache/sedona/core/spatialOperator/KNNQuery.java
@@ -19,13 +19,15 @@
 
 package org.apache.sedona.core.spatialOperator;
 
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.core.knnJudgement.GeometryDistanceComparator;
 import org.apache.sedona.core.knnJudgement.KnnJudgement;
 import org.apache.sedona.core.knnJudgement.KnnJudgementUsingIndex;
 import org.apache.sedona.core.spatialRDD.SpatialRDD;
-import org.apache.sedona.core.utils.CRSTransformation;
 import org.apache.spark.api.java.JavaRDD;
 import org.locationtech.jts.geom.Geometry;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.operation.TransformException;
 
 import java.io.Serializable;
 import java.util.List;
@@ -52,7 +54,12 @@ public class KNNQuery
     {
         U queryCenter = originalQueryPoint;
         if (spatialRDD.getCRStransformation()) {
-            queryCenter = CRSTransformation.Transform(spatialRDD.getSourceEpsgCode(), spatialRDD.getTargetEpgsgCode(), originalQueryPoint);
+            try {
+                queryCenter = (U) Functions.transform(originalQueryPoint, spatialRDD.getSourceEpsgCode(), spatialRDD.getTargetEpgsgCode());
+            }
+            catch (FactoryException | TransformException e) {
+                throw new RuntimeException(e);
+            }
         }
 
         if (useIndex) {
diff --git a/core/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java b/core/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java
index 746d24099..4e8d17e9d 100644
--- a/core/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java
+++ b/core/src/main/java/org/apache/sedona/core/spatialOperator/RangeQuery.java
@@ -19,10 +19,10 @@
 
 package org.apache.sedona.core.spatialOperator;
 
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.core.rangeJudgement.RangeFilter;
 import org.apache.sedona.core.rangeJudgement.RangeFilterUsingIndex;
 import org.apache.sedona.core.spatialRDD.SpatialRDD;
-import org.apache.sedona.core.utils.CRSTransformation;
 import org.apache.spark.api.java.JavaRDD;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Envelope;
@@ -55,7 +55,7 @@ public class RangeQuery
     {
         U queryGeometry = originalQueryGeometry;
         if (spatialRDD.getCRStransformation()) {
-            queryGeometry = CRSTransformation.Transform(spatialRDD.getSourceEpsgCode(), spatialRDD.getTargetEpgsgCode(), originalQueryGeometry);
+            queryGeometry = (U) Functions.transform(originalQueryGeometry, spatialRDD.getSourceEpsgCode(), spatialRDD.getTargetEpgsgCode());
         }
 
         if (useIndex == true) {
diff --git a/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java b/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
index 06e82d565..c3b4bfa25 100644
--- a/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
+++ b/core/src/main/java/org/apache/sedona/core/spatialRDD/SpatialRDD.java
@@ -21,6 +21,7 @@ package org.apache.sedona.core.spatialRDD;
 
 import org.apache.commons.lang.NullArgumentException;
 import org.apache.log4j.Logger;
+import org.apache.sedona.common.Functions;
 import org.apache.sedona.common.utils.GeomUtils;
 import org.apache.sedona.core.enums.GridType;
 import org.apache.sedona.core.enums.IndexType;
@@ -153,29 +154,11 @@ public class SpatialRDD<T extends Geometry>
      */
     public boolean CRSTransform(String sourceEpsgCRSCode, String targetEpsgCRSCode, boolean lenient)
     {
-        try {
-            CoordinateReferenceSystem sourceCRS = CRS.decode(sourceEpsgCRSCode);
-            CoordinateReferenceSystem targetCRS = CRS.decode(targetEpsgCRSCode);
-            final MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, lenient);
-            this.CRStransformation = true;
-            this.sourceEpsgCode = sourceEpsgCRSCode;
-            this.targetEpgsgCode = targetEpsgCRSCode;
-            this.rawSpatialRDD = this.rawSpatialRDD.map(new Function<T, T>()
-            {
-                @Override
-                public T call(T originalObject)
-                        throws Exception
-                {
-                    return (T) JTS.transform(originalObject, transform);
-                }
-            });
-            return true;
-        }
-        catch (FactoryException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return false;
-        }
+        this.CRStransformation = true;
+        this.sourceEpsgCode = sourceEpsgCRSCode;
+        this.targetEpgsgCode = targetEpsgCRSCode;
+        this.rawSpatialRDD = this.rawSpatialRDD.map((geom) -> (T) Functions.transform(geom, sourceEpsgCRSCode, targetEpgsgCode, lenient));
+        return true;
     }
 
     /**
diff --git a/core/src/main/java/org/apache/sedona/core/utils/CRSTransformation.java b/core/src/main/java/org/apache/sedona/core/utils/CRSTransformation.java
deleted file mode 100644
index c24417d13..000000000
--- a/core/src/main/java/org/apache/sedona/core/utils/CRSTransformation.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.core.utils;
-
-import org.geotools.geometry.jts.JTS;
-import org.geotools.referencing.CRS;
-import org.locationtech.jts.geom.Envelope;
-import org.locationtech.jts.geom.Geometry;
-import org.locationtech.jts.geom.Point;
-import org.locationtech.jts.geom.Polygon;
-import org.opengis.geometry.MismatchedDimensionException;
-import org.opengis.referencing.FactoryException;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.referencing.operation.MathTransform;
-import org.opengis.referencing.operation.TransformException;
-
-// TODO: Auto-generated Javadoc
-
-/**
- * The Class CRSTransformation.
- */
-public class CRSTransformation
-{
-
-    /**
-     * Transform.
-     *
-     * @param sourceEpsgCRSCode the source epsg CRS code
-     * @param targetEpsgCRSCode the target epsg CRS code
-     * @param sourceObject the source object
-     * @return the point
-     */
-    public static Point Transform(String sourceEpsgCRSCode, String targetEpsgCRSCode, Point sourceObject)
-    {
-        try {
-            CoordinateReferenceSystem sourceCRS = CRS.decode(sourceEpsgCRSCode);
-            CoordinateReferenceSystem targetCRS = CRS.decode(targetEpsgCRSCode);
-            final MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, false);
-            return (Point) JTS.transform(sourceObject, transform);
-        }
-        catch (FactoryException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (MismatchedDimensionException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (TransformException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Transform.
-     *
-     * @param sourceEpsgCRSCode the source epsg CRS code
-     * @param targetEpsgCRSCode the target epsg CRS code
-     * @param sourceObject the source object
-     * @return the polygon
-     */
-    public static Polygon Transform(String sourceEpsgCRSCode, String targetEpsgCRSCode, Polygon sourceObject)
-    {
-        try {
-            CoordinateReferenceSystem sourceCRS = CRS.decode(sourceEpsgCRSCode);
-            CoordinateReferenceSystem targetCRS = CRS.decode(targetEpsgCRSCode);
-            final MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, false);
-            return (Polygon) JTS.transform(sourceObject, transform);
-        }
-        catch (FactoryException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (MismatchedDimensionException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (TransformException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Transform.
-     *
-     * @param sourceEpsgCRSCode the source epsg CRS code
-     * @param targetEpsgCRSCode the target epsg CRS code
-     * @param sourceObject the source object
-     * @return the envelope
-     */
-    public static Envelope Transform(String sourceEpsgCRSCode, String targetEpsgCRSCode, Envelope sourceObject)
-    {
-        try {
-            CoordinateReferenceSystem sourceCRS = CRS.decode(sourceEpsgCRSCode);
-            CoordinateReferenceSystem targetCRS = CRS.decode(targetEpsgCRSCode);
-            final MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, false);
-            return JTS.transform(sourceObject, transform);
-        }
-        catch (FactoryException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (MismatchedDimensionException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (TransformException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    public static <T extends Geometry> T Transform(String sourceEpsgCRSCode, String targetEpsgCRSCode, T sourceObject)
-    {
-        try {
-            CoordinateReferenceSystem sourceCRS = CRS.decode(sourceEpsgCRSCode);
-            CoordinateReferenceSystem targetCRS = CRS.decode(targetEpsgCRSCode);
-            final MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, false);
-            return (T) JTS.transform(sourceObject, transform);
-        }
-        catch (FactoryException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (MismatchedDimensionException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-        catch (TransformException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-            return null;
-        }
-    }
-}
diff --git a/core/src/test/java/org/apache/sedona/core/utils/CRSTransformationTest.java b/core/src/test/java/org/apache/sedona/core/utils/CRSTransformationTest.java
index e051d8337..c501ebade 100644
--- a/core/src/test/java/org/apache/sedona/core/utils/CRSTransformationTest.java
+++ b/core/src/test/java/org/apache/sedona/core/utils/CRSTransformationTest.java
@@ -160,7 +160,7 @@ public class CRSTransformationTest
             gridType = GridType.getGridType(prop.getProperty("gridType"));
             indexType = IndexType.getIndexType(prop.getProperty("indexType"));
             numPartitions = Integer.parseInt(prop.getProperty("numPartitions"));
-            queryEnvelope = new Envelope(30.01, 40.01, -90.01, -80.01);
+            queryEnvelope = new Envelope(-90.01, -80.01, 30.01, 40.01);
             loopTimes = 1;
         }
         catch (IOException ex) {
@@ -203,6 +203,7 @@ public class CRSTransformationTest
             throws Exception
     {
         PointRDD spatialRDD = new PointRDD(sc, InputLocation, offset, splitter, true);
+        spatialRDD.flipCoordinates();
         spatialRDD.CRSTransform( "epsg:4326", "epsg:3005");
         long resultSize = RangeQuery.SpatialRangeQuery(spatialRDD, queryEnvelope, false, false).count();
         assert resultSize == 3127;
diff --git a/docs/api/flink/Function.md b/docs/api/flink/Function.md
index 63c895061..a491e221d 100644
--- a/docs/api/flink/Function.md
+++ b/docs/api/flink/Function.md
@@ -2223,17 +2223,21 @@ Transform the Spatial Reference System / Coordinate Reference System of A, from
 For SourceCRS and TargetCRS, WKT format is also available since v1.3.1.
 
 !!!note
-    By default, this function uses lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
+    By default, this function uses lon/lat order since `v1.5.0`. Before, it used lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
 
 !!!note
-    If ==ST_Transform== throws an Exception called "Bursa wolf parameters required", you need to disable the error notification in ST_Transform. You can append a boolean value at the end.
+    By default, ==ST_Transform== follows the `lenient` mode which tries to fix issues by itself. You can append a boolean value at the end to enable the `strict` mode. In `strict` mode, ==ST_Transform== will throw an error if it finds any issue.
 
-Format: `ST_Transform (A:geometry, SourceCRS:string, TargetCRS:string ,[Optional] DisableError)`
+Format: `ST_Transform (A:geometry, SourceCRS:string, TargetCRS:string ,[Optional] lenientMode:bool)`
 
 Since: `v1.2.0`
 
 Example:
 
+```sql
+SELECT ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((170 50,170 72,-130 72,-130 50,170 50))'),'EPSG:4326', 'EPSG:32649'))
+```
+
 ```sql
 SELECT ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((170 50,170 72,-130 72,-130 50,170 50))'),'EPSG:4326', 'EPSG:32649', false))
 ```
diff --git a/docs/api/sql/Function.md b/docs/api/sql/Function.md
index 267145326..3f8fcda0c 100644
--- a/docs/api/sql/Function.md
+++ b/docs/api/sql/Function.md
@@ -2315,17 +2315,21 @@ Transform the Spatial Reference System / Coordinate Reference System of A, from
 For SourceCRS and TargetCRS, WKT format is also available since v1.3.1.
 
 !!!note
-	By default, this function uses lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
+    By default, this function uses lon/lat order since `v1.5.0`. Before, it used lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
 
 !!!note
-	If ==ST_Transform== throws an Exception called "Bursa wolf parameters required", you need to disable the error notification in ST_Transform. You can append a boolean value at the end.
+    By default, ==ST_Transform== follows the `lenient` mode which tries to fix issues by itself. You can append a boolean value at the end to enable the `strict` mode. In `strict` mode, ==ST_Transform== will throw an error if it finds any issue.
 
-Format: `ST_Transform (A:geometry, SourceCRS:string, TargetCRS:string ,[Optional] DisableError)`
+Format: `ST_Transform (A:geometry, SourceCRS:string, TargetCRS:string ,[Optional] lenientMode:bool)`
 
 Since: `v1.0.0`
 
 Spark SQL example:
 
+```sql
+SELECT ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((170 50,170 72,-130 72,-130 50,170 50))'),'EPSG:4326', 'EPSG:32649'))
+```
+
 ```sql
 SELECT ST_AsText(ST_Transform(ST_GeomFromText('POLYGON((170 50,170 72,-130 72,-130 50,170 50))'),'EPSG:4326', 'EPSG:32649', false))
 ```
diff --git a/docs/api/sql/Raster-loader.md b/docs/api/sql/Raster-loader.md
index fe00d7fc6..129b432ff 100644
--- a/docs/api/sql/Raster-loader.md
+++ b/docs/api/sql/Raster-loader.md
@@ -7,6 +7,8 @@ Sedona provides two types of raster DataFrame loaders. They both use Sedona buil
 
 The raster loader of Sedona leverages Spark built-in binary data source and works with several RS RasterUDT constructors to produce RasterUDT type. Each raster is a row in the resulting DataFrame and stored in a `RasterUDT` format.
 
+By default, these functions uses lon/lat order since `v1.5.0`. Before, it used lat/lon order.
+
 ### Load raster to a binary DataFrame
 
 You can load any type of raster data using the code below. Then use the RS constructors below to create RasterUDT.
diff --git a/docs/tutorial/rdd.md b/docs/tutorial/rdd.md
index 584d8053d..f2fdecdec 100644
--- a/docs/tutorial/rdd.md
+++ b/docs/tutorial/rdd.md
@@ -203,6 +203,8 @@ For WKT/WKB/GeoJSON data, please use ==ST_GeomFromWKT / ST_GeomFromWKB / ST_Geom
 
 Sedona doesn't control the coordinate unit (degree-based or meter-based) of all geometries in an SpatialRDD. The unit of all related distances in Sedona is same as the unit of all geometries in an SpatialRDD.
 
+By default, this function uses lon/lat order since `v1.5.0`. Before, it used lat/lon order. You can use ==spatialRDD.flipCoordinates== to swap X and Y.
+
 To convert Coordinate Reference System of an SpatialRDD, use the following code:
 
 === "Scala"
diff --git a/docs/tutorial/sql.md b/docs/tutorial/sql.md
index 1f87c7283..7524c2fa8 100644
--- a/docs/tutorial/sql.md
+++ b/docs/tutorial/sql.md
@@ -437,6 +437,8 @@ For Postgis there is no need to add a query to convert geometry types since it's
 
 Sedona doesn't control the coordinate unit (degree-based or meter-based) of all geometries in a Geometry column. The unit of all related distances in SedonaSQL is same as the unit of all geometries in a Geometry column.
 
+By default, this function uses lon/lat order since `v1.5.0`. Before, it used lat/lon order. You can use ==ST_FlipCoordinates== to swap X and Y.
+
 To convert Coordinate Reference System of the Geometry column created before, use the following code:
 
 ```sql
diff --git a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
index 8e3caf329..d49449484 100644
--- a/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
+++ b/flink/src/test/java/org/apache/sedona/flink/FunctionTest.java
@@ -225,8 +225,8 @@ public class FunctionTest extends TestBase{
     public void testTransformWKT() throws FactoryException {
         Table pointTable = createPointTable_real(testDataSize);
 
-        CoordinateReferenceSystem CRS_SRC = CRS.decode("epsg:4326");
-        CoordinateReferenceSystem CRS_TGT = CRS.decode("epsg:3857");
+        CoordinateReferenceSystem CRS_SRC = CRS.decode("epsg:4326", true);
+        CoordinateReferenceSystem CRS_TGT = CRS.decode("epsg:3857", true);
 
         String SRC_WKT = CRS_SRC.toWKT();
         String TGT_WKT = CRS_TGT.toWKT();
diff --git a/python/sedona/core/SpatialRDD/spatial_rdd.py b/python/sedona/core/SpatialRDD/spatial_rdd.py
index 429d1000d..1522c0ee4 100644
--- a/python/sedona/core/SpatialRDD/spatial_rdd.py
+++ b/python/sedona/core/SpatialRDD/spatial_rdd.py
@@ -120,6 +120,9 @@ class SpatialRDD:
         """
         return self._srdd.CRSTransform(sourceEpsgCRSCode, targetEpsgCRSCode)
 
+    def flipCoordinates(self):
+        return self._srdd.flipCoordinates()
+
     def MinimumBoundingRectangle(self):
         raise NotImplementedError()
 
diff --git a/python/tests/properties/crs_transform.py b/python/tests/properties/crs_transform.py
index 548ceed58..f04c93970 100644
--- a/python/tests/properties/crs_transform.py
+++ b/python/tests/properties/crs_transform.py
@@ -31,6 +31,6 @@ num_partitions = 11
 distance = 0.01
 input_location_query_polygon = os.path.join(tests_resource, "crs-test-polygon.csv")
 loop_times = 1
-query_envelope = Envelope(30.01, 40.01, -90.01, -80.01)
+query_envelope = Envelope(-90.01, -80.01, 30.01, 40.01)
 query_point = Point(34.01, -84.01)
 top_k = 100
\ No newline at end of file
diff --git a/python/tests/sql/test_dataframe_api.py b/python/tests/sql/test_dataframe_api.py
index 103334406..ce51f7631 100644
--- a/python/tests/sql/test_dataframe_api.py
+++ b/python/tests/sql/test_dataframe_api.py
@@ -147,7 +147,7 @@ test_configurations = [
     (stf.ST_SubDivide, ("line", 5), "linestring_geom", "", ["LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)"]),
     (stf.ST_SubDivideExplode, ("line", 5), "linestring_geom", "collect_list(geom)", ["LINESTRING (0 0, 2.5 0)", "LINESTRING (2.5 0, 5 0)"]),
     (stf.ST_SymDifference, ("a", "b"), "overlapping_polys", "", "MULTIPOLYGON (((1 0, 0 0, 0 1, 1 1, 1 0)), ((2 0, 2 1, 3 1, 3 0, 2 0)))"),
-    (stf.ST_Transform, ("point", lambda: f.lit("EPSG:4326"), lambda: f.lit("EPSG:32649")), "point_geom", "ST_ReducePrecision(geom, 2)", "POINT (-33788209.77 0)"),
+    (stf.ST_Transform, ("point", lambda: f.lit("EPSG:4326"), lambda: f.lit("EPSG:32649")), "point_geom", "ST_ReducePrecision(geom, 2)", "POINT (-34870890.91 1919456.06)"),
     (stf.ST_Translate, ("geom", 1.0, 1.0,), "square_geom", "", "POLYGON ((2 1, 2 2, 3 2, 3 1, 2 1))"),
     (stf.ST_Union, ("a", "b"), "overlapping_polys", "", "POLYGON ((1 0, 0 0, 0 1, 1 1, 2 1, 3 1, 3 0, 2 0, 1 0))"),
     (stf.ST_X, ("b",), "two_points", "", 3.0),
diff --git a/python/tests/sql/test_function.py b/python/tests/sql/test_function.py
index 659541333..17d7fd338 100644
--- a/python/tests/sql/test_function.py
+++ b/python/tests/sql/test_function.py
@@ -184,7 +184,7 @@ class TestPredicateJoin(TestBase):
         polygon_df.createOrReplaceTempView("polygondf")
         polygon_df.show()
         function_df = self.spark.sql(
-            "select ST_Transform(ST_FlipCoordinates(polygondf.countyshape), 'epsg:4326','epsg:3857', false) from polygondf")
+            "select ST_Transform(polygondf.countyshape, 'epsg:4326','epsg:3857', false) from polygondf")
         function_df.show()
 
     def test_st_intersection_intersects_but_not_contains(self):
diff --git a/python/tests/streaming/spark/test_constructor_functions.py b/python/tests/streaming/spark/test_constructor_functions.py
index 26668ba8e..e197cd2cd 100644
--- a/python/tests/streaming/spark/test_constructor_functions.py
+++ b/python/tests/streaming/spark/test_constructor_functions.py
@@ -80,10 +80,10 @@ SEDONA_LISTED_SQL_FUNCTIONS = [
      .with_transform("ST_ASText")),
     (SuiteContainer.empty()
      .with_function_name("ST_Transform")
-     .with_arguments(["ST_GeomFromText('POINT(21.5 52.5)')", "'epsg:4326'", "'epsg:2180'"])
+     .with_arguments(["ST_GeomFromText('POINT(52.5 21.5)')", "'epsg:4326'", "'epsg:2180'"])
      .with_expected_result(-2501415.806893427)
      #.with_expected_result("POINT (-2501415.806893427 4119952.52325666)")
-     .with_transform("ST_X")),
+     .with_transform("ST_Y")),
      #.with_transform("ST_ASText")),
     (SuiteContainer.empty()
      .with_function_name("ST_Intersection")
diff --git a/python/tests/utils/test_crs_transformation.py b/python/tests/utils/test_crs_transformation.py
index d43cc0efe..46d63463b 100644
--- a/python/tests/utils/test_crs_transformation.py
+++ b/python/tests/utils/test_crs_transformation.py
@@ -35,6 +35,7 @@ class TestCrsTransformation(TestBase):
             splitter,
             True
         )
+        spatial_rdd.flipCoordinates()
         spatial_rdd.CRSTransform("epsg:4326", "epsg:3005")
 
         for i in range(loop_times):
diff --git a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
index d6df59a88..d662f9508 100644
--- a/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
+++ b/sql/common/src/main/scala/org/apache/sedona/sql/UDF/Catalog.scala
@@ -71,7 +71,7 @@ object Catalog {
     function[ST_Length](),
     function[ST_Area](),
     function[ST_Centroid](),
-    function[ST_Transform](false),
+    function[ST_Transform](true),
     function[ST_Intersection](),
     function[ST_Difference](),
     function[ST_SymDifference](),
diff --git a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
index 932164817..95bc493eb 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/functionTestScala.scala
@@ -21,14 +21,13 @@ package org.apache.sedona.sql
 
 import org.apache.commons.codec.binary.Hex
 import org.apache.sedona.sql.implicits._
-import org.apache.spark.sql.catalyst.expressions.{GenericRow, GenericRowWithSchema}
+import org.apache.spark.sql.catalyst.expressions.GenericRowWithSchema
 import org.apache.spark.sql.functions._
-import org.apache.spark.sql.sedona_sql.expressions.ST_Degrees
 import org.apache.spark.sql.{DataFrame, Row}
 import org.geotools.referencing.CRS
 import org.junit.Assert.assertEquals
 import org.locationtech.jts.algorithm.MinimumBoundingCircle
-import org.locationtech.jts.geom.{Geometry, Polygon}
+import org.locationtech.jts.geom.{Coordinate, Geometry, GeometryFactory, Polygon}
 import org.locationtech.jts.io.WKTWriter
 import org.locationtech.jts.linearref.LengthIndexedLine
 import org.locationtech.jts.operation.distance3d.Distance3DOp
@@ -189,52 +188,44 @@ class functionTestScala extends TestBaseScala with Matchers with GeometrySample
     }
 
     it("Passed ST_Transform") {
-      var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
-      polygonWktDf.createOrReplaceTempView("polygontable")
-      var polygonDf = sparkSession.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
-      polygonDf.createOrReplaceTempView("polygondf")
-      val polygon = "POLYGON ((110.54671 55.818002, 110.54671 55.143743, 110.940494 55.143743, 110.940494 55.818002, 110.54671 55.818002))"
-      // Floats don't have an exact decimal representation. String format varies across jvm:s. Do an approximate match.
-      val forceXYExpect = "POLYGON \\(\\(471596.69167460\\d* 6185916.95119\\d*, 471107.562364\\d* 6110880.97422\\d*, 496207.10915\\d* 6110788.80471\\d*, 496271.3193704\\d* 6185825.6056\\d*, 471596.6916746\\d* 6185916.95119\\d*\\)\\)"
-
-      sparkSession.createDataset(Seq(polygon))
-        .withColumn("geom", expr("ST_GeomFromWKT(value)"))
-        .createOrReplaceTempView("df")
-
-      val forceXYResult = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'EPSG:4326', 'EPSG:32649', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
-      forceXYResult should fullyMatch regex(forceXYExpect)
+      val point = "POINT (120 60)"
+      val transformedResult = sparkSession.sql(s"""select ST_Transform(ST_geomFromWKT('$point'),'EPSG:4326', 'EPSG:3857', false)""").rdd.map(row => row.getAs[Geometry](0)).collect()(0)
+      println(transformedResult)
+      assertEquals(1.3358338895192828E7, transformedResult.getCoordinate.x, FP_TOLERANCE)
+      assertEquals(8399737.889818355, transformedResult.getCoordinate.y, FP_TOLERANCE)
     }
 
     it("Passed ST_transform WKT version"){
-      var polygonWktDf = sparkSession.read.format("csv").option("delimiter", "\t").option("header", "false").load(mixedWktGeometryInputLocation)
-      polygonWktDf.createOrReplaceTempView("polygontable")
-      var polygonDf = sparkSession.sql("select ST_GeomFromWKT(polygontable._c0) as countyshape from polygontable")
-      polygonDf.createOrReplaceTempView("polygondf")
-      val polygon = "POLYGON ((110.54671 55.818002, 110.54671 55.143743, 110.940494 55.143743, 110.940494 55.818002, 110.54671 55.818002))"
-      val forceXYExpect = "POLYGON \\(\\(471596.6916746\\d* 6185916.95119\\d*, 471107.562364\\d* 6110880.97422\\d*, 496207.10915\\d* 6110788.80471\\d*, 496271.319370\\d* 6185825.6056\\d*, 471596.6916746\\d* 6185916.95119\\d*\\)\\)"
-
-      val EPSG_TGT_CRS = CRS.decode("EPSG:32649")
+      val polygon = "POLYGON ((120 60, 121 61, 122 62, 123 63, 124 64, 120 60))"
+      val geometryFactory = new GeometryFactory
+      val coords = new Array[Coordinate](6)
+      coords(0) = new Coordinate(1000961.4042164611, 6685590.893548286)
+      coords(1) = new Coordinate(1039394.2790044537, 6804110.988854166)
+      coords(2) = new Coordinate(1074157.4382441062, 6923060.447921266)
+      coords(3) = new Coordinate(1105199.4259604653, 7042351.1239674715)
+      coords(4) = new Coordinate(1132473.1932022288, 7161889.652860963)
+      coords(5) = coords(0)
+      val polygonExpected = geometryFactory.createPolygon(coords)
+      val EPSG_TGT_CRS = CRS.decode("EPSG:32649", true)
       val EPSG_TGT_WKT = EPSG_TGT_CRS.toWKT()
-      val EPSG_SRC_CRS = CRS.decode("EPSG:4326")
+      val EPSG_SRC_CRS = CRS.decode("EPSG:4326", true)
       val EPSG_SRC_WKT = EPSG_SRC_CRS.toWKT()
 
       sparkSession.createDataset(Seq(polygon))
         .withColumn("geom", expr("ST_GeomFromWKT(value)"))
         .createOrReplaceTempView("df")
+      val result_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_geomFromWKT('$polygon'),'EPSG:4326', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0)).collect()(0)
+      assertEquals(0, result_TGT_WKT.compareTo(polygonExpected, COORDINATE_SEQUENCE_COMPARATOR))
 
-      val forceXYResult_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'EPSG:4326', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
-      forceXYResult_TGT_WKT should fullyMatch regex(forceXYExpect)
-
-      val forceXYResult_SRC_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'$EPSG_SRC_WKT', 'EPSG:32649', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
-      forceXYResult_SRC_WKT should fullyMatch regex(forceXYExpect)
-
-      val forceXYResult_SRC_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_FlipCoordinates(ST_geomFromWKT('$polygon')),'$EPSG_SRC_WKT', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0).toString).collect()(0)
-      forceXYResult_SRC_TGT_WKT should fullyMatch regex(forceXYExpect)
+      val result_SRC_WKT = sparkSession.sql(s"""select ST_Transform(ST_geomFromWKT('$polygon'),'$EPSG_SRC_WKT', 'EPSG:32649', false)""").rdd.map(row => row.getAs[Geometry](0)).collect()(0)
+      assertEquals(0, result_SRC_WKT.compareTo(polygonExpected, COORDINATE_SEQUENCE_COMPARATOR))
 
+      val result_SRC_TGT_WKT = sparkSession.sql(s"""select ST_Transform(ST_geomFromWKT('$polygon'),'$EPSG_SRC_WKT', '$EPSG_TGT_WKT', false)""").rdd.map(row => row.getAs[Geometry](0)).collect()(0)
+      assertEquals(0, result_SRC_TGT_WKT.compareTo(polygonExpected, COORDINATE_SEQUENCE_COMPARATOR))
     }
 
     it("Passed Function exception check"){
-      val EPSG_TGT_CRS = CRS.decode("EPSG:32649")
+      val EPSG_TGT_CRS = CRS.decode("EPSG:32649", true)
       val EPSG_TGT_WKT = EPSG_TGT_CRS.toWKT()
       val epsgFactoryErrorString = EPSG_TGT_WKT.substring(0,EPSG_TGT_WKT.length() - 1)
       val epsgString = "EPSG:4326"
diff --git a/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala b/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index 976cce6cc..60d7d0c71 100644
--- a/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/sql/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -542,8 +542,8 @@ class rasteralgebraTest extends TestBaseScala with BeforeAndAfter with GivenWhen
       assert(!df.selectExpr("RS_Intersects(raster, ST_SetSRID(ST_Point(-13057457,4027023), 3857))").first().getBoolean(0))
 
       // query window and raster not in the same CRS
-      assert(df.selectExpr("RS_Intersects(raster, ST_SetSRID(ST_Point(33.81798,-117.47993), 4326))").first().getBoolean(0))
-      assert(!df.selectExpr("RS_Intersects(raster, ST_SetSRID(ST_Point(33.97896,-117.27868), 4326))").first().getBoolean(0))
+      assert(df.selectExpr("RS_Intersects(raster, ST_SetSRID(ST_Point(-117.47993, 33.81798), 4326))").first().getBoolean(0))
+      assert(!df.selectExpr("RS_Intersects(raster, ST_SetSRID(ST_Point(-117.27868, 33.97896), 4326))").first().getBoolean(0))
     }
 
     it("Passed RS_AddBandFromArray collect generated raster") {
diff --git a/viz/src/test/scala/org/apache/sedona/viz/sql/standardVizOperatorTest.scala b/viz/src/test/scala/org/apache/sedona/viz/sql/standardVizOperatorTest.scala
index a5346739e..9d020c89b 100644
--- a/viz/src/test/scala/org/apache/sedona/viz/sql/standardVizOperatorTest.scala
+++ b/viz/src/test/scala/org/apache/sedona/viz/sql/standardVizOperatorTest.scala
@@ -65,10 +65,10 @@ class standardVizOperatorTest extends TestBaseScala {
           |LATERAL VIEW
           | EXPLODE(
           |  ST_Pixelize(
-          |   ST_Transform(ST_FlipCoordinates(shape), 'epsg:4326','epsg:3857'),
+          |   ST_Transform(shape, 'epsg:4326','epsg:3857'),
           |   256,
           |   256,
-          |   (SELECT ST_Transform(ST_FlipCoordinates(bound), 'epsg:4326','epsg:3857') FROM boundtable)
+          |   (SELECT ST_Transform(bound, 'epsg:4326','epsg:3857') FROM boundtable)
           |  )) AS pixel
         """.stripMargin).createOrReplaceTempView("pixels")
       spark.sql(