Skip to content

Commit 132c99c

Browse files
committed
Issue #737 leverage pystac for deeper spatial dimension detection
1 parent 9b92d6f commit 132c99c

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

openeo/metadata.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -655,12 +655,17 @@ def is_band_asset(asset: pystac.Asset) -> bool:
655655
else:
656656
raise ValueError(stac_object)
657657

658-
# At least assume there are spatial dimensions
659-
# TODO: are there conditions in which we even should not assume the presence of spatial dimensions?
660-
dimensions = [
661-
SpatialDimension(name="x", extent=[None, None]),
662-
SpatialDimension(name="y", extent=[None, None]),
663-
]
658+
dimensions = []
659+
660+
spatial_dimensions = _StacMetadataParser().get_spatial_dimensions(stac_object)
661+
# Unless spatial dimensions are explicitly absent from the STAC metadata:
662+
# assume we at least have spatial dimensions ("x" and "y", per openEO API recommendation).
663+
if spatial_dimensions is None:
664+
spatial_dimensions = [
665+
SpatialDimension(name="x", extent=[None, None]),
666+
SpatialDimension(name="y", extent=[None, None]),
667+
]
668+
dimensions.extend(spatial_dimensions)
664669

665670
# TODO: conditionally include band dimension when there was actual indication of band metadata?
666671
band_dimension = BandDimension(name="bands", bands=bands)
@@ -780,3 +785,17 @@ def get_temporal_dimension(self, stac_obj: pystac.STACObject) -> Union[TemporalD
780785
if len(temporal_dims) == 1:
781786
name, extent = temporal_dims[0]
782787
return TemporalDimension(name=name, extent=extent)
788+
789+
def get_spatial_dimensions(self, stac_obj: pystac.STACObject) -> Union[List[SpatialDimension], None]:
790+
if _PYSTAC_1_9_EXTENSION_INTERFACE:
791+
if stac_obj.ext.has("cube") and hasattr(stac_obj.ext, "cube"):
792+
return [
793+
SpatialDimension(
794+
name=n,
795+
extent=d.extent or [None, None],
796+
crs=d.reference_system or SpatialDimension.DEFAULT_CRS,
797+
step=d.step,
798+
)
799+
for (n, d) in stac_obj.ext.cube.dimensions.items()
800+
if d.dim_type == pystac.extensions.datacube.DimensionType.SPATIAL
801+
]

tests/test_metadata.py

+87
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,93 @@ def test_metadata_from_stac_temporal_dimension(tmp_path, stac_dict, expected):
937937
assert not metadata.has_temporal_dimension()
938938

939939

940+
@pytest.mark.skipif(
941+
not _PYSTAC_1_9_EXTENSION_INTERFACE,
942+
reason="No backport of implementation/test below PySTAC 1.9 extension interface",
943+
)
944+
@pytest.mark.parametrize(
945+
["stac_dict", "expected"],
946+
[
947+
(
948+
# Item without cube:dimensions metadata -> assume spatial dimensions x and y
949+
StacDummyBuilder.item(),
950+
{"x": [None, None], "y": [None, None]},
951+
),
952+
(
953+
StacDummyBuilder.item(
954+
cube_dimensions={
955+
"t": {"type": "temporal", "extent": ["2024-04-04", "2024-06-06"]},
956+
"x": {"type": "spatial", "axis": "x", "extent": [-10, 20]},
957+
"y": {"type": "spatial", "axis": "y", "extent": [30, 50]},
958+
}
959+
),
960+
{"x": [-10, 20], "y": [30, 50]},
961+
),
962+
(
963+
# Custom dimension names
964+
StacDummyBuilder.item(
965+
cube_dimensions={
966+
"TTT": {"type": "temporal", "extent": ["2024-04-04", "2024-06-06"]},
967+
"XXX": {"type": "spatial", "axis": "x", "extent": [-10, 20]},
968+
"YYY": {"type": "spatial", "axis": "y", "extent": [30, 50]},
969+
}
970+
),
971+
{"XXX": [-10, 20], "YYY": [30, 50]},
972+
),
973+
(
974+
# Explicitly absent spatial dimensions
975+
StacDummyBuilder.item(
976+
cube_dimensions={
977+
"t": {"type": "temporal", "extent": ["2024-04-04", "2024-06-06"]},
978+
}
979+
),
980+
{},
981+
),
982+
(
983+
# No cube:dimensions metadata -> assume spatial dimensions x and y
984+
StacDummyBuilder.collection(),
985+
{"x": [None, None], "y": [None, None]},
986+
),
987+
(
988+
StacDummyBuilder.collection(
989+
cube_dimensions={
990+
"t": {"type": "temporal", "extent": ["2024-04-04", "2024-06-06"]},
991+
"x": {"type": "spatial", "axis": "x", "extent": [-10, 20]},
992+
"y": {"type": "spatial", "axis": "y", "extent": [30, 50]},
993+
}
994+
),
995+
{"x": [-10, 20], "y": [30, 50]},
996+
),
997+
(
998+
# Explicitly absent spatial dimensions
999+
StacDummyBuilder.collection(
1000+
cube_dimensions={
1001+
"t": {"type": "temporal", "extent": ["2024-04-04", "2024-06-06"]},
1002+
}
1003+
),
1004+
{},
1005+
),
1006+
(
1007+
StacDummyBuilder.catalog(),
1008+
{"x": [None, None], "y": [None, None]},
1009+
),
1010+
(
1011+
# Note: a catalog is not supposed to have datacube extension enabled, but we should not choke on that
1012+
StacDummyBuilder.catalog(stac_extensions=[StacDummyBuilder._EXT_DATACUBE]),
1013+
{"x": [None, None], "y": [None, None]},
1014+
),
1015+
],
1016+
)
1017+
def test_metadata_from_stac_spatial_dimensions(tmp_path, stac_dict, expected):
1018+
path = tmp_path / "stac.json"
1019+
# TODO #738 real request mocking of STAC resources compatible with pystac?
1020+
path.write_text(json.dumps(stac_dict))
1021+
metadata = metadata_from_stac(str(path))
1022+
dims = metadata.spatial_dimensions
1023+
assert all(isinstance(d, SpatialDimension) for d in dims)
1024+
assert {d.name: d.extent for d in dims} == expected
1025+
1026+
9401027
@pytest.mark.parametrize(
9411028
["kwargs", "expected_x", "expected_y"],
9421029
[

0 commit comments

Comments
 (0)