Skip to content

Commit c51cea4

Browse files
committed
Issue #114/#211/#197 tests for load_geojson and its properties arg
1 parent 5eafa44 commit c51cea4

File tree

6 files changed

+359
-82
lines changed

6 files changed

+359
-82
lines changed

openeo_driver/ProcessGraphDeserializer.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1581,17 +1581,23 @@ def load_uploaded_files(args: dict, env: EvalEnv) -> Union[DriverVectorCube,Driv
15811581
.returns("vector-cube", schema={"type": "object", "subtype": "vector-cube"})
15821582
)
15831583
def to_vector_cube(args: Dict, env: EvalEnv):
1584-
# TODO: standardization of something like this? https://github.com/Open-EO/openeo-processes/issues/346
1584+
_log.warning("Experimental process `to_vector_cube` is deprecated, use `load_geojson` instead")
1585+
# TODO: remove this experimental/deprecated process
15851586
data = extract_arg(args, "data", process_id="to_vector_cube")
15861587
if isinstance(data, dict) and data.get("type") in {"Polygon", "MultiPolygon", "Feature", "FeatureCollection"}:
15871588
return env.backend_implementation.vector_cube_cls.from_geojson(data)
1588-
# TODO: support more inputs: string with geojson, string with WKT, list of WKT, string with URL to GeoJSON, ...
15891589
raise FeatureUnsupportedException(f"Converting {type(data)} to vector cube is not supported")
15901590

15911591

15921592
@process_registry_100.add_function(spec=read_spec("openeo-processes/2.x/proposals/load_geojson.json"))
15931593
def load_geojson(args: ProcessArgs, env: EvalEnv) -> DriverVectorCube:
1594-
data = args.get_required("data", validator=ProcessArgs.validator_geojson_dict())
1594+
data = args.get_required(
1595+
"data",
1596+
validator=ProcessArgs.validator_geojson_dict(
1597+
# TODO: also allow LineString and MultiLineString?
1598+
allowed_types=["Point", "MultiPoint", "Polygon", "MultiPolygon", "Feature", "FeatureCollection"]
1599+
),
1600+
)
15951601
properties = args.get_optional("properties", default=[], expected_type=(list, tuple))
15961602
vector_cube = env.backend_implementation.vector_cube_cls.from_geojson(data, columns_for_cube=properties)
15971603
return vector_cube

openeo_driver/datacube.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import geopandas as gpd
1010
import numpy
11+
import pandas
1112
import pyproj
1213
import shapely.geometry
1314
import shapely.geometry.base
@@ -284,14 +285,21 @@ def from_geodataframe(
284285
elif columns_for_cube == cls.COLUMN_SELECTION_ALL:
285286
columns_for_cube = available_columns
286287
elif isinstance(columns_for_cube, list):
287-
# TODO #114 limit to subset with available columns (and automatically fill in missing columns with nodata)?
288288
columns_for_cube = columns_for_cube
289289
else:
290290
raise ValueError(columns_for_cube)
291291
assert isinstance(columns_for_cube, list)
292292

293293
if columns_for_cube:
294-
cube_df = data[columns_for_cube]
294+
existing = [c for c in columns_for_cube if c in available_columns]
295+
to_add = [c for c in columns_for_cube if c not in available_columns]
296+
if existing:
297+
cube_df = data[existing]
298+
if to_add:
299+
cube_df.loc[:, to_add] = numpy.nan
300+
else:
301+
cube_df = pandas.DataFrame(index=data.index, columns=to_add)
302+
295303
# TODO: remove `columns_for_cube` from geopandas data frame?
296304
# Enabling that triggers failure of som existing tests that use `aggregate_spatial`
297305
# to "enrich" a vector cube with pre-existing properties
@@ -311,6 +319,7 @@ def from_geodataframe(
311319
return cls(geometries=geometries_df, cube=cube)
312320

313321
else:
322+
# TODO: add a dummy 1D no-data cube?
314323
return cls(geometries=data)
315324

316325
@classmethod
@@ -429,6 +438,16 @@ def to_wkt(self) -> List[str]:
429438
wkts = [str(g) for g in self._geometries.geometry]
430439
return wkts
431440

441+
def to_internal_json(self) -> dict:
442+
"""
443+
Export to an internal JSON-style representation.
444+
Subject to change any time: not intended for public consumption, just for (unit) test purposes.
445+
"""
446+
return {
447+
"geometries": shapely.geometry.mapping(self._geometries),
448+
"cube": self._cube.to_dict(data="array") if self._cube is not None else None,
449+
}
450+
432451
def get_crs(self) -> pyproj.CRS:
433452
return self._geometries.crs or pyproj.CRS.from_epsg(4326)
434453

openeo_driver/testing.py

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import http.server
77
import json
88
import logging
9+
import math
910
import multiprocessing
1011
import re
1112
import urllib.request
@@ -494,6 +495,11 @@ def approxify(x: Any, rel: Optional = None, abs: Optional[float] = None) -> Any:
494495
raise ValueError(x)
495496

496497

498+
class IsNan:
499+
def __eq__(self, other):
500+
return isinstance(other, float) and math.isnan(other)
501+
502+
497503
class ApproxGeometry:
498504
"""Helper to compactly and approximately compare geometries."""
499505

tests/test_testing.py

+15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
22
import re
33
import subprocess
4+
5+
import numpy
46
import sys
57
import textwrap
68
import urllib.error
@@ -22,6 +24,7 @@
2224
ephemeral_fileserver,
2325
preprocess_check_and_replace,
2426
ApproxGeoJSONByBounds,
27+
IsNan,
2528
)
2629

2730

@@ -261,6 +264,18 @@ def test_approxify_tolerance_rel():
261264
assert {"a": [10.1, 2.1]} != approxify({"a": [10, 2.3]}, rel=0.01)
262265

263266

267+
@pytest.mark.parametrize("other", [float("nan"), numpy.nan])
268+
def test_is_nan(other):
269+
assert other == IsNan()
270+
assert IsNan() == other
271+
272+
273+
@pytest.mark.parametrize("other", [0, 123, False, True, None, "dfd", [], {}, ()])
274+
def test_is_not_nan(other):
275+
assert other != IsNan()
276+
assert IsNan() != other
277+
278+
264279
@pytest.mark.parametrize(
265280
"format",
266281
[

tests/test_vectorcube.py

+139-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from openeo_driver.errors import OpenEOApiException
1212
from openeo_driver.datacube import DriverVectorCube
13-
from openeo_driver.testing import DictSubSet, ApproxGeometry
13+
from openeo_driver.testing import DictSubSet, ApproxGeometry, IsNan
1414
from openeo_driver.util.geometry import as_geojson_feature_collection
1515
from openeo_driver.utils import EvalEnv
1616

@@ -83,6 +83,144 @@ def test_to_wkt(self, gdf):
8383
['POLYGON ((1 1, 3 1, 2 3, 1 1))', 'POLYGON ((4 2, 5 4, 3 4, 4 2))']
8484
)
8585

86+
def test_to_internal_json_defaults(self, gdf):
87+
vc = DriverVectorCube(gdf)
88+
assert vc.to_internal_json() == {
89+
"geometries": DictSubSet(
90+
{
91+
"type": "FeatureCollection",
92+
"features": [
93+
DictSubSet(
94+
{
95+
"type": "Feature",
96+
"geometry": {
97+
"type": "Polygon",
98+
"coordinates": (((1.0, 1.0), (3.0, 1.0), (2.0, 3.0), (1.0, 1.0)),),
99+
},
100+
"properties": {"id": "first", "pop": 1234},
101+
}
102+
),
103+
DictSubSet(
104+
{
105+
"type": "Feature",
106+
"geometry": {
107+
"type": "Polygon",
108+
"coordinates": (((4.0, 2.0), (5.0, 4.0), (3.0, 4.0), (4.0, 2.0)),),
109+
},
110+
"properties": {"id": "second", "pop": 5678},
111+
}
112+
),
113+
],
114+
}
115+
),
116+
"cube": None,
117+
}
118+
119+
@pytest.mark.parametrize(
120+
["columns_for_cube", "expected_cube"],
121+
[
122+
(
123+
"numerical",
124+
{
125+
"name": None,
126+
"dims": ("geometries", "properties"),
127+
"coords": {
128+
"geometries": {"attrs": {}, "data": [0, 1], "dims": ("geometries",)},
129+
"properties": {"attrs": {}, "data": ["pop"], "dims": ("properties",)},
130+
},
131+
"data": [[1234], [5678]],
132+
"attrs": {},
133+
},
134+
),
135+
(
136+
"all",
137+
{
138+
"name": None,
139+
"dims": ("geometries", "properties"),
140+
"coords": {
141+
"geometries": {"attrs": {}, "data": [0, 1], "dims": ("geometries",)},
142+
"properties": {"attrs": {}, "data": ["id", "pop"], "dims": ("properties",)},
143+
},
144+
"data": [["first", 1234], ["second", 5678]],
145+
"attrs": {},
146+
},
147+
),
148+
([], None),
149+
(
150+
["pop", "id"],
151+
{
152+
"name": None,
153+
"dims": ("geometries", "properties"),
154+
"coords": {
155+
"geometries": {"attrs": {}, "data": [0, 1], "dims": ("geometries",)},
156+
"properties": {"attrs": {}, "data": ["pop", "id"], "dims": ("properties",)},
157+
},
158+
"data": [[1234, "first"], [5678, "second"]],
159+
"attrs": {},
160+
},
161+
),
162+
(
163+
["pop", "color"],
164+
{
165+
"name": None,
166+
"dims": ("geometries", "properties"),
167+
"coords": {
168+
"geometries": {"attrs": {}, "data": [0, 1], "dims": ("geometries",)},
169+
"properties": {"attrs": {}, "data": ["pop", "color"], "dims": ("properties",)},
170+
},
171+
"data": [[1234.0, IsNan()], [5678.0, IsNan()]],
172+
"attrs": {},
173+
},
174+
),
175+
(
176+
["color"],
177+
{
178+
"name": None,
179+
"dims": ("geometries", "properties"),
180+
"coords": {
181+
"geometries": {"attrs": {}, "data": [0, 1], "dims": ("geometries",)},
182+
"properties": {"attrs": {}, "data": ["color"], "dims": ("properties",)},
183+
},
184+
"data": [[IsNan()], [IsNan()]],
185+
"attrs": {},
186+
},
187+
),
188+
],
189+
)
190+
def test_to_internal_json_columns_for_cube(self, gdf, columns_for_cube, expected_cube):
191+
vc = DriverVectorCube.from_geodataframe(gdf, columns_for_cube=columns_for_cube)
192+
internal = vc.to_internal_json()
193+
assert internal == {
194+
"geometries": DictSubSet(
195+
{
196+
"type": "FeatureCollection",
197+
"features": [
198+
DictSubSet(
199+
{
200+
"type": "Feature",
201+
"geometry": {
202+
"type": "Polygon",
203+
"coordinates": (((1.0, 1.0), (3.0, 1.0), (2.0, 3.0), (1.0, 1.0)),),
204+
},
205+
"properties": {"id": "first", "pop": 1234},
206+
}
207+
),
208+
DictSubSet(
209+
{
210+
"type": "Feature",
211+
"geometry": {
212+
"type": "Polygon",
213+
"coordinates": (((4.0, 2.0), (5.0, 4.0), (3.0, 4.0), (4.0, 2.0)),),
214+
},
215+
"properties": {"id": "second", "pop": 5678},
216+
}
217+
),
218+
],
219+
}
220+
),
221+
"cube": expected_cube,
222+
}
223+
86224
def test_get_crs(self, gdf):
87225
vc = DriverVectorCube(gdf)
88226
assert vc.get_crs() == pyproj.CRS.from_epsg(4326)

0 commit comments

Comments
 (0)