Skip to content

Commit 43d53d6

Browse files
committed
Merge branch 'issue949-cube-metadata2'
2 parents 91bcb8f + 9d71711 commit 43d53d6

7 files changed

Lines changed: 54 additions & 29 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Changed
1313

1414
- Ranged downloads (e.g. of batch job results) no longer implement their own retry loop; transient failures are retried by the standard urllib3 retry configuration of the connection's session ([#934](https://github.com/Open-EO/openeo-python-client/issues/934))
15+
- Replaced improper `CollectionMetadata` usage with `CubeMetadata`. `CollectionMetadata` describes the metadata of a whole openEO/STAC collection, while `CubeMetadata` describes the specific dimension metadata of a concrete cube being processed. (Related to [464](https://github.com/Open-EO/openeo-python-client/issues/464), [#949](https://github.com/Open-EO/openeo-python-client/issues/949), [#827](https://github.com/Open-EO/openeo-python-client/issues/827).)
16+
- The `metadata` attribute of `DataCube`/`VectorCube` is now a `CubeMetadata` object instead of misleading `CollectionMetadata`.
17+
- The `apply_metadata()` UDF signature changed to use `CubeMetadata` as input and output type annotation.
1518

1619
### Removed
1720

‎openeo/extra/spectral_indices/spectral_indices.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
import importlib.resources
33
import json
44
import re
5-
from typing import Dict, List, Optional, Set
5+
from typing import Dict, Iterable, List, Optional, Set
66

77
from openeo import BaseOpenEoException
8-
from openeo.metadata import CollectionMetadata
98
from openeo.processes import ProcessBuilder, array_create, array_modify
109
from openeo.rest.datacube import DataCube
1110

@@ -211,6 +210,14 @@ def _callback(
211210
return array_create(data=index_values)
212211

213212

213+
def _extract_collection_ids(cube: DataCube) -> Iterable[str]:
214+
"""Determine the collection ids used in load_collection nodes in the process graph."""
215+
for node in cube.result_node().walk_nodes():
216+
if node.process_id == "load_collection" and isinstance(node.arguments.get("id"), str):
217+
yield node.arguments["id"]
218+
# TODO: Possible to determing/guess collection id or name from `load_stac` url (without requesting it)?
219+
220+
214221
def compute_and_rescale_indices(
215222
datacube: DataCube,
216223
index_dict: dict,
@@ -270,10 +277,14 @@ def compute_and_rescale_indices(
270277
# Automatic band mapping
271278
band_mapping = _BandMapping()
272279
if platform is None:
273-
if isinstance(datacube.metadata, CollectionMetadata) and datacube.metadata.get("id"):
274-
platform = band_mapping.guess_platform(name=datacube.metadata.get("id"))
280+
collection_ids = set(_extract_collection_ids(cube=datacube))
281+
platform_guesses = set(band_mapping.guess_platform(name=cid) for cid in collection_ids)
282+
if len(platform_guesses) == 1:
283+
[platform] = platform_guesses
275284
else:
276-
raise BandMappingException("Unable to determine satellite platform from data cube metadata")
285+
raise BandMappingException(
286+
f"Unable to guess satellite platform from {collection_ids=} ({platform_guesses=})"
287+
)
277288
band_to_var = band_mapping.actual_band_name_to_variable_map(
278289
platform=platform, band_names=datacube.metadata.band_names
279290
)

‎openeo/local/connection.py‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
Band,
2222
BandDimension,
2323
CollectionMetadata,
24+
CubeMetadata,
2425
SpatialDimension,
2526
TemporalDimension,
2627
)
@@ -233,13 +234,7 @@ def load_stac(
233234
if temporal_extent is not None:
234235
arguments["temporal_extent"] = TemporalInterval.parse_obj(temporal_extent)
235236
xarray_cube = load_stac(**arguments)
236-
attrs = xarray_cube.attrs
237-
for at in attrs:
238-
# allowed types: str, Number, ndarray, number, list, tuple
239-
if not isinstance(attrs[at], (int, float, str, np.ndarray, list, tuple)):
240-
attrs[at] = str(attrs[at])
241-
metadata = CollectionMetadata(
242-
attrs,
237+
metadata = CubeMetadata(
243238
dimensions=[
244239
SpatialDimension(name=xarray_cube.openeo.x_dim, extent=[]),
245240
SpatialDimension(name=xarray_cube.openeo.y_dim, extent=[]),

‎openeo/metadata/__init__.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,9 +544,15 @@ class CollectionMetadata(CubeMetadata):
544544
Metadata is expected to follow format defined by
545545
https://openeo.org/documentation/1.0/developers/api/reference.html#operation/describe-collection
546546
(with partial support for older versions)
547-
548547
"""
549548

549+
# TODO #949 Fully decouple CollectionMetadata from CubeMetadata.
550+
# In the beginning, there was only CollectionMetadata, and later CubeMetadata was inserted as quickfix.
551+
# Superficially there are similarities, but this coupled design is becoming counterproductive.
552+
# CollectionMetadata generally describes an external, immutable (STAC) resource,
553+
# while CubeMetadata is for keeping track of important dimension/metadata aspects during cube manipulations.
554+
# Problem: openeo-geopyspark-driver still heavily depends on CollectionMetadata acting as cube metadata.
555+
550556
def __init__(self, metadata: dict, dimensions: List[Dimension] = None, _federation: Optional[dict] = None):
551557
self._orig_metadata = metadata
552558
if dimensions is None:
@@ -572,6 +578,7 @@ def _parse_dimensions(cls, spec: dict, complain: Callable[[str], None] = _log.wa
572578
:return list: list of `Dimension` objects
573579
574580
"""
581+
# TODO: can this whole parse logic be replaced with _StacMetadataParser/metadata_from_stac logic?
575582

576583
# Dimension info is in `cube:dimensions` (or 0.4-style `properties/cube:dimensions`)
577584
cube_dimensions = (
@@ -654,6 +661,10 @@ def _clone_and_update(
654661
655662
This overrides the method in `CubeMetadata` to keep the original metadata.
656663
"""
664+
# TODO #949 there are multiple (indications of) design errors here:
665+
# - the signature is not compatible with base interface
666+
# - at client side, collection metadata is an external resource and immutable client side
667+
# supporting a transform to a new "CollectionMetadata" object makes little sense
657668
cls = type(self)
658669
if metadata is None:
659670
metadata = self._orig_metadata

‎openeo/rest/datacube.py‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
from openeo.internal.warnings import UserDeprecationWarning, deprecated, legacy_alias
4949
from openeo.metadata import (
5050
Band,
51-
CollectionMetadata,
5251
CubeMetadata,
5352
metadata_from_stac,
5453
)
@@ -106,10 +105,14 @@ class DataCube(_ProcessGraphAbstraction):
106105
_DEFAULT_RASTER_FORMAT = "GTiff"
107106

108107
def __init__(
109-
self, graph: PGNode, connection: Optional[Connection] = None, metadata: Optional[CollectionMetadata] = None
108+
self,
109+
graph: PGNode,
110+
*,
111+
connection: Optional[Connection] = None,
112+
metadata: Optional[CubeMetadata] = None,
110113
):
111114
super().__init__(pgnode=graph, connection=connection)
112-
self.metadata: Optional[CollectionMetadata] = metadata
115+
self.metadata: Optional[CubeMetadata] = metadata
113116

114117
def process(
115118
self,
@@ -137,7 +140,7 @@ def process(
137140

138141
graph_add_node = legacy_alias(process, "graph_add_node", since="0.1.1")
139142

140-
def process_with_node(self, pg: PGNode, metadata: Optional[CollectionMetadata] = None) -> DataCube:
143+
def process_with_node(self, pg: PGNode, metadata: Optional[CubeMetadata] = None) -> DataCube:
141144
"""
142145
Generic helper to create a new DataCube by applying a process (given as process graph node)
143146
@@ -234,8 +237,11 @@ def load_collection(
234237
}
235238
if isinstance(collection_id, Parameter):
236239
fetch_metadata = False
237-
metadata: Optional[CollectionMetadata] = (
238-
connection.collection_metadata(collection_id) if connection and fetch_metadata else None
240+
metadata: Optional[CubeMetadata] = (
241+
# TODO: eliminate `.collection_metadata()._dimensions` hack and directly parse metadata like load_stac?
242+
CubeMetadata(dimensions=connection.collection_metadata(collection_id)._dimensions)
243+
if (connection and fetch_metadata)
244+
else None
239245
)
240246
if bands is not None:
241247
bands = cls._get_bands(bands, process_id="load_collection")

‎openeo/rest/vectorcube.py‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from openeo.internal.documentation import openeo_process
1414
from openeo.internal.graph_building import PGNode
1515
from openeo.internal.warnings import legacy_alias
16-
from openeo.metadata import CollectionMetadata, CubeMetadata, Dimension
16+
from openeo.metadata import CubeMetadata, Dimension
1717
from openeo.rest import (
1818
DEFAULT_JOB_STATUS_POLL_CONNECTION_RETRY_INTERVAL,
1919
DEFAULT_JOB_STATUS_POLL_INTERVAL_MAX,
@@ -53,20 +53,19 @@ def __init__(self, graph: PGNode, connection: Union[Connection, None], metadata:
5353
self.metadata = metadata
5454

5555
@classmethod
56-
def _build_metadata(cls, add_properties: bool = False) -> CollectionMetadata:
57-
"""Helper to build a (minimal) `CollectionMetadata` object."""
56+
def _build_metadata(cls, add_properties: bool = False) -> CubeMetadata:
57+
"""Helper to build a (minimal) `CubeMetadata` object."""
5858
# Vector cubes have at least a "geometry" dimension
5959
dimensions = [Dimension(name="geometry", type="geometry")]
6060
if add_properties:
6161
dimensions.append(Dimension(name="properties", type="other"))
62-
# TODO #464: use a more generic metadata container than "collection" metadata
63-
return CollectionMetadata(metadata={}, dimensions=dimensions)
62+
return CubeMetadata(dimensions=dimensions)
6463

6564
def process(
6665
self,
6766
process_id: str,
6867
arguments: dict = None,
69-
metadata: Optional[CollectionMetadata] = None,
68+
metadata: Optional[CubeMetadata] = None,
7069
namespace: Optional[str] = None,
7170
**kwargs,
7271
) -> VectorCube:

‎openeo/udf/udf_signatures.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from deprecated import deprecated
1111
from pandas import Series
1212

13-
from openeo.metadata import CollectionMetadata
13+
from openeo.metadata import CubeMetadata
1414
from openeo.udf.udf_data import UdfData
1515
from openeo.udf.xarraydatacube import XarrayDataCube
1616

@@ -79,7 +79,7 @@ def apply_udf_data(data: UdfData):
7979
pass
8080

8181

82-
def apply_metadata(metadata: CollectionMetadata, context: dict) -> CollectionMetadata:
82+
def apply_metadata(metadata: CubeMetadata, context: dict) -> CubeMetadata:
8383
"""
8484
.. warning::
8585
This signature is not yet fully standardized and subject to change.
@@ -93,7 +93,7 @@ def apply_metadata(metadata: CollectionMetadata, context: dict) -> CollectionMet
9393
This function does not need to be provided when using the UDF in combination with processes that by design have a clear
9494
effect on cube metadata, such as :py:meth:`~openeo.rest.datacube.DataCube.reduce_dimension()`
9595
96-
:param metadata: the collection metadata of the input data cube
96+
:param metadata: the metadata of the input data cube
9797
:param context: A dictionary containing user context.
9898
9999
:return: output metadata: the expected metadata of the cube, after applying the udf
@@ -103,7 +103,7 @@ def apply_metadata(metadata: CollectionMetadata, context: dict) -> CollectionMet
103103
104104
An example for a UDF that is applied on the 'bands' dimension, and returns a new set of bands with different labels.
105105
106-
>>> def apply_metadata(metadata: CollectionMetadata, context: dict) -> CollectionMetadata:
106+
>>> def apply_metadata(metadata: CubeMetadata, context: dict) -> CubeMetadata:
107107
... return metadata.rename_labels(
108108
... dimension="bands",
109109
... target=["computed_band_1", "computed_band_2"]

0 commit comments

Comments
 (0)