diff --git a/CHANGELOG.md b/CHANGELOG.md index 125c1a5f..6e72b931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and start a new "In Progress" section above it. ## In progress +## 0.77.2 + +- fixup "polygons" argument of "apply_polygon" ([#229](https://github.com/Open-EO/openeo-python-driver/issues/229)) + ## 0.76.1 - Attempt to workaround issue with in-place process graph modification diff --git a/openeo_driver/ProcessGraphDeserializer.py b/openeo_driver/ProcessGraphDeserializer.py index fa5bb4c1..4642e6b2 100644 --- a/openeo_driver/ProcessGraphDeserializer.py +++ b/openeo_driver/ProcessGraphDeserializer.py @@ -789,9 +789,7 @@ def reduce_dimension(args: ProcessArgs, env: EvalEnv) -> DriverDataCube: @process_registry_100.add_function( spec=read_spec("openeo-processes/experimental/chunk_polygon.json"), name="chunk_polygon" ) -@process_registry_100.add_function(spec=read_spec("openeo-processes/2.x/proposals/apply_polygon.json")) -@process_registry_2xx.add_function(spec=read_spec("openeo-processes/2.x/proposals/apply_polygon.json")) -def apply_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube: +def chunk_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube: # TODO #229 deprecate this process and promote the "apply_polygon" name. # See https://github.com/Open-EO/openeo-processes/issues/287, https://github.com/Open-EO/openeo-processes/pull/298 data_cube = args.get_required("data", expected_type=DriverDataCube) @@ -829,6 +827,46 @@ def apply_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube: return data_cube.chunk_polygon(reducer=reduce_pg, chunks=polygon, mask_value=mask_value, context=context, env=env) +@process_registry_100.add_function(spec=read_spec("openeo-processes/2.x/proposals/apply_polygon.json")) +@process_registry_2xx.add_function(spec=read_spec("openeo-processes/2.x/proposals/apply_polygon.json")) +def apply_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube: + data_cube = args.get_required("data", expected_type=DriverDataCube) + process = args.get_deep("process", "process_graph", expected_type=dict) + polygons = args.get_required("polygons") + mask_value = args.get_optional("mask_value", expected_type=(int, float), default=None) + context = args.get_optional("context", default=None) + + # TODO #114 EP-3981 normalize first to vector cube and simplify logic + # TODO: this logic (copied from original chunk_polygon implementation) coerces the input polygons + # to a single MultiPolygon of pure (non-multi) polygons, which is conceptually wrong. + # Instead it should normalize to a feature collection or vector cube. + if isinstance(polygons, DelayedVector): + polygons = list(polygons.geometries) + for p in polygons: + if not isinstance(p, shapely.geometry.Polygon): + reason = "{m!s} is not a polygon.".format(m=p) + raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason) + polygon = MultiPolygon(polygons) + elif isinstance(polygons, shapely.geometry.base.BaseGeometry): + polygon = MultiPolygon(polygons) + elif isinstance(polygons, dict): + polygon = geojson_to_multipolygon(polygons) + if isinstance(polygon, shapely.geometry.Polygon): + polygon = MultiPolygon([polygon]) + elif isinstance(polygons, str): + # Delayed vector is not supported yet. + reason = "Polygon of type string is not yet supported." + raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason) + else: + reason = "Polygon type is not supported." + raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason) + if polygon.area == 0: + reason = "Polygon {m!s} has an area of {a!r}".format(m=polygon, a=polygon.area) + raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason) + + return data_cube.apply_polygon(polygons=polygon, process=process, mask_value=mask_value, context=context, env=env) + + @process_registry_100.add_function(spec=read_spec("openeo-processes/experimental/fit_class_random_forest.json")) @process_registry_2xx.add_function(spec=read_spec("openeo-processes/experimental/fit_class_random_forest.json")) def fit_class_random_forest(args: dict, env: EvalEnv) -> DriverMlModel: diff --git a/openeo_driver/_version.py b/openeo_driver/_version.py index 2f8690c6..e9d2db99 100644 --- a/openeo_driver/_version.py +++ b/openeo_driver/_version.py @@ -1 +1 @@ -__version__ = "0.77.1a1" +__version__ = "0.77.2a1" diff --git a/openeo_driver/datacube.py b/openeo_driver/datacube.py index 110d592c..7fad3348 100644 --- a/openeo_driver/datacube.py +++ b/openeo_driver/datacube.py @@ -1,3 +1,4 @@ +from __future__ import annotations import abc import inspect import io @@ -125,9 +126,23 @@ def chunk_polygon( env: EvalEnv, context: Optional[dict] = None, ) -> "DriverDataCube": - # TODO: rename/update `chunk_polygon` to `apply_polygon` (https://github.com/Open-EO/openeo-processes/pull/298) + # TODO #229 drop this deprecated API once unused (replaced by `apply_polygon`) (https://github.com/Open-EO/openeo-processes/pull/298) self._not_implemented() + def apply_polygon( + self, + *, + # TODO #229 better type for `polygons` arg: should be vector cube or feature collection like construct + polygons: shapely.geometry.base.BaseGeometry, + process: dict, + mask_value: Optional[float] = None, + context: Optional[dict] = None, + env: EvalEnv, + ) -> DriverDataCube: + # TODO #229 remove this temporary adapter to deprecated `chunk_polygon` method. + return self.chunk_polygon(reducer=process, chunks=polygons, mask_value=mask_value, env=env, context=context) + # self._not_implemented() + def add_dimension(self, name: str, label, type: str = "other") -> 'DriverDataCube': self._not_implemented() diff --git a/tests/data/pg/1.0/apply_polygon.json b/tests/data/pg/1.0/apply_polygon.json index d2b3a110..c98324cd 100644 --- a/tests/data/pg/1.0/apply_polygon.json +++ b/tests/data/pg/1.0/apply_polygon.json @@ -7,7 +7,7 @@ "process_id": "apply_polygon", "arguments": { "data": {"from_node": "collection"}, - "chunks": { + "polygons": { "type": "FeatureCollection", "features": [ {