Skip to content

Commit be0c986

Browse files
Also support openeo.api.process.Parameter objects for the projection argument of resample_spatial #897
1 parent b4c3497 commit be0c986

5 files changed

Lines changed: 95 additions & 29 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
### Removed
2121

2222
### Fixed
23-
- `DataCube.resample_spatial()` now supports `openeo.api.process.Parameter` objects for the `resolution` parameter. ([#897](https://github.com/Open-EO/openeo-python-client/issues/897))
23+
- `DataCube.resample_spatial()` now supports `openeo.api.process.Parameter` objects for the `resolution` and `projection` parameter. ([#897](https://github.com/Open-EO/openeo-python-client/issues/897))
2424

2525
## [0.49.0] - 2026-04-01
2626

openeo/metadata.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -495,13 +495,14 @@ def drop_dimension(self, name: str = None) -> CubeMetadata:
495495

496496
def resample_spatial(
497497
self,
498-
resolution: Union[float, Tuple[float, float], List[float], "Parameter"] = 0.0,
499-
projection: Union[int, str, None] = None,
498+
resolution: Union[float, Tuple[float, float], List[float], Parameter] = 0.0,
499+
projection: Union[int, str, Parameter, None] = None,
500500
) -> CubeMetadata:
501501
if isinstance(resolution, Parameter):
502-
normalized_resolution = None
502+
normalized_resolution = None, None
503503
else:
504504
normalized_resolution = normalize_resample_resolution(resolution)
505+
505506
if self._dimensions is None:
506507
# Best-effort fallback to work with
507508
dimensions = [
@@ -516,25 +517,16 @@ def resample_spatial(
516517
spatial_indices = [i for i, d in enumerate(dimensions) if isinstance(d, SpatialDimension)]
517518
if len(spatial_indices) != 2:
518519
raise MetadataException(f"Expected two spatial dimensions but found {spatial_indices=}")
519-
if normalized_resolution is not None:
520-
assert len(normalized_resolution) == 2
521-
for i, r in zip(spatial_indices, normalized_resolution):
522-
dim: SpatialDimension = dimensions[i]
523-
dimensions[i] = SpatialDimension(
524-
name=dim.name,
525-
extent=dim.extent,
526-
crs=projection or dim.crs,
527-
step=r if r != 0.0 else dim.step,
528-
)
529-
elif projection:
530-
for i in spatial_indices:
531-
dim: SpatialDimension = dimensions[i]
532-
dimensions[i] = SpatialDimension(
533-
name=dim.name,
534-
extent=dim.extent,
535-
crs=projection,
536-
step=dim.step,
537-
)
520+
521+
assert len(normalized_resolution) == 2
522+
for i, r in zip(spatial_indices, normalized_resolution):
523+
dim: SpatialDimension = dimensions[i]
524+
dimensions[i] = SpatialDimension(
525+
name=dim.name,
526+
extent=dim.extent,
527+
crs=None if isinstance(projection, Parameter) else projection or dim.crs,
528+
step=r if r != 0.0 else dim.step,
529+
)
538530

539531
return self._clone_and_update(dimensions=dimensions)
540532

openeo/rest/datacube.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,7 @@ def band(self, band: Union[str, int]) -> DataCube:
810810
def resample_spatial(
811811
self,
812812
resolution: Union[float, Tuple[float, float], List[float], Parameter] = 0.0,
813-
projection: Union[int, str, None] = None,
813+
projection: Union[int, str, Parameter, None] = None,
814814
method: str = "near",
815815
align: str = "upper-left",
816816
) -> DataCube:

tests/rest/datacube/test_datacube.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,51 @@ def test_resample_spatial_parameter_resolution_no_projection(s2cube):
913913
}
914914

915915

916+
def test_resample_spatial_parameter_projection(s2cube):
917+
"""A Parameter object passed as projection must appear in the process graph; metadata crs should be None."""
918+
proj_param = Parameter.integer("proj", description="The target projection.")
919+
cube = s2cube.resample_spatial(resolution=10, projection=proj_param)
920+
assert get_download_graph(cube, drop_load_collection=True, drop_save_result=True) == {
921+
"resamplespatial1": {
922+
"process_id": "resample_spatial",
923+
"arguments": {
924+
"data": {"from_node": "loadcollection1"},
925+
"resolution": 10,
926+
"projection": {"from_parameter": "proj"},
927+
"method": "near",
928+
"align": "upper-left",
929+
},
930+
}
931+
}
932+
assert cube.metadata.spatial_dimensions == [
933+
SpatialDimension(name="x", extent=None, crs=None, step=10),
934+
SpatialDimension(name="y", extent=None, crs=None, step=10),
935+
]
936+
937+
938+
def test_resample_spatial_parameter_resolution_and_projection(s2cube):
939+
"""When both resolution and projection are Parameters, step and crs should both be None."""
940+
res_param = Parameter.number("res", description="The spatial resolution.")
941+
proj_param = Parameter.integer("proj", description="The target projection.")
942+
cube = s2cube.resample_spatial(resolution=res_param, projection=proj_param)
943+
assert get_download_graph(cube, drop_load_collection=True, drop_save_result=True) == {
944+
"resamplespatial1": {
945+
"process_id": "resample_spatial",
946+
"arguments": {
947+
"data": {"from_node": "loadcollection1"},
948+
"resolution": {"from_parameter": "res"},
949+
"projection": {"from_parameter": "proj"},
950+
"method": "near",
951+
"align": "upper-left",
952+
},
953+
}
954+
}
955+
assert cube.metadata.spatial_dimensions == [
956+
SpatialDimension(name="x", extent=None, crs=None, step=None),
957+
SpatialDimension(name="y", extent=None, crs=None, step=None),
958+
]
959+
960+
916961
def test_resample_cube_spatial(s2cube):
917962
cube1 = s2cube.resample_spatial(resolution=[2.0, 3.0], projection=4578)
918963
cube2 = s2cube.resample_spatial(resolution=10, projection=32631)

tests/test_metadata.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,10 +1286,10 @@ def test_metadata_resample_spatial_parameter_resolution_only(cube_metadata):
12861286
param = Parameter.number("res", description="The spatial resolution.")
12871287
metadata = cube_metadata.resample_spatial(resolution=param)
12881288
assert isinstance(metadata, CubeMetadata)
1289-
# step must not change because the resolution is unknown at build time
1289+
# step must be set to None after parametrized resample_spatial.
12901290
assert metadata.spatial_dimensions == [
1291-
SpatialDimension(name="x", extent=[2, 7], crs=4326, step=0.1),
1292-
SpatialDimension(name="y", extent=[49, 52], crs=4326, step=0.1),
1291+
SpatialDimension(name="x", extent=[2, 7], crs=4326, step=None),
1292+
SpatialDimension(name="y", extent=[49, 52], crs=4326, step=None),
12931293
]
12941294
assert metadata.temporal_dimension == cube_metadata.temporal_dimension
12951295
assert metadata.band_dimension == cube_metadata.band_dimension
@@ -1302,8 +1302,37 @@ def test_metadata_resample_spatial_parameter_resolution_with_projection(cube_met
13021302
metadata = cube_metadata.resample_spatial(resolution=param, projection=32631)
13031303
assert isinstance(metadata, CubeMetadata)
13041304
assert metadata.spatial_dimensions == [
1305-
SpatialDimension(name="x", extent=[2, 7], crs=32631, step=0.1),
1306-
SpatialDimension(name="y", extent=[49, 52], crs=32631, step=0.1),
1305+
SpatialDimension(name="x", extent=[2, 7], crs=32631, step=None),
1306+
SpatialDimension(name="y", extent=[49, 52], crs=32631, step=None),
1307+
]
1308+
assert metadata.temporal_dimension == cube_metadata.temporal_dimension
1309+
assert metadata.band_dimension == cube_metadata.band_dimension
1310+
1311+
1312+
@pytest.mark.parametrize("cube_metadata", [CUBE_METADATA_XYTB, CUBE_METADATA_TBXY])
1313+
def test_metadata_resample_spatial_parameter_projection_only(cube_metadata):
1314+
"""When projection is a Parameter, crs should be set to None."""
1315+
param = Parameter.integer("proj", description="The target projection.")
1316+
metadata = cube_metadata.resample_spatial(resolution=10, projection=param)
1317+
assert isinstance(metadata, CubeMetadata)
1318+
assert metadata.spatial_dimensions == [
1319+
SpatialDimension(name="x", extent=[2, 7], crs=None, step=10),
1320+
SpatialDimension(name="y", extent=[49, 52], crs=None, step=10),
1321+
]
1322+
assert metadata.temporal_dimension == cube_metadata.temporal_dimension
1323+
assert metadata.band_dimension == cube_metadata.band_dimension
1324+
1325+
1326+
@pytest.mark.parametrize("cube_metadata", [CUBE_METADATA_XYTB, CUBE_METADATA_TBXY])
1327+
def test_metadata_resample_spatial_parameter_resolution_and_projection(cube_metadata):
1328+
"""When both resolution and projection are Parameters, step and crs should both be None."""
1329+
res_param = Parameter.number("res", description="The spatial resolution.")
1330+
proj_param = Parameter.integer("proj", description="The target projection.")
1331+
metadata = cube_metadata.resample_spatial(resolution=res_param, projection=proj_param)
1332+
assert isinstance(metadata, CubeMetadata)
1333+
assert metadata.spatial_dimensions == [
1334+
SpatialDimension(name="x", extent=[2, 7], crs=None, step=None),
1335+
SpatialDimension(name="y", extent=[49, 52], crs=None, step=None),
13071336
]
13081337
assert metadata.temporal_dimension == cube_metadata.temporal_dimension
13091338
assert metadata.band_dimension == cube_metadata.band_dimension

0 commit comments

Comments
 (0)