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 e936ecdea4 [SEDONA-740] Fix rasterization rounding issue (#2065)
e936ecdea4 is described below

commit e936ecdea4b920b6409bd6a45a5f1d1c9314f79b
Author: Pranav Toggi <[email protected]>
AuthorDate: Sun Jul 6 22:35:01 2025 -0700

    [SEDONA-740] Fix rasterization rounding issue (#2065)
    
    * Fix bug; add tests
    
    * address comments
---
 .../apache/sedona/common/raster/Rasterization.java |  17 ++---
 .../sedona/common/raster/FunctionEditorsTest.java  |  10 +--
 .../common/raster/RasterBandAccessorsTest.java     |  76 +++++++++++++++++----
 .../common/raster/RasterConstructorsTest.java      |   2 +-
 spark/common/src/test/resources/raster/test7.tiff  | Bin 0 -> 65920 bytes
 spark/common/src/test/resources/raster/test8.tiff  | Bin 0 -> 65920 bytes
 .../scala/org/apache/sedona/sql/rasterIOTest.scala |   2 +-
 .../org/apache/sedona/sql/rasteralgebraTest.scala  |  16 ++---
 8 files changed, 86 insertions(+), 37 deletions(-)

diff --git 
a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java 
b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
index b62a615b1c..730272c6e0 100644
--- a/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
+++ b/common/src/main/java/org/apache/sedona/common/raster/Rasterization.java
@@ -549,14 +549,14 @@ public class Rasterization {
         // Calculate scan line limits to iterate between for each segment
         // Using BigDecimal to avoid floating point errors
         double yStart =
-            Math.ceil(
+            Math.round(
                 (BigDecimal.valueOf(params.upperLeftY)
                         .subtract(BigDecimal.valueOf(worldP1.y))
                         .divide(BigDecimal.valueOf(params.scaleY), 
RoundingMode.CEILING))
                     .doubleValue());
 
         double yEnd =
-            Math.floor(
+            Math.round(
                 (BigDecimal.valueOf(params.upperLeftY)
                         .subtract(BigDecimal.valueOf(worldP2.y))
                         .divide(BigDecimal.valueOf(params.scaleY), 
RoundingMode.FLOOR))
@@ -581,17 +581,14 @@ public class Rasterization {
           }
         } else {
           double slope = (worldP2.y - worldP1.y) / (worldP2.x - worldP1.x);
-          //        System.out.println("slope: " + slope);
-
+          double xMin = (geomExtent.getMinX() - params.upperLeftX) / 
params.scaleX;
+          double xMax = (geomExtent.getMaxX() - params.upperLeftX) / 
params.scaleX;
           for (double y = yStart; y >= yEnd; y--) {
             double xIntercept = p1X + ((p1Y - y) / slope);
-            if ((xIntercept < 0) || (xIntercept >= 
params.writableRaster.getWidth())) {
-              continue; // skip xIntercepts outside geomExtent
-            }
-            if (!scanlineIntersections.containsKey(y)) {
-              scanlineIntersections.put(y, new TreeSet<>());
+            if ((xIntercept < xMin) || (xIntercept >= xMax)) {
+              continue; // Skip xIntercepts outside geomExtent
             }
-            scanlineIntersections.get(y).add(xIntercept);
+            scanlineIntersections.computeIfAbsent(y, k -> new 
TreeSet<>()).add(xIntercept);
           }
         }
       }
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java 
b/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
index 679c4c2920..37c6d4ed73 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/FunctionEditorsTest.java
@@ -85,11 +85,11 @@ public class FunctionEditorsTest extends RasterTestBase {
     expected =
         new double[] {
           0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0,
-          0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 
0.0, 0.0, 10.0, 10.0,
-          10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 
10.0, 10.0, 0.0, 0.0,
-          0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 
10.0, 10.0, 10.0, 10.0,
-          10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 
0.0, 0.0, 0.0, 0.0,
-          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0, 0.0
+          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 10.0, 10.0, 10.0,
+          10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 10.0, 
10.0, 0.0, 0.0, 0.0,
+          0.0, 10.0, 10.0, 0.0, 0.0, 10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 
10.0, 10.0, 10.0, 10.0,
+          10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 0.0, 
0.0, 0.0, 0.0, 0.0,
+          0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0, 0.0, 0.0, 0.0
         };
     assertArrayEquals(actual, expected, 0.0);
   }
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
index 54613223dd..d953d91ece 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/RasterBandAccessorsTest.java
@@ -100,11 +100,11 @@ public class RasterBandAccessorsTest extends 
RasterTestBase {
             0);
 
     Double actualZonalStats = RasterBandAccessors.getZonalStats(raster, 
extent, "mode");
-    assertEquals(10.0, actualZonalStats, FP_TOLERANCE);
+    assertNull(actualZonalStats);
 
     String actualZonalStatsAll =
         Arrays.toString(RasterBandAccessors.getZonalStatsAll(raster, extent));
-    String expectedZonalStatsAll = "[1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 
10.0, 10.0]";
+    String expectedZonalStatsAll = "[0.0, null, null, null, null, null, null, 
null, null]";
     assertEquals(expectedZonalStatsAll, actualZonalStatsAll);
   }
 
@@ -132,19 +132,19 @@ public class RasterBandAccessorsTest extends 
RasterTestBase {
     Geometry geom = Constructors.geomFromWKT(polygon, 
RasterAccessors.srid(raster));
 
     double actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "sum", 
false, false);
-    double expected = 1.0896994E7;
+    double expected = 1.0795427E7;
     assertEquals(expected, actual, 0d);
 
     actual = RasterBandAccessors.getZonalStats(raster, geom, 2, "mean", false, 
false);
-    expected = 220.76055239764008;
+    expected = 220.7711988936036;
     assertEquals(expected, actual, FP_TOLERANCE);
 
     actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "count");
-    expected = 185953.0;
+    expected = 185104.0;
     assertEquals(expected, actual, 0.1d);
 
     actual = RasterBandAccessors.getZonalStats(raster, geom, 3, "variance", 
false, false);
-    expected = 13560.056183580346;
+    expected = 13553.040611690152;
     assertEquals(expected, actual, FP_TOLERANCE);
 
     actual = RasterBandAccessors.getZonalStats(raster, geom, "max");
@@ -156,7 +156,7 @@ public class RasterBandAccessorsTest extends RasterTestBase 
{
     assertEquals(expected, actual, 1E-1);
 
     actual = RasterBandAccessors.getZonalStats(raster, geom, 1, "sd", false, 
false);
-    expected = 92.53811912983977;
+    expected = 92.3801832204387;
     assertEquals(expected, actual, FP_TOLERANCE);
 
     geom =
@@ -220,6 +220,58 @@ public class RasterBandAccessorsTest extends 
RasterTestBase {
     assertEquals(expected, actual, FP_TOLERANCE);
   }
 
+  @Test
+  public void testRasterization1() throws FactoryException, ParseException, 
IOException {
+    GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + 
"raster/test7.tiff");
+    String wkt =
+        "POLYGON ((-0.897979307443705 52.22640443968169, -0.897982946766236 
52.22638696176784, -0.89799206869722 52.22637025568977, -0.898006322680796 
52.22635496345145, -0.89802516094114 52.22634167272326, -0.898047859533683 
52.2263308942584, -0.898073546166002 52.22632304226535, -0.898101233719275 
52.22631841849023, -0.898129858182076 52.22631720062118, -0.89815831953887 
52.22631943546002, -0.898185524042003 52.2263250371237, -0.898210426242843 
52.22633379034471, -0.898232069167008 52. [...]
+    Geometry geom = Constructors.geomFromWKT(wkt, 4326);
+
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, 
false, false))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
+
+    double[] expected = new double[] {14.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 
0.0};
+    assertArrayEquals(expected, actual, FP_TOLERANCE);
+  }
+
+  @Test
+  public void testRasterization2() throws FactoryException, ParseException, 
IOException {
+    GridCoverage2D raster =
+        rasterFromGeoTiff(resourceFolder + 
"raster_geotiff_color/FAA_UTM18N_NAD83.tif");
+
+    Geometry geom =
+        Constructors.geomFromWKT(
+            "POLYGON ((223494.02395197155 4217428.006125106, 223479.8395471539 
4217398.373656692, 223505.34101226972 4217373.172295491, 223535.14466627932 
4217360.826244754, 223565.4369792635 4217365.465341343, 223601.56141698774 
4217373.684776339, 223638.23068926093 4217400.554304458, 223640.98980568675 
4217424.9074383825, 223639.6956443374 4217443.73089679, 223617.52307020774 
4217455.706515524, 223608.8979089979 4217478.55688384, 223580.76530214178 
4217483.17859071, 223544.9615263639 4 [...]
+            26918);
+
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, 
false, true))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
+
+    double[] expected = new double[] {7.0, 1785.0, 255.0, 255.0, 255.0, 0.0, 
0.0, 255.0, 255.0};
+    assertArrayEquals(expected, actual, FP_TOLERANCE);
+  }
+
+  @Test
+  public void testRasterization3() throws FactoryException, ParseException, 
IOException {
+    GridCoverage2D raster = rasterFromGeoTiff(resourceFolder + 
"raster/test8.tiff");
+
+    Geometry geom =
+        Constructors.geomFromWKT(
+            "POLYGON((0.719049156542861 52.250273602713776,0.71904724344863 
52.2502560225192,0.71903977906225 52.250239009194175,0.719027050241651 
52.250223216550275,0.719009546152358 52.25020925148843,0.718987939468514 
52.25019765067622,0.718963060522109 52.25018885992432,0.718935865393908 
52.25018321705467,0.718907399172285 52.25018093891829,0.718878755791851 
52.250182113062095,0.71885103599512 52.250186694364565,0.718825305032585 
52.250194506769795,0.718802551726619 52.25020525005296, [...]
+            4326);
+    double[] actual =
+        Arrays.stream(RasterBandAccessors.getZonalStatsAll(raster, geom, 1, 
true, true))
+            .mapToDouble(Double::doubleValue)
+            .toArray();
+
+    double[] expected = new double[] {2.0, 10.0, 5.0, 5.0, 5.0, 0.0, 0.0, 5.0, 
5.0};
+    assertArrayEquals(expected, actual, FP_TOLERANCE);
+  }
+
   @Test
   public void testZonalStatsAll() throws IOException, FactoryException, 
ParseException {
     GridCoverage2D raster =
@@ -234,13 +286,13 @@ public class RasterBandAccessorsTest extends 
RasterTestBase {
             .toArray();
     double[] expected =
         new double[] {
-          185953.0,
-          1.0896994E7,
-          58.600796975566816,
+          185104.0,
+          1.0795427E7,
+          58.32087367104147,
           0.0,
           0.0,
-          92.53811912983977,
-          8563.303492088418,
+          92.3801832204387,
+          8534.098251841822,
           0.0,
           255.0
         };
diff --git 
a/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
 
b/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
index bff8261d48..f42d4acc5e 100644
--- 
a/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
+++ 
b/common/src/test/java/org/apache/sedona/common/raster/RasterConstructorsTest.java
@@ -129,7 +129,7 @@ public class RasterConstructorsTest extends RasterTestBase {
 
     expected =
         new double[] {
-          1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 
1.0, 1.0, 0.0, 0.0, 1.0,
+          0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 
1.0, 1.0, 0.0, 0.0, 1.0,
           1.0, 0.0
         };
     assertArrayEquals(expected, actual, 0.1d);
diff --git a/spark/common/src/test/resources/raster/test7.tiff 
b/spark/common/src/test/resources/raster/test7.tiff
new file mode 100644
index 0000000000..cf510b5d77
Binary files /dev/null and b/spark/common/src/test/resources/raster/test7.tiff 
differ
diff --git a/spark/common/src/test/resources/raster/test8.tiff 
b/spark/common/src/test/resources/raster/test8.tiff
new file mode 100644
index 0000000000..64be625abf
Binary files /dev/null and b/spark/common/src/test/resources/raster/test8.tiff 
differ
diff --git 
a/spark/common/src/test/scala/org/apache/sedona/sql/rasterIOTest.scala 
b/spark/common/src/test/scala/org/apache/sedona/sql/rasterIOTest.scala
index a62b975454..8b36f32272 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/rasterIOTest.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/rasterIOTest.scala
@@ -86,7 +86,7 @@ class rasterIOTest extends TestBaseScala with BeforeAndAfter 
with GivenWhenThen
       rasterDf.write.format("raster").mode(SaveMode.Overwrite).save(tempDir + 
"/raster-written")
       df = sparkSession.read.format("binaryFile").load(tempDir + 
"/raster-written/*")
       rasterDf = df.selectExpr("RS_FromGeoTiff(content)")
-      assert(rasterCount == 6)
+      assert(rasterCount == 8)
       assert(rasterDf.count() == 0)
     }
 
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 be0c69a722..1b6490c234 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
@@ -1634,11 +1634,11 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
           |""".stripMargin)
 
       var actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 
'mode')").first().get(0)
-      assertEquals(10.0, actual)
+      assertNull(actual)
 
       val statsDf = df.selectExpr("RS_ZonalStatsAll(raster, geom) as stats")
       actual = statsDf.first().getStruct(0).toSeq.slice(0, 9)
-      val expected = Seq(1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 10.0)
+      val expected = Seq(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
       assertTrue(expected.equals(actual))
     }
 
@@ -1651,20 +1651,20 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         "ST_GeomFromWKT('POLYGON ((236722 4204770, 243900 4204770, 243900 
4197590, 221170 4197590, 236722 4204770))', 26918) as geom")
       var actual =
         df.selectExpr("RS_ZonalStats(raster, geom, 1, 'sum', false, 
true)").first().get(0)
-      assertEquals(1.0896994e7, actual)
+      assertEquals(1.0795427e7, actual)
 
       actual =
         df.selectExpr("RS_ZonalStats(raster, geom, 1, 'count', false, 
false)").first().get(0)
-      assertEquals(185953.0, actual)
+      assertEquals(185104.0, actual)
 
       actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 'mean', true, 
false)").first().get(0)
       assertEquals(58.650240700685295, actual)
 
       actual = df.selectExpr("RS_ZonalStats(raster, geom, 1, 
'variance')").first().get(0)
-      assertEquals(8563.303492088418, actual)
+      assertEquals(8534.098251841822, actual)
 
       actual = df.selectExpr("RS_ZonalStats(raster, geom, 
'sd')").first().get(0)
-      assertEquals(92.53811912983977, actual)
+      assertEquals(92.3801832204387, actual)
 
       // Test with a polygon in EPSG:4326
       actual = df
@@ -1721,8 +1721,8 @@ class rasteralgebraTest extends TestBaseScala with 
BeforeAndAfter with GivenWhen
         .getStruct(0)
         .toSeq
         .slice(0, 9)
-      val expected = Seq(185953.0, 1.0896994e7, 58.600796975566816, 0.0, 0.0, 
92.53811912983977,
-        8563.303492088418, 0.0, 255.0)
+      val expected = Seq(185104.0, 1.0795427e7, 58.32087367104147, 0.0, 0.0, 
92.3801832204387,
+        8534.098251841822, 0.0, 255.0)
       assertTrue(expected.equals(actual))
 
       // Test with a polygon that does not intersect the raster in lenient mode

Reply via email to