Skip to content

Commit 34cad31

Browse files
committed
fixup! fixup! Issue #114/#141 convert inline GeoJSON in aggregate_spatial to VectorCube
1 parent 4e02771 commit 34cad31

File tree

6 files changed

+152
-54
lines changed

6 files changed

+152
-54
lines changed

openeo_driver/datacube.py

+40
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ def write_assets(
243243
format_info = IOFORMATS.get(format)
244244
# TODO: check if format can be used for vector data?
245245
path = directory / f"vectorcube.{format_info.extension}"
246+
247+
if format_info.format == "JSON":
248+
# TODO: eliminate this legacy format?
249+
return self._write_legacy_aggregate_polygon_result_json(directory=directory)
250+
246251
self._as_geopandas_df().to_file(path, driver=format_info.fiona_driver)
247252

248253
if not format_info.multi_file:
@@ -275,6 +280,41 @@ def write_assets(
275280
def to_multipolygon(self) -> shapely.geometry.MultiPolygon:
276281
return shapely.ops.unary_union(self._geometries.geometry)
277282

283+
def _write_legacy_aggregate_polygon_result_json(
284+
self, directory: Path
285+
) -> Dict[str, StacAsset]:
286+
"""Export to legacy AggregatePolygonResult JSON format"""
287+
# TODO: eliminate this legacy, non-standard format?
288+
from openeo_driver.save_result import AggregatePolygonResult, JSONResult
289+
290+
def write_spatiotemporal(cube: xarray.DataArray) -> Dict[str, StacAsset]:
291+
"""Export to legacy AggregatePolygonResult JSON format"""
292+
cube = cube.transpose("t", self.DIM_GEOMETRIES, "bands")
293+
timeseries = {
294+
t.item(): t_slice.values.tolist()
295+
for t, t_slice in zip(cube.coords["t"], cube)
296+
}
297+
result = AggregatePolygonResult(timeseries=timeseries, regions=self)
298+
return result.write_assets(directory=directory / "ignored")
299+
300+
def write_spatial(cube: xarray.DataArray) -> Dict[str, StacAsset]:
301+
cube = cube.transpose(self.DIM_GEOMETRIES, "bands")
302+
result = JSONResult(data=cube.values.tolist())
303+
return result.write_assets(directory / "ignored")
304+
305+
cube = self._cube
306+
# TODO: more flexible temporal/band dimension detection?
307+
if cube.dims == (self.DIM_GEOMETRIES, "t"):
308+
return write_spatiotemporal(cube.expand_dims({"bands": ["band"]}, axis=-1))
309+
elif cube.dims == (self.DIM_GEOMETRIES, "t", "bands"):
310+
return write_spatiotemporal(cube)
311+
elif cube.dims == (self.DIM_GEOMETRIES, "bands"):
312+
return write_spatial(cube)
313+
else:
314+
raise ValueError(
315+
f"Unsupported cube configuration {cube.dims} for _write_legacy_aggregate_polygon_result_json"
316+
)
317+
278318
def get_bounding_box(self) -> Tuple[float, float, float, float]:
279319
return tuple(self._geometries.total_bounds)
280320

openeo_driver/dummy/dummy_backend.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,15 @@ def assert_polygon_sequence(geometries: Union[Sequence, BaseMultipartGeometry])
227227
dims += (self.metadata.band_dimension.name,)
228228
coords[self.metadata.band_dimension.name] = self.metadata.band_names
229229
shape = [len(coords[d]) for d in dims]
230-
data = numpy.arange(numpy.prod(shape)).reshape(shape)
231-
cube = xarray.DataArray(data=data, dims=dims, coords=coords, name="aggregate_spatial")
230+
data = numpy.arange(numpy.prod(shape), dtype="float")
231+
data[0] = 2.345
232+
data[1] = float("nan")
233+
cube = xarray.DataArray(
234+
data=data.reshape(shape),
235+
dims=dims,
236+
coords=coords,
237+
name="aggregate_spatial",
238+
)
232239
return geometries.with_cube(cube=cube, flatten_prefix="agg")
233240
elif isinstance(geometries, str):
234241
geometries = [geometry for geometry in DelayedVector(geometries).geometries]

openeo_driver/save_result.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def __init__(self, data, format: str = "json", options: dict = None):
161161
super().__init__(format=format, options=options)
162162
self.data = data
163163

164-
def write_assets(self, path:str) -> Dict[str, StacAsset]:
164+
def write_assets(self, path: Union[str, Path]) -> Dict[str, StacAsset]:
165165
"""
166166
Save generated assets into a directory, return asset metadata.
167167
TODO: can an asset also be a full STAC item? In principle, one openEO job can either generate a full STAC collection, or one STAC item with multiple assets...
@@ -220,7 +220,7 @@ def get_data(self):
220220
# By default, keep original (proprietary) result format
221221
return self.data
222222

223-
def write_assets(self, directory: str) -> Dict[str, StacAsset]:
223+
def write_assets(self, directory: Union[str, Path]) -> Dict[str, StacAsset]:
224224
"""
225225
Save generated assets into a directory, return asset metadata.
226226
TODO: can an asset also be a full STAC item? In principle, one openEO job can either generate a full STAC collection, or one STAC item with multiple assets...

tests/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def udp_registry(backend_implementation) -> UserDefinedProcesses:
4242
def flask_app(backend_implementation) -> flask.Flask:
4343
app = build_app(
4444
backend_implementation=backend_implementation,
45-
# error_handling=False
45+
# error_handling=False,
4646
)
4747
app.config.from_mapping(TEST_APP_CONFIG)
4848
return app

tests/data/pg/1.0/no_nested_json_result.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"result": true,
6868
"process_id": "save_result",
6969
"arguments": {
70-
"format": "GTIFF",
70+
"format": "GeoJSON",
7171
"data": {
7272
"from_node": "aggregatespatial1"
7373
},

tests/test_views_execute.py

+99-48
Original file line numberDiff line numberDiff line change
@@ -734,17 +734,33 @@ def test_aggregate_spatial(api):
734734
"2015-07-06T00:00:00Z": [[2.345]],
735735
"2015-08-22T00:00:00Z": [[None]]
736736
}
737-
params = dummy_backend.last_load_collection_call('S2_FAPAR_CLOUDCOVER')
738-
assert params["spatial_extent"] == {"west": 7.02, "south": 51.29, "east": 7.65, "north": 51.75, "crs": 'EPSG:4326'}
739-
assert params["aggregate_spatial_geometries"] == shapely.geometry.shape({
740-
"type": "Polygon",
741-
"coordinates": [[[7.02, 51.75], [7.65, 51.74], [7.65, 51.29], [7.04, 51.31], [7.02, 51.75]]]
742-
})
737+
params = dummy_backend.last_load_collection_call("S2_FAPAR_CLOUDCOVER")
738+
assert params["spatial_extent"] == {
739+
"west": 7.02,
740+
"south": 51.29,
741+
"east": 7.65,
742+
"north": 51.75,
743+
"crs": "EPSG:4326",
744+
}
745+
assert params["aggregate_spatial_geometries"] == DriverVectorCube.from_geojson(
746+
{
747+
"type": "Polygon",
748+
"coordinates": [
749+
[
750+
[7.02, 51.75],
751+
[7.65, 51.74],
752+
[7.65, 51.29],
753+
[7.04, 51.31],
754+
[7.02, 51.75],
755+
]
756+
],
757+
}
758+
)
743759

744760

745761
def test_execute_aggregate_spatial_spatial_cube(api100):
746762
resp = api100.check_result("aggregate_spatial_spatial_cube.json")
747-
assert resp.json == [[100.0, 100.1], [101.0, 101.1]]
763+
assert resp.json == [[2.345, None], [2.0, 3.0]]
748764

749765

750766
@pytest.mark.parametrize(["geometries", "expected"], [
@@ -789,37 +805,51 @@ def test_aggregate_spatial_vector_cube_basic(api100, feature_collection_test_pat
789805
assert params["spatial_extent"] == {"west": 1, "south": 1, "east": 5, "north": 4, "crs": "EPSG:4326"}
790806
assert isinstance(params["aggregate_spatial_geometries"], DriverVectorCube)
791807

792-
assert res.json == DictSubSet({
793-
"type": "FeatureCollection",
794-
"features": [
795-
DictSubSet({
796-
"type": "Feature",
797-
"geometry": {"type": "Polygon", "coordinates": [[[1, 1], [3, 1], [2, 3], [1, 1]]]},
798-
"properties": {
799-
"id": "first", "pop": 1234,
800-
"agg~2015-07-06T00:00:00Z~B02": 0,
801-
"agg~2015-07-06T00:00:00Z~B03": 1,
802-
"agg~2015-07-06T00:00:00Z~B04": 2,
803-
"agg~2015-08-22T00:00:00Z~B02": 3,
804-
"agg~2015-08-22T00:00:00Z~B03": 4,
805-
"agg~2015-08-22T00:00:00Z~B04": 5,
806-
},
807-
}),
808-
DictSubSet({
809-
"type": "Feature",
810-
"geometry": {"type": "Polygon", "coordinates": [[[4, 2], [5, 4], [3, 4], [4, 2]]]},
811-
"properties": {
812-
"id": "second", "pop": 5678,
813-
"agg~2015-07-06T00:00:00Z~B02": 6,
814-
"agg~2015-07-06T00:00:00Z~B03": 7,
815-
"agg~2015-07-06T00:00:00Z~B04": 8,
816-
"agg~2015-08-22T00:00:00Z~B02": 9,
817-
"agg~2015-08-22T00:00:00Z~B03": 10,
818-
"agg~2015-08-22T00:00:00Z~B04": 11,
819-
},
820-
}),
821-
]
822-
})
808+
assert res.json == DictSubSet(
809+
{
810+
"type": "FeatureCollection",
811+
"features": [
812+
DictSubSet(
813+
{
814+
"type": "Feature",
815+
"geometry": {
816+
"type": "Polygon",
817+
"coordinates": [[[1, 1], [3, 1], [2, 3], [1, 1]]],
818+
},
819+
"properties": {
820+
"id": "first",
821+
"pop": 1234,
822+
"agg~2015-07-06T00:00:00Z~B02": 2.345,
823+
"agg~2015-07-06T00:00:00Z~B03": None,
824+
"agg~2015-07-06T00:00:00Z~B04": 2.0,
825+
"agg~2015-08-22T00:00:00Z~B02": 3.0,
826+
"agg~2015-08-22T00:00:00Z~B03": 4.0,
827+
"agg~2015-08-22T00:00:00Z~B04": 5.0,
828+
},
829+
}
830+
),
831+
DictSubSet(
832+
{
833+
"type": "Feature",
834+
"geometry": {
835+
"type": "Polygon",
836+
"coordinates": [[[4, 2], [5, 4], [3, 4], [4, 2]]],
837+
},
838+
"properties": {
839+
"id": "second",
840+
"pop": 5678,
841+
"agg~2015-07-06T00:00:00Z~B02": 6.0,
842+
"agg~2015-07-06T00:00:00Z~B03": 7.0,
843+
"agg~2015-07-06T00:00:00Z~B04": 8.0,
844+
"agg~2015-08-22T00:00:00Z~B02": 9.0,
845+
"agg~2015-08-22T00:00:00Z~B03": 10.0,
846+
"agg~2015-08-22T00:00:00Z~B04": 11.0,
847+
},
848+
}
849+
),
850+
],
851+
}
852+
)
823853

824854

825855
@pytest.mark.parametrize(["info", "preprocess_pg", "aggregate_data", "p1_properties", "p2_properties"], [
@@ -828,9 +858,14 @@ def test_aggregate_spatial_vector_cube_basic(api100, feature_collection_test_pat
828858
{},
829859
"lc",
830860
{
831-
"id": "first", "pop": 1234,
832-
"agg~2015-07-06T00:00:00Z~B02": 0, "agg~2015-07-06T00:00:00Z~B03": 1, "agg~2015-07-06T00:00:00Z~B04": 2,
833-
"agg~2015-08-22T00:00:00Z~B02": 3, "agg~2015-08-22T00:00:00Z~B03": 4, "agg~2015-08-22T00:00:00Z~B04": 5,
861+
"id": "first",
862+
"pop": 1234,
863+
"agg~2015-07-06T00:00:00Z~B02": 2.345,
864+
"agg~2015-07-06T00:00:00Z~B03": None,
865+
"agg~2015-07-06T00:00:00Z~B04": 2,
866+
"agg~2015-08-22T00:00:00Z~B02": 3,
867+
"agg~2015-08-22T00:00:00Z~B03": 4,
868+
"agg~2015-08-22T00:00:00Z~B04": 5,
834869
},
835870
{
836871
"id": "second", "pop": 5678,
@@ -850,7 +885,13 @@ def test_aggregate_spatial_vector_cube_basic(api100, feature_collection_test_pat
850885
}},
851886
},
852887
"r",
853-
{"id": "first", "pop": 1234, "agg~B02": 0, "agg~B03": 1, "agg~B04": 2},
888+
{
889+
"id": "first",
890+
"pop": 1234,
891+
"agg~B02": 2.345,
892+
"agg~B03": None,
893+
"agg~B04": 2,
894+
},
854895
{"id": "second", "pop": 5678, "agg~B02": 3, "agg~B03": 4, "agg~B04": 5},
855896
),
856897
(
@@ -865,10 +906,20 @@ def test_aggregate_spatial_vector_cube_basic(api100, feature_collection_test_pat
865906
}}
866907
},
867908
"r",
868-
{"id": "first", "pop": 1234, "agg~2015-07-06T00:00:00Z": 0, "agg~2015-08-22T00:00:00Z": 1},
869-
{"id": "second", "pop": 5678, "agg~2015-07-06T00:00:00Z": 2, "agg~2015-08-22T00:00:00Z": 3},
870-
),
871-
(
909+
{
910+
"id": "first",
911+
"pop": 1234,
912+
"agg~2015-07-06T00:00:00Z": 2.345,
913+
"agg~2015-08-22T00:00:00Z": None,
914+
},
915+
{
916+
"id": "second",
917+
"pop": 5678,
918+
"agg~2015-07-06T00:00:00Z": 2,
919+
"agg~2015-08-22T00:00:00Z": 3,
920+
},
921+
),
922+
(
872923
"no-time-nor-bands",
873924
{
874925
"r1": {"process_id": "reduce_dimension", "arguments": {
@@ -887,8 +938,8 @@ def test_aggregate_spatial_vector_cube_basic(api100, feature_collection_test_pat
887938
}},
888939
},
889940
"r2",
890-
{"id": "first", "pop": 1234, "agg": 0},
891-
{"id": "second", "pop": 5678, "agg": 1},
941+
{"id": "first", "pop": 1234, "agg": 2.345},
942+
{"id": "second", "pop": 5678, "agg": None},
892943
),
893944
])
894945
def test_aggregate_spatial_vector_cube_dimensions(

0 commit comments

Comments
 (0)