From f0e2b328753a67a0635973bbb122c61d5776afd8 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Thu, 25 Jun 2026 11:24:22 +0200 Subject: [PATCH 1/6] Only do geometries.geoms when the attribute is present. https://github.com/Open-EO/openeo-python-driver/issues/519 --- openeo_driver/save_result.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openeo_driver/save_result.py b/openeo_driver/save_result.py index cfab273b..ba2a4d9a 100644 --- a/openeo_driver/save_result.py +++ b/openeo_driver/save_result.py @@ -446,7 +446,8 @@ def _create_point_timeseries_xarray(self, feature_ids, timestamps, lats, lons, a def to_netcdf(self, destination: Optional[str] = None) -> str: def features_ids_from_index(geometries): - return ["feature_%d" % i for i in range(len(geometries.geoms))] + geoms = geometries.geoms if hasattr(geometries, "geoms") else geometries + return ["feature_%d" % i for i in range(len(geoms))] if isinstance(self._regions, GeometryCollection): points = [r.representative_point() for r in self._regions.geoms] From 3c79546016d6c65a1bedcf5f4958c618ffa21f28 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Thu, 25 Jun 2026 13:26:05 +0200 Subject: [PATCH 2/6] Add test_aggregate_polygon_result_vector_cube_no_ids_to_netcdf --- tests/test_save_result_netcdf.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_save_result_netcdf.py b/tests/test_save_result_netcdf.py index 9c19847f..8bc7a73b 100644 --- a/tests/test_save_result_netcdf.py +++ b/tests/test_save_result_netcdf.py @@ -1,9 +1,11 @@ from pathlib import Path +import geopandas as gpd import numpy as np import pytest import xarray as xr from numpy.testing import assert_array_equal, assert_allclose +from openeo_driver.datacube import DriverVectorCube from openeo_driver.save_result import AggregatePolygonResult, AggregatePolygonResultCSV from shapely.geometry import GeometryCollection, Polygon @@ -150,3 +152,27 @@ def test_aggregate_polygon_result_CSV(tmp_path): timeseries_ds.red.sel( t='2017-09-05') assert_allclose( timeseries_ds.red.sel(feature=2).sel( t='2017-09-06').data,4645.719597) + + +def test_aggregate_polygon_result_vector_cube_no_ids_to_netcdf(tmp_path): + """Test for https://github.com/Open-EO/openeo-python-driver/issues/519""" + timeseries = { + "2020-06-01T00:00:00Z": [[77.2], [77.15]], + "2020-06-02T00:00:00Z": [[77.21], [77.16]], + } + # GeoDataFrame without an 'id' column so that get_ids() returns None + gdf = gpd.GeoDataFrame( + geometry=[ + Polygon([(77.11, 28.56), (77.29, 28.56), (77.29, 28.69), (77.11, 28.69)]), + Polygon([(77.11, 28.56), (77.20, 28.56), (77.20, 28.62), (77.11, 28.62)]), + ] + ) + regions = DriverVectorCube(gdf) + + result = AggregatePolygonResult(timeseries, regions=regions) + filename = result.to_netcdf(tmp_path / "timeseries_vector_cube_no_ids.nc") + + timeseries_ds = xr.open_dataset(filename) + # Feature IDs should be auto-generated as "feature_0", "feature_1", ... + assert list(timeseries_ds.coords["feature_names"].data) == ["feature_0", "feature_1"] + assert_array_equal(timeseries_ds.band_0.sel(feature=0).data, [77.2, 77.21]) From b1cdcfbd94545495dae2128c1cd525f3f38ae0c4 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Mon, 29 Jun 2026 12:36:16 +0200 Subject: [PATCH 3/6] Stricter type checking. https://github.com/Open-EO/openeo-python-driver/issues/519 --- openeo_driver/save_result.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openeo_driver/save_result.py b/openeo_driver/save_result.py index ba2a4d9a..e8aea1a0 100644 --- a/openeo_driver/save_result.py +++ b/openeo_driver/save_result.py @@ -20,6 +20,7 @@ from deprecated import deprecated from flask import send_from_directory, jsonify, Response import shapely.geometry +from geopandas import GeoSeries from shapely.geometry import GeometryCollection from shapely.geometry.base import BaseGeometry import geopandas as gpd @@ -314,8 +315,7 @@ def __init__( """ super().__init__(data=timeseries) if not isinstance(regions, (GeometryCollection, DriverVectorCube)): - # TODO: raise exception instead of warning? - _log.warning( + raise Exception( f"AggregatePolygonResult: GeometryCollection or DriverVectorCube expected but got {type(regions)}" ) self._regions = regions @@ -445,18 +445,26 @@ def _create_point_timeseries_xarray(self, feature_ids, timestamps, lats, lons, a return the_array def to_netcdf(self, destination: Optional[str] = None) -> str: - def features_ids_from_index(geometries): + def features_ids_from_index(geometries: Union[GeometryCollection, GeoSeries]): + if not isinstance(geometries, (GeometryCollection, GeoSeries)): + raise Exception( + f"AggregatePolygonResult: GeometryCollection or GeoSeries expected but got {type(geometries)}" + ) geoms = geometries.geoms if hasattr(geometries, "geoms") else geometries return ["feature_%d" % i for i in range(len(geoms))] if isinstance(self._regions, GeometryCollection): points = [r.representative_point() for r in self._regions.geoms] feature_ids = features_ids_from_index(self._regions) - else: + elif isinstance(self._regions, DriverVectorCube): points = [r.representative_point() for r in self._regions.get_geometries()] feature_ids = self._regions.get_ids() feature_ids = (list(feature_ids) if feature_ids is not None else features_ids_from_index(self._regions.get_geometries())) + else: + raise Exception( + f"AggregatePolygonResult: GeometryCollection or DriverVectorCube expected but got {type(self._regions)}" + ) lats = [p.y for p in points] lons = [p.x for p in points] From c7ed687a36d2244161b28c0906519a2bc989850f Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Mon, 29 Jun 2026 13:09:42 +0200 Subject: [PATCH 4/6] Use typing instead of hasattr(). https://github.com/Open-EO/openeo-python-driver/issues/519 --- openeo_driver/save_result.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openeo_driver/save_result.py b/openeo_driver/save_result.py index e8aea1a0..1cdcca11 100644 --- a/openeo_driver/save_result.py +++ b/openeo_driver/save_result.py @@ -446,12 +446,16 @@ def _create_point_timeseries_xarray(self, feature_ids, timestamps, lats, lons, a def to_netcdf(self, destination: Optional[str] = None) -> str: def features_ids_from_index(geometries: Union[GeometryCollection, GeoSeries]): - if not isinstance(geometries, (GeometryCollection, GeoSeries)): + if isinstance(geometries, GeometryCollection): + geoms_length = len(geometries.geoms) + elif isinstance(geometries, GeoSeries): + geoms_length = len(geometries) + else: raise Exception( f"AggregatePolygonResult: GeometryCollection or GeoSeries expected but got {type(geometries)}" ) - geoms = geometries.geoms if hasattr(geometries, "geoms") else geometries - return ["feature_%d" % i for i in range(len(geoms))] + + return ["feature_%d" % i for i in range(geoms_length)] if isinstance(self._regions, GeometryCollection): points = [r.representative_point() for r in self._regions.geoms] From 0ae9d1b2cddfac60a6b41b5ec3a717d581fc7462 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Mon, 29 Jun 2026 13:15:07 +0200 Subject: [PATCH 5/6] Use suggested snippet from Stefaan. https://github.com/Open-EO/openeo-python-driver/issues/519 --- openeo_driver/save_result.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/openeo_driver/save_result.py b/openeo_driver/save_result.py index 1cdcca11..953c380d 100644 --- a/openeo_driver/save_result.py +++ b/openeo_driver/save_result.py @@ -20,7 +20,6 @@ from deprecated import deprecated from flask import send_from_directory, jsonify, Response import shapely.geometry -from geopandas import GeoSeries from shapely.geometry import GeometryCollection from shapely.geometry.base import BaseGeometry import geopandas as gpd @@ -445,26 +444,17 @@ def _create_point_timeseries_xarray(self, feature_ids, timestamps, lats, lons, a return the_array def to_netcdf(self, destination: Optional[str] = None) -> str: - def features_ids_from_index(geometries: Union[GeometryCollection, GeoSeries]): - if isinstance(geometries, GeometryCollection): - geoms_length = len(geometries.geoms) - elif isinstance(geometries, GeoSeries): - geoms_length = len(geometries) - else: - raise Exception( - f"AggregatePolygonResult: GeometryCollection or GeoSeries expected but got {type(geometries)}" - ) - - return ["feature_%d" % i for i in range(geoms_length)] - if isinstance(self._regions, GeometryCollection): points = [r.representative_point() for r in self._regions.geoms] - feature_ids = features_ids_from_index(self._regions) + feature_ids = ["feature_%d" % i for i in range(len(self._regions.geoms))] elif isinstance(self._regions, DriverVectorCube): points = [r.representative_point() for r in self._regions.get_geometries()] feature_ids = self._regions.get_ids() - feature_ids = (list(feature_ids) if feature_ids is not None - else features_ids_from_index(self._regions.get_geometries())) + feature_ids = ( + list(feature_ids) + if feature_ids is not None + else ["feature_%d" % i for i in range(self._regions.geometry_count())] + ) else: raise Exception( f"AggregatePolygonResult: GeometryCollection or DriverVectorCube expected but got {type(self._regions)}" From f987b2fe55e1cae66289a6e4a4c881b8003ad571 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Mon, 29 Jun 2026 13:16:45 +0200 Subject: [PATCH 6/6] ValueError --- openeo_driver/save_result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openeo_driver/save_result.py b/openeo_driver/save_result.py index 953c380d..bd5a1719 100644 --- a/openeo_driver/save_result.py +++ b/openeo_driver/save_result.py @@ -314,7 +314,7 @@ def __init__( """ super().__init__(data=timeseries) if not isinstance(regions, (GeometryCollection, DriverVectorCube)): - raise Exception( + raise ValueError( f"AggregatePolygonResult: GeometryCollection or DriverVectorCube expected but got {type(regions)}" ) self._regions = regions @@ -456,7 +456,7 @@ def to_netcdf(self, destination: Optional[str] = None) -> str: else ["feature_%d" % i for i in range(self._regions.geometry_count())] ) else: - raise Exception( + raise ValueError( f"AggregatePolygonResult: GeometryCollection or DriverVectorCube expected but got {type(self._regions)}" )