james-willis commented on code in PR #1027:
URL: https://github.com/apache/sedona-db/pull/1027#discussion_r3606232056


##########
python/sedonadb/tests/functions/test_raster_functions.py:
##########
@@ -268,3 +277,173 @@ def 
test_rs_setbandnodatavalue_two_arg_requires_single_band():
         SedonaDB().assert_query_result(
             "SELECT RS_SetBandNoDataValue(RS_Example(), 0)", None
         )
+
+
+# RS_Values samples one pixel per sub-point and returns a List<Double> in input
+# order. Same raster facts as `test_rs_value_point`: band `b` is filled with 
`b`,
+# (74.58, 110.57) is the centroid of pixel (10, 10), and (44.58, 80.57) is the
+# centroid of the top-left pixel (0, 0), which is set to nodata. A point far
+# outside the footprint yields a NULL element; the whole result is never NULL
+# here because the geometry is non-null.
[email protected](
+    ("expr", "expected"),
+    [
+        # Two in-bounds points + one outside, on band 1.
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57, 74.58 110.57, 0 0)', 'OGC:CRS84'), 1)",
+            [1.0, 1.0, None],
+        ),
+        # Explicit band selects the plane; nodata corner and outside are NULL.
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57, 44.58 80.57, 0 0)', 'OGC:CRS84'), 2)",
+            [2.0, None, None],
+        ),
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57)', 'OGC:CRS84'), 3)",
+            [3.0],
+        ),
+        # A bare Point is accepted and yields a one-element list.
+        (
+            "RS_Values(RS_Example(), ST_Point(74.58, 110.57, 'OGC:CRS84'), 1)",
+            [1.0],
+        ),
+        # An empty MultiPoint is an empty list (not NULL).
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT EMPTY', 
'OGC:CRS84'), 1)",
+            [],
+        ),
+    ],
+)
+def test_rs_values_multipoint(expr, expected):
+    SedonaDB().assert_query_result(f"SELECT {expr}", [(expected,)])
+
+
+def test_rs_values_default_band_requires_single_band(con):
+    # RS_Example has 3 bands, so omitting the band is ambiguous and errors.
+    with pytest.raises(Exception, match="specify which band"):
+        con.sql(
+            "SELECT RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57)', 'OGC:CRS84'))"
+        ).to_arrow_table()
+
+
+def test_rs_values_ensureloaded_outdb(con, sedona_testing):
+    """RS_Values over an OutDb raster exercises the needs_pixels ->
+    RS_EnsureLoaded planner path end to end: the raster from RS_FromPath 
carries
+    no pixels, so the planner must materialise it before the kernel samples.
+
+    sentinel2.tif's top-left pixel holds 2324 (see test_rs_ensureloaded); its
+    world center is derived from the raster's own georeference so the test does
+    not hard-code the file's geotransform.
+    """
+    path = sedona_testing / "data/raster/sentinel2.tif"
+    t = con.sql("SELECT RS_FromPath($1) AS raster", params=(str(path),))
+    view = "test_rs_values_ensureloaded_outdb_raster"
+    t.to_view(view)
+    try:
+        meta = con.sql(
+            f"SELECT RS_GeoReference(raster) AS georef, RS_SRID(raster) AS 
srid FROM {view}"
+        ).to_arrow_table()
+        georef = [float(v) for v in meta["georef"][0].as_py().split()]
+        scale_x, skew_y, skew_x, scale_y, ul_x, ul_y = georef
+        srid = meta["srid"][0].as_py()
+        # World center of pixel (0, 0): upper-left corner + half a pixel step.
+        cx = ul_x + 0.5 * scale_x + 0.5 * skew_x
+        cy = ul_y + 0.5 * skew_y + 0.5 * scale_y
+
+        result = (
+            con.sql(
+                f"""
+            SELECT RS_Values(
+                raster,
+                ST_GeomFromText('MULTIPOINT ({cx} {cy})', 'EPSG:{srid}'),
+                1
+            ) AS v FROM {view}
+            """
+            )
+            .to_arrow_table()["v"][0]
+            .as_py()

Review Comment:
   done



##########
python/sedonadb/tests/functions/test_raster_functions.py:
##########
@@ -283,6 +292,183 @@ def 
test_rs_setbandnodatavalue_two_arg_requires_single_band():
         )
 
 
+# RS_Values samples one pixel per sub-point and returns a List<Double> in input
+# order. Same raster facts as `test_rs_value_point`: band `b` is filled with 
`b`,
+# (74.58, 110.57) is the centroid of pixel (10, 10), and (44.58, 80.57) is the
+# centroid of the top-left pixel (0, 0), which is set to nodata. A point far
+# outside the footprint yields a NULL element; the whole result is never NULL
+# here because the geometry is non-null.
[email protected](
+    ("expr", "expected"),
+    [
+        # Two in-bounds points + one outside, on band 1.
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57, 74.58 110.57, 0 0)', 'OGC:CRS84'), 1)",
+            [1.0, 1.0, None],
+        ),
+        # Explicit band selects the plane; nodata corner and outside are NULL.
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57, 44.58 80.57, 0 0)', 'OGC:CRS84'), 2)",
+            [2.0, None, None],
+        ),
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57)', 'OGC:CRS84'), 3)",
+            [3.0],
+        ),
+        # A bare Point is accepted and yields a one-element list.
+        (
+            "RS_Values(RS_Example(), ST_Point(74.58, 110.57, 'OGC:CRS84'), 1)",
+            [1.0],
+        ),
+        # An empty MultiPoint is an empty list (not NULL).
+        (
+            "RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT EMPTY', 
'OGC:CRS84'), 1)",
+            [],
+        ),
+    ],
+)
+def test_rs_values_multipoint(expr, expected):
+    SedonaDB().assert_query_result(f"SELECT {expr}", [(expected,)])
+
+
+def test_rs_values_default_band_requires_single_band(con):
+    # RS_Example has 3 bands, so omitting the band is ambiguous and errors.
+    with pytest.raises(Exception, match="specify which band"):
+        con.sql(
+            "SELECT RS_Values(RS_Example(), ST_GeomFromText('MULTIPOINT (74.58 
110.57)', 'OGC:CRS84'))"
+        ).to_arrow_table()
+
+
+def test_rs_values_ensureloaded_outdb(con, sedona_testing):
+    """RS_Values over an OutDb raster exercises the needs_pixels ->
+    RS_EnsureLoaded planner path end to end: the raster from RS_FromPath 
carries
+    no pixels, so the planner must materialise it before the kernel samples.
+
+    sentinel2.tif's top-left pixel holds 2324 (see test_rs_ensureloaded); its
+    world center is derived from the raster's own georeference so the test does
+    not hard-code the file's geotransform.
+    """
+    path = sedona_testing / "data/raster/sentinel2.tif"
+    t = con.sql("SELECT RS_FromPath($1) AS raster", params=(str(path),))
+    view = "test_rs_values_ensureloaded_outdb_raster"
+    t.to_view(view)
+    try:
+        # `.to_pylist()` converts the whole (one-row) table in one pass;
+        # indexing a column with `[0]` first would force a chunk-combining
+        # copy in pyarrow.
+        meta = (
+            con.sql(
+                f"SELECT RS_GeoReference(raster) AS georef, RS_SRID(raster) AS 
srid FROM {view}"
+            )
+            .to_arrow_table()
+            .to_pylist()[0]
+        )
+        georef = [float(v) for v in meta["georef"].split()]
+        scale_x, skew_y, skew_x, scale_y, ul_x, ul_y = georef
+        srid = meta["srid"]
+        # World center of pixel (0, 0): upper-left corner + half a pixel step.
+        cx = ul_x + 0.5 * scale_x + 0.5 * skew_x
+        cy = ul_y + 0.5 * skew_y + 0.5 * scale_y
+
+        values = (
+            con.sql(
+                f"""
+            SELECT RS_Values(
+                raster,
+                ST_GeomFromText('MULTIPOINT ({cx} {cy})', 'EPSG:{srid}'),
+                1
+            ) AS v FROM {view}
+            """
+            )
+            .to_arrow_table()["v"]
+            .to_pylist()
+        )
+        assert values == [[2324.0]]
+    finally:
+        con.drop_view(view)
+
+
+def test_rs_values_matches_rasterio(con):
+    """Cross-check RS_Values against rasterio on a random raster.
+
+    The plural counterpart of `test_rs_value_matches_rasterio`: the same dense
+    set of sample points is passed as a single MultiPoint, so one `RS_Values`
+    call returns a list that must match rasterio's per-point reads element for
+    element, in order.
+    """
+    import numpy as np

Review Comment:
   done



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to