Skip to content

Commit 3445dec

Browse files
Issue #288 use DriverVectorCube in apply_polygon
1 parent 2157e8e commit 3445dec

File tree

4 files changed

+37
-71
lines changed

4 files changed

+37
-71
lines changed

openeo_driver/ProcessGraphDeserializer.py

Lines changed: 30 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,34 @@ def reduce_dimension(args: ProcessArgs, env: EvalEnv) -> DriverDataCube:
805805
return data_cube.reduce_dimension(reducer=reduce_pg, dimension=dimension, context=context, env=env)
806806

807807

808+
def _apply_polygon(
809+
data_cube: DriverDataCube, process: dict, polygons, mask_value: Union[int, float], context, env: EvalEnv
810+
):
811+
polygon: DriverVectorCube = None
812+
if isinstance(polygons, DelayedVector):
813+
polygons = list(polygons.geometries)
814+
for p in polygons:
815+
if not isinstance(p, shapely.geometry.Polygon):
816+
reason = "{m!s} is not a polygon.".format(m=p)
817+
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
818+
polygon = DriverVectorCube.from_geometry(polygons)
819+
elif isinstance(polygons, DriverVectorCube):
820+
polygon = polygons
821+
elif isinstance(polygons, shapely.geometry.base.BaseGeometry):
822+
polygon = DriverVectorCube.from_geometry(polygons)
823+
elif isinstance(polygons, dict):
824+
polygon = DriverVectorCube.from_geojson(polygons)
825+
else:
826+
reason = f"unsupported type: {type(polygons).__name__}"
827+
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
828+
829+
if polygon.get_area() == 0:
830+
reason = "Polygon {m!s} has an area of {a!r}".format(m=polygon, a=polygon.get_area())
831+
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
832+
833+
return data_cube.apply_polygon(polygons=polygon, process=process, mask_value=mask_value, context=context, env=env)
834+
835+
808836
@process_registry_100.add_function(
809837
spec=read_spec("openeo-processes/experimental/chunk_polygon.json"), name="chunk_polygon"
810838
)
@@ -816,34 +844,7 @@ def chunk_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube:
816844
chunks = args.get_required("chunks")
817845
mask_value = args.get_optional("mask_value", expected_type=(int, float), default=None)
818846
context = args.get_optional("context", default=None)
819-
820-
# Chunks parameter check.
821-
# TODO #114 EP-3981 normalize first to vector cube and simplify logic
822-
if isinstance(chunks, DelayedVector):
823-
polygons = list(chunks.geometries)
824-
for p in polygons:
825-
if not isinstance(p, shapely.geometry.Polygon):
826-
reason = "{m!s} is not a polygon.".format(m=p)
827-
raise ProcessParameterInvalidException(parameter='chunks', process='chunk_polygon', reason=reason)
828-
polygon = MultiPolygon(polygons)
829-
elif isinstance(chunks, shapely.geometry.base.BaseGeometry):
830-
polygon = MultiPolygon(chunks)
831-
elif isinstance(chunks, dict):
832-
polygon = geojson_to_multipolygon(chunks)
833-
if isinstance(polygon, shapely.geometry.Polygon):
834-
polygon = MultiPolygon([polygon])
835-
elif isinstance(chunks, str):
836-
# Delayed vector is not supported yet.
837-
reason = "Polygon of type string is not yet supported."
838-
raise ProcessParameterInvalidException(parameter='chunks', process='chunk_polygon', reason=reason)
839-
else:
840-
reason = "Polygon type is not supported."
841-
raise ProcessParameterInvalidException(parameter='chunks', process='chunk_polygon', reason=reason)
842-
if polygon.area == 0:
843-
reason = "Polygon {m!s} has an area of {a!r}".format(m=polygon, a=polygon.area)
844-
raise ProcessParameterInvalidException(parameter='chunks', process='chunk_polygon', reason=reason)
845-
846-
return data_cube.chunk_polygon(reducer=reduce_pg, chunks=polygon, mask_value=mask_value, context=context, env=env)
847+
return _apply_polygon(data_cube, reduce_pg, chunks, mask_value, context, env)
847848

848849

849850
@process_registry_100.add_function(spec=read_spec("openeo-processes/2.x/proposals/apply_polygon.json"))
@@ -854,36 +855,7 @@ def apply_polygon(args: ProcessArgs, env: EvalEnv) -> DriverDataCube:
854855
polygons = args.get_required("polygons")
855856
mask_value = args.get_optional("mask_value", expected_type=(int, float), default=None)
856857
context = args.get_optional("context", default=None)
857-
858-
# TODO #114 EP-3981 normalize first to vector cube and simplify logic
859-
# TODO #288: this logic (copied from original chunk_polygon implementation) coerces the input polygons
860-
# to a single MultiPolygon of pure (non-multi) polygons, which is conceptually wrong.
861-
# Instead it should normalize to a feature collection or vector cube.
862-
if isinstance(polygons, DelayedVector):
863-
polygons = list(polygons.geometries)
864-
for p in polygons:
865-
if not isinstance(p, shapely.geometry.Polygon):
866-
reason = "{m!s} is not a polygon.".format(m=p)
867-
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
868-
polygon = MultiPolygon(polygons)
869-
elif isinstance(polygons, DriverVectorCube):
870-
# TODO #288: I know it's wrong to coerce to MultiPolygon here, but we stick to this ill-defined API for now.
871-
polygon = polygons.to_multipolygon()
872-
elif isinstance(polygons, shapely.geometry.base.BaseGeometry):
873-
polygon = MultiPolygon(polygons)
874-
elif isinstance(polygons, dict):
875-
polygon = geojson_to_multipolygon(polygons)
876-
if isinstance(polygon, shapely.geometry.Polygon):
877-
polygon = MultiPolygon([polygon])
878-
else:
879-
reason = f"unsupported type: {type(polygons).__name__}"
880-
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
881-
882-
if polygon.area == 0:
883-
reason = "Polygon {m!s} has an area of {a!r}".format(m=polygon, a=polygon.area)
884-
raise ProcessParameterInvalidException(parameter="polygons", process="apply_polygon", reason=reason)
885-
886-
return data_cube.apply_polygon(polygons=polygon, process=process, mask_value=mask_value, context=context, env=env)
858+
return _apply_polygon(data_cube, process, polygons, mask_value, context, env)
887859

888860

889861
@process_registry_100.add_function(spec=read_spec("openeo-processes/experimental/fit_class_random_forest.json"))

openeo_driver/datacube.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def chunk_polygon(
124124
reducer: dict,
125125
# TODO #288:` chunks` should be an explicit collection of geometries (e.g a FeatureCollection, vector cube base class or an iterable of geometries)
126126
# Note that subclass implementations even wrongly retype this to `MultiPolygon`.
127-
chunks: Union[shapely.geometry.base.BaseGeometry],
127+
chunks: DriverVectorCube,
128128
mask_value: Union[float, None],
129129
env: EvalEnv,
130130
context: Optional[dict] = None,
@@ -137,7 +137,7 @@ def apply_polygon(
137137
*,
138138
# TODO #229/#288 better type for `polygons` arg: should be vector cube or something alike
139139
# TODO #288: use `geometries` argument instead of confusing `polygons` argument (https://github.com/Open-EO/openeo-processes/issues/511)
140-
polygons: shapely.geometry.base.BaseGeometry,
140+
polygons: DriverVectorCube,
141141
process: dict,
142142
mask_value: Optional[float] = None,
143143
context: Optional[dict] = None,

openeo_driver/dry_run.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -650,18 +650,12 @@ def ndvi(self, nir: str = "nir", red: str = "red", target_band: str = None) -> '
650650
return self
651651

652652
def chunk_polygon(
653-
# TODO #288: `chunks`: MultiPolygon should not be abused as collection of separate geometries.
654-
self, reducer, chunks: MultiPolygon, mask_value: float, env: EvalEnv, context: Optional[dict] = None
653+
self, reducer, chunks: DriverVectorCube, mask_value: float, env: EvalEnv, context: Optional[dict] = None
655654
) -> "DryRunDataCube":
656655
# TODO #229: rename/update `chunk_polygon` to `apply_polygon` (https://github.com/Open-EO/openeo-processes/pull/298)
657-
if isinstance(chunks, Polygon):
658-
polygons = [chunks]
659-
elif isinstance(chunks, MultiPolygon):
660-
polygons: List[Polygon] = chunks.geoms
661-
else:
656+
if not isinstance(chunks, DriverVectorCube):
662657
raise ValueError(f"Invalid type for `chunks`: {type(chunks)}")
663-
# TODO #71 #114 Deprecate/avoid usage of GeometryCollection
664-
geometries, bbox = self._normalize_geometry(GeometryCollection(polygons))
658+
geometries, bbox = self._normalize_geometry(chunks)
665659
cube = self.filter_bbox(**bbox, operation="weak_spatial_extent")
666660
return cube._process("chunk_polygon", arguments={"geometries": geometries})
667661

tests/test_views_execute.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3730,8 +3730,8 @@ def test_apply_polygon_with_vector_cube(api, tmp_path):
37303730
assert dummy.apply_polygon.call_count == 1
37313731
polygons = dummy.apply_polygon.call_args.kwargs["polygons"]
37323732
# TODO #288 instead of MultPolygon, this should actually be a vector cube, feature collection or something equivalent
3733-
assert isinstance(polygons, shapely.geometry.MultiPolygon)
3734-
assert polygons.bounds == (4.45, 51.1, 4.52, 51.2)
3733+
assert isinstance(polygons, DriverVectorCube)
3734+
assert polygons.get_bounding_box() == (4.45, 51.1, 4.52, 51.2)
37353735

37363736

37373737
def test_fit_class_random_forest(api):

0 commit comments

Comments
 (0)