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

jiayu pushed a commit to branch rs-set-crs-2674
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/rs-set-crs-2674 by this push:
     new bb42c4df69 fix: address PR review comments
bb42c4df69 is described below

commit bb42c4df69a842f20e3e1c219e49ab9c1d0dcd7a
Author: Jia Yu <[email protected]>
AuthorDate: Sun Mar 1 02:11:08 2026 -0800

    fix: address PR review comments
    
    - Use longitude-first axis order for WKT parsing 
(FORCE_LONGITUDE_FIRST_AXIS_ORDER)
    - Remove tryResolveToEpsg() - RS_SRID returns 0 for custom CRS, use RS_CRS 
instead
    - Add null/empty guard for format parameter in crs()
    - Use ConcurrentHashMap for thread-safe alias cache writes
    - Guard DefaultMathTransformFactory downcast with instanceof
    - Catch specific exceptions in proj4sedona parsing, attach as suppressed
    - Remove 'lossless' claim from PROJJSON docs
    - Update RS_SRID docs: 0 can mean custom (non-EPSG) CRS
    - Fix Javadoc to include WKT2 in format list
    - Update Spark test to expect SRID=0 for WKT1 without AUTHORITY
---
 .../sedona/common/raster/RasterAccessors.java      |  3 ++
 .../apache/sedona/common/raster/RasterEditors.java | 50 ++++++++++++++++------
 .../common/raster/CrsRoundTripComplianceTest.java  |  2 +-
 docs/api/sql/Raster-Operators/RS_CRS.md            |  2 +-
 docs/api/sql/Raster-Operators/RS_SRID.md           |  2 +-
 .../org/apache/sedona/sql/rasteralgebraTest.scala  |  8 +++-
 6 files changed, 48 insertions(+), 19 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java 
b/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
index e8306a02bc..187365bbd8 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/RasterAccessors.java
@@ -381,6 +381,9 @@ public class RasterAccessors {
    * @return The CRS definition string in the requested format, or null if no 
CRS is set.
    */
   public static String crs(GridCoverage2D raster, String format) {
+    if (format == null || format.trim().isEmpty()) {
+      format = "projjson";
+    }
     CoordinateReferenceSystem crsDef = raster.getCoordinateReferenceSystem();
     if (crsDef instanceof DefaultEngineeringCRS) {
       if (((DefaultEngineeringCRS) crsDef).isWildcard()) {
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 23030e5f58..ae88b28e88 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
@@ -30,6 +30,7 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.media.jai.Interpolation;
@@ -42,10 +43,12 @@ import org.geotools.api.coverage.grid.GridCoverage;
 import org.geotools.api.coverage.grid.GridGeometry;
 import org.geotools.api.metadata.spatial.PixelOrientation;
 import org.geotools.api.referencing.FactoryException;
+import org.geotools.api.referencing.crs.CRSFactory;
 import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
 import org.geotools.api.referencing.datum.PixelInCell;
 import org.geotools.api.referencing.operation.MathTransform;
 import org.geotools.api.referencing.operation.MathTransform2D;
+import org.geotools.api.referencing.operation.MathTransformFactory;
 import org.geotools.api.referencing.operation.OperationMethod;
 import org.geotools.api.referencing.operation.Projection;
 import org.geotools.api.referencing.operation.TransformException;
@@ -62,6 +65,7 @@ import org.geotools.referencing.ReferencingFactoryFinder;
 import org.geotools.referencing.crs.DefaultEngineeringCRS;
 import org.geotools.referencing.operation.DefaultMathTransformFactory;
 import org.geotools.referencing.operation.transform.AffineTransform2D;
+import org.geotools.util.factory.Hints;
 import org.locationtech.jts.index.strtree.STRtree;
 
 public class RasterEditors {
@@ -154,14 +158,17 @@ public class RasterEditors {
       // Not an authority code, continue
     }
 
-    // Step 2: Try GeoTools CRS.parseWKT (handles WKT1)
+    // Step 2: Try GeoTools WKT parsing with longitude-first axis order 
(handles WKT1)
     try {
-      return CRS.parseWKT(crsString);
+      Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, 
Boolean.TRUE);
+      CRSFactory crsFactory = ReferencingFactoryFinder.getCRSFactory(hints);
+      return crsFactory.createFromWKT(crsString);
     } catch (FactoryException e) {
       // Not WKT1, continue
     }
 
     // Step 3: Use proj4sedona (handles WKT2, PROJ, PROJJSON)
+    Exception lastError = null;
     try {
       Proj proj = new Proj(crsString);
 
@@ -183,9 +190,12 @@ public class RasterEditors {
       // 3. Strip unexpected parameters iteratively
       String wkt1 = proj.toWkt1();
       if (wkt1 != null && !wkt1.isEmpty()) {
+        Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, 
Boolean.TRUE);
+        CRSFactory crsFactory = ReferencingFactoryFinder.getCRSFactory(hints);
+
         // Strategy 1: Try raw WKT1 directly
         try {
-          return CRS.parseWKT(wkt1);
+          return crsFactory.createFromWKT(wkt1);
         } catch (FactoryException ex) {
           // Raw WKT1 failed, continue with normalization
         }
@@ -199,7 +209,7 @@ public class RasterEditors {
         String currentWkt = normalizedWkt;
         for (int attempt = 0; attempt < 5; attempt++) {
           try {
-            return CRS.parseWKT(currentWkt);
+            return crsFactory.createFromWKT(currentWkt);
           } catch (FactoryException ex) {
             String msg = ex.getMessage();
             if (msg != null) {
@@ -209,18 +219,24 @@ public class RasterEditors {
                 continue;
               }
             }
+            lastError = ex;
             break; // Different kind of error, give up
           }
         }
       }
-    } catch (Exception e) {
-      // proj4sedona could not parse it either
+    } catch (RuntimeException e) {
+      lastError = e;
     }
 
-    throw new IllegalArgumentException(
-        "Cannot parse CRS string. Supported formats: EPSG code (e.g. 
'EPSG:4326'), "
-            + "WKT1, WKT2, PROJ string, PROJJSON. Input: "
-            + crsString);
+    IllegalArgumentException error =
+        new IllegalArgumentException(
+            "Cannot parse CRS string. Supported formats: EPSG code (e.g. 
'EPSG:4326'), "
+                + "WKT1, WKT2, PROJ string, PROJJSON. Input: "
+                + crsString);
+    if (lastError != null) {
+      error.addSuppressed(lastError);
+    }
+    throw error;
   }
 
   // Fallback map for proj4sedona projection names that have no equivalent in 
GeoTools'
@@ -243,7 +259,8 @@ public class RasterEditors {
   }
 
   // Lazy-initialized caches built once from GeoTools' registered 
OperationMethod objects.
-  // aliasCache: exact alias string -> canonical OGC name
+  // aliasCache: exact alias string -> canonical OGC name (ConcurrentHashMap 
for thread-safe
+  // writes from Tier 2 normalized matching)
   // normalizedCache: normalized form (lowercase, no spaces/underscores) -> 
set of canonical names
   private static volatile Map<String, String> aliasCache;
   private static volatile Map<String, Set<String>> normalizedCache;
@@ -341,11 +358,16 @@ public class RasterEditors {
       if (aliasCache != null) {
         return;
       }
-      DefaultMathTransformFactory factory =
-          (DefaultMathTransformFactory) 
ReferencingFactoryFinder.getMathTransformFactory(null);
+      MathTransformFactory mtf = 
ReferencingFactoryFinder.getMathTransformFactory(null);
+      DefaultMathTransformFactory factory;
+      if (mtf instanceof DefaultMathTransformFactory) {
+        factory = (DefaultMathTransformFactory) mtf;
+      } else {
+        factory = new DefaultMathTransformFactory();
+      }
       Set<OperationMethod> methods = 
factory.getAvailableMethods(Projection.class);
 
-      Map<String, String> aliases = new HashMap<>();
+      Map<String, String> aliases = new ConcurrentHashMap<>();
       Map<String, Set<String>> normalized = new HashMap<>();
 
       for (OperationMethod method : methods) {
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/CrsRoundTripComplianceTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/CrsRoundTripComplianceTest.java
index 6a64d6de35..5833d86d7d 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/CrsRoundTripComplianceTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/CrsRoundTripComplianceTest.java
@@ -29,7 +29,7 @@ import org.junit.Test;
 /**
  * Round-trip compliance tests for RS_SetCRS and RS_CRS across representative 
EPSG codes.
  *
- * <p>For each EPSG code and each format (PROJ, PROJJSON, WKT1), this test:
+ * <p>For each EPSG code and each format (PROJ, PROJJSON, WKT1, WKT2), this 
test:
  *
  * <ol>
  *   <li>Creates a raster with that CRS via RS_SetCRS("EPSG:xxxx")
diff --git a/docs/api/sql/Raster-Operators/RS_CRS.md 
b/docs/api/sql/Raster-Operators/RS_CRS.md
index 04d98dc0eb..c8586c02a7 100644
--- a/docs/api/sql/Raster-Operators/RS_CRS.md
+++ b/docs/api/sql/Raster-Operators/RS_CRS.md
@@ -37,7 +37,7 @@ Since: `v1.9.0`
 
 | Format | Description |
 | :--- | :--- |
-| `'projjson'` | PROJJSON format (default). Modern, lossless, machine-readable 
JSON representation. |
+| `'projjson'` | PROJJSON format (default). Modern, machine-readable JSON 
representation. |
 | `'wkt2'` | Well-Known Text 2 (ISO 19162). Modern standard CRS 
representation. |
 | `'wkt1'` | Well-Known Text 1 (OGC 01-009). Legacy format, widely supported. |
 | `'proj'` | PROJ string format. Compact, human-readable representation. |
diff --git a/docs/api/sql/Raster-Operators/RS_SRID.md 
b/docs/api/sql/Raster-Operators/RS_SRID.md
index 7820828e2e..8d79b94b0c 100644
--- a/docs/api/sql/Raster-Operators/RS_SRID.md
+++ b/docs/api/sql/Raster-Operators/RS_SRID.md
@@ -19,7 +19,7 @@
 
 # RS_SRID
 
-Introduction: Returns the spatial reference system identifier (SRID) of the 
raster geometry.
+Introduction: Returns the spatial reference system identifier (SRID) of the 
raster geometry. Returns 0 if the raster has no CRS defined or if the CRS is a 
custom (non-EPSG) coordinate reference system. To retrieve the full CRS 
definition for custom CRS, use [RS_CRS](RS_CRS.md).
 
 Format: `RS_SRID (raster: Raster)`
 
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
index db975596b1..a6736429da 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasteralgebraTest.scala
@@ -500,9 +500,13 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
       val wkt1 =
         "GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 
84\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]"
       val df = sparkSession.read.format("binaryFile").load(resourceFolder + 
"raster/test1.tiff")
-      val result =
+      // WKT1 without AUTHORITY clause: RS_SRID returns 0, but RS_CRS still 
works
+      val srid =
         df.selectExpr(s"RS_SRID(RS_SetCRS(RS_FromGeoTiff(content), 
'${wkt1}'))").first().getInt(0)
-      assert(result == 4326)
+      assert(srid == 0)
+      val crs =
+        df.selectExpr(s"RS_CRS(RS_SetCRS(RS_FromGeoTiff(content), '${wkt1}'), 
'wkt1')").first().getString(0)
+      assert(crs != null && crs.contains("WGS"))
     }
 
     it("Passed RS_CRS should handle null values") {

Reply via email to