Skip to content

Commit 13febaa

Browse files
raster-blasterRasterBlastervincentsarago
authored
fix: reset pixel_selection per feature in statistics FeatureCollection (#1334)
* fix: reset pixel_selection state per feature in statistics endpoint When processing a FeatureCollection with multiple distinct features, the pixel_selection object (e.g. FirstMethod) retained its internal mosaic array state from the previous feature. If features had different geometries producing different pixel dimensions, this caused a ValueError in numpy broadcasting: ValueError: operands could not be broadcast together with shapes (1,7,15) (1,4,11) The fix creates a fresh pixel_selection instance for each feature in the FeatureCollection loop, ensuring no stale state carries over. * fix mosaic pixelselection dependency --------- Co-authored-by: RasterBlaster <rasterblaster@Sravanthis-MacBook-Air.local> Co-authored-by: vincentsarago <vincent.sarago@gmail.com>
1 parent 7ccd945 commit 13febaa

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

src/titiler/mosaic/tests/test_factory.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,3 +849,103 @@ def test_wmts_extension_mosaic():
849849
assert ["0", "1"] == list(
850850
layer.tilematrixsetlinks["WebMercatorQuad"].tilematrixlimits
851851
)
852+
853+
854+
def test_mosaic_statistics_featurecollection():
855+
"""Test statistics endpoint with FeatureCollection containing multiple distinct features.
856+
857+
Regression test for bug where pixel_selection state from feature N
858+
carried over to feature N+1, causing ValueError when features have
859+
different pixel dimensions.
860+
"""
861+
mosaic = MosaicTilerFactory(
862+
backend=MosaicJSONBackend,
863+
add_statistics=True,
864+
router_prefix="mosaic",
865+
)
866+
app = FastAPI()
867+
app.include_router(mosaic.router, prefix="/mosaic")
868+
client = TestClient(app)
869+
870+
# Two distinct polygons within the mosaic bounds but different sizes
871+
# mosaic bounds: [-75.98, 44.93, -71.33, 47.09]
872+
feature_a = {
873+
"type": "Feature",
874+
"properties": {},
875+
"geometry": {
876+
"type": "Polygon",
877+
"coordinates": [
878+
[
879+
[-74.5, 45.5],
880+
[-74.5, 46.0],
881+
[-74.0, 46.0],
882+
[-74.0, 45.5],
883+
[-74.5, 45.5],
884+
]
885+
],
886+
},
887+
}
888+
889+
feature_b = {
890+
"type": "Feature",
891+
"properties": {},
892+
"geometry": {
893+
"type": "Polygon",
894+
"coordinates": [
895+
[
896+
[-73.5, 45.0],
897+
[-73.5, 45.3],
898+
[-73.0, 45.3],
899+
[-73.0, 45.0],
900+
[-73.5, 45.0],
901+
]
902+
],
903+
},
904+
}
905+
906+
with tmpmosaic() as mosaic_file:
907+
# Test 1: Single Feature works
908+
response = client.post(
909+
"/mosaic/statistics",
910+
params={"url": mosaic_file},
911+
json=feature_a,
912+
)
913+
assert response.status_code == 200
914+
assert response.headers["content-type"] == "application/geo+json"
915+
resp = response.json()
916+
assert resp["type"] == "Feature"
917+
assert "statistics" in resp["properties"]
918+
919+
# Test 2: FeatureCollection with 1 feature works
920+
fc_single = {"type": "FeatureCollection", "features": [feature_a]}
921+
response = client.post(
922+
"/mosaic/statistics",
923+
params={"url": mosaic_file},
924+
json=fc_single,
925+
)
926+
assert response.status_code == 200
927+
resp = response.json()
928+
assert resp["type"] == "FeatureCollection"
929+
assert len(resp["features"]) == 1
930+
assert "statistics" in resp["features"][0]["properties"]
931+
932+
# Test 3: FeatureCollection with 2 DISTINCT features
933+
# This was the bug - pixel_selection state from feature_a
934+
# caused ValueError when processing feature_b
935+
fc_multi = {"type": "FeatureCollection", "features": [feature_a, feature_b]}
936+
response = client.post(
937+
"/mosaic/statistics",
938+
params={"url": mosaic_file},
939+
json=fc_multi,
940+
)
941+
assert response.status_code == 200
942+
resp = response.json()
943+
assert resp["type"] == "FeatureCollection"
944+
assert len(resp["features"]) == 2
945+
assert "statistics" in resp["features"][0]["properties"]
946+
assert "statistics" in resp["features"][1]["properties"]
947+
# Each feature should have its own statistics
948+
stats_a = resp["features"][0]["properties"]["statistics"]
949+
stats_b = resp["features"][1]["properties"]["statistics"]
950+
assert len(stats_a) > 0
951+
assert len(stats_b) > 0

src/titiler/mosaic/titiler/mosaic/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def PixelSelectionParams(
7575
"""
7676
Returns the mosaic method used to combine datasets together.
7777
"""
78-
return PixelSelectionMethod[pixel_selection].value()
78+
return PixelSelectionMethod[pixel_selection].value
7979

8080

8181
def DatasetPathParams(url: Annotated[str, Query(description="Mosaic URL")]) -> str:

0 commit comments

Comments
 (0)