Skip to content

Commit 130295b

Browse files
committed
geometry relation support for data search
- optional arg in filter creation - add support in CLI filter command - remove some superfluous respx.mock annotations see #1204
1 parent 84944f6 commit 130295b

File tree

3 files changed

+36
-20
lines changed

3 files changed

+36
-20
lines changed

planet/cli/data.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ def data(ctx, base_url):
5858
ctx.obj['BASE_URL'] = base_url
5959

6060

61-
# TODO: filter().
62-
def geom_to_filter(ctx, param, value: Optional[dict]) -> Optional[dict]:
63-
return data_filter.geometry_filter(value) if value else None
64-
65-
6661
def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]:
6762
# TODO: validate and normalize
6863
return data_filter.asset_filter(assets) if assets else None
@@ -172,9 +167,14 @@ def _func(obj):
172167
DATETIME can be an RFC3339 or ISO 8601 string.""")
173168
@click.option('--geom',
174169
type=types.JSON(),
175-
callback=geom_to_filter,
176-
help="""Filter to items that overlap a given geometry. Can be a
170+
help="""Filter to items with a given geometry. Can be a
177171
json string, filename, or '-' for stdin.""")
172+
@click.option(
173+
"--geom-relation",
174+
type=click.Choice(["intersects", "contains", "within", "disjoint"]),
175+
help="""Optional geometry search refinement, defaults to intersects.
176+
May also be contains, within, or disjoint.""",
177+
)
178178
@click.option('--number-in',
179179
type=click.Tuple([types.Field(), types.CommaSeparatedFloat()]),
180180
multiple=True,
@@ -224,6 +224,7 @@ def filter(ctx,
224224
asset,
225225
date_range,
226226
geom,
227+
geom_relation,
227228
number_in,
228229
nrange,
229230
string_in,
@@ -245,9 +246,12 @@ def filter(ctx,
245246
permission = data_filter.permission_filter() if permission else None
246247
std_quality = data_filter.std_quality_filter() if std_quality else None
247248

249+
geometry = data_filter.geometry_filter(geom,
250+
geom_relation) if geom else None
251+
248252
filter_options = (asset,
249253
date_range,
250-
geom,
254+
geometry,
251255
number_in,
252256
nrange,
253257
string_in,

planet/data_filter.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def update_filter(field_name: str,
218218
callback=_datetime_to_rfc3339)
219219

220220

221-
def geometry_filter(geom: dict) -> dict:
221+
def geometry_filter(geom: dict, relation: Optional[str] = None) -> dict:
222222
"""Create a GeometryFilter
223223
224224
The GeometryFilter can be used to search for items with a footprint
@@ -237,10 +237,17 @@ def geometry_filter(geom: dict) -> dict:
237237
Parameters:
238238
geom: GeoJSON describing the filter geometry, feature, or feature
239239
collection.
240+
relation: Optional geometry search refinement, defaults to intersects.
241+
May also be contains, within, or disjoint.
240242
"""
241243
geom_filter = _field_filter('GeometryFilter',
242244
field_name='geometry',
243245
config=geojson.validate_geom_as_geojson(geom))
246+
if relation:
247+
allowed = {"intersects", "contains", "disjoint", "within"}
248+
if relation not in allowed:
249+
raise ValueError(f"relation must be one of {allowed}")
250+
geom_filter["relation"] = relation
244251
return geom_filter
245252

246253

tests/integration/test_data_cli.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ def _func(filter1, filter2):
173173
return _func
174174

175175

176-
@respx.mock
177176
def test_data_filter_defaults(invoke, assert_and_filters_equal):
178177

179178
result = invoke(["filter"])
@@ -183,7 +182,6 @@ def test_data_filter_defaults(invoke, assert_and_filters_equal):
183182
assert_and_filters_equal(json.loads(result.output), empty_filter)
184183

185184

186-
@respx.mock
187185
def test_data_filter_permission(invoke, assert_and_filters_equal):
188186
result = invoke(["filter", "--permission"])
189187
assert result.exit_code == 0
@@ -197,7 +195,6 @@ def test_data_filter_permission(invoke, assert_and_filters_equal):
197195
assert_and_filters_equal(json.loads(result.output), expected_filt)
198196

199197

200-
@respx.mock
201198
def test_data_filter_std_quality(invoke, assert_and_filters_equal):
202199
result = invoke(["filter", '--std-quality'])
203200
assert result.exit_code == 0
@@ -230,7 +227,6 @@ def test_data_filter_asset(asset, expected, invoke, assert_and_filters_equal):
230227
assert_and_filters_equal(json.loads(result.output), expected_filt)
231228

232229

233-
@respx.mock
234230
def test_data_filter_date_range_success(invoke, assert_and_filters_equal):
235231
"""Check filter is created correctly and that multiple options results in
236232
multiple filters"""
@@ -262,13 +258,11 @@ def test_data_filter_date_range_success(invoke, assert_and_filters_equal):
262258
assert_and_filters_equal(json.loads(result.output), expected_filt)
263259

264260

265-
@respx.mock
266261
def test_data_filter_date_range_invalid(invoke):
267262
result = invoke(["filter"] + '--date-range field gt 2021'.split())
268263
assert result.exit_code == 2
269264

270265

271-
@respx.mock
272266
@pytest.mark.parametrize("geom_fixture",
273267
[('geom_geojson'), ('feature_geojson'),
274268
('featurecollection_geojson')])
@@ -296,7 +290,22 @@ def test_data_filter_geom(geom_fixture,
296290
assert_and_filters_equal(json.loads(result.output), expected_filt)
297291

298292

299-
@respx.mock
293+
def test_data_filter_geom_relation(request, invoke):
294+
geom = request.getfixturevalue("geom_geojson")
295+
geom_str = json.dumps(geom)
296+
result = invoke(["filter", f'--geom={geom_str}', '--geom-relation=disjoint'])
297+
assert result.exit_code == 0
298+
299+
and_filter = json.loads(result.output)
300+
assert and_filter["config"][0]["relation"] == "disjoint"
301+
302+
# --geom-relation is ignored without --geom
303+
result = invoke(["filter", '--geom-relation=disjoint'])
304+
assert result.exit_code == 0
305+
and_filter = json.loads(result.output)
306+
assert and_filter["config"] == []
307+
308+
300309
def test_data_filter_number_in_success(invoke, assert_and_filters_equal):
301310

302311
result = invoke(["filter"] + '--number-in field 1'.split() +
@@ -317,14 +326,12 @@ def test_data_filter_number_in_success(invoke, assert_and_filters_equal):
317326
assert_and_filters_equal(json.loads(result.output), expected_filt)
318327

319328

320-
@respx.mock
321329
def test_data_filter_number_in_badparam(invoke, assert_and_filters_equal):
322330

323331
result = invoke(["filter"] + '--number-in field 1,str'.split())
324332
assert result.exit_code == 2
325333

326334

327-
@respx.mock
328335
def test_data_filter_range(invoke, assert_and_filters_equal):
329336
"""Check filter is created correctly, that multiple options results in
330337
multiple filters, and that floats are processed correctly."""
@@ -352,7 +359,6 @@ def test_data_filter_range(invoke, assert_and_filters_equal):
352359
assert_and_filters_equal(json.loads(result.output), expected_filt)
353360

354361

355-
@respx.mock
356362
def test_data_filter_string_in(invoke, assert_and_filters_equal):
357363

358364
result = invoke(["filter"] + '--string-in field foo'.split() +
@@ -375,7 +381,6 @@ def test_data_filter_string_in(invoke, assert_and_filters_equal):
375381
assert_and_filters_equal(json.loads(result.output), expected_filt)
376382

377383

378-
@respx.mock
379384
def test_data_filter_update(invoke, assert_and_filters_equal):
380385
"""Check filter is created correctly and that multiple options results in
381386
multiple filters"""

0 commit comments

Comments
 (0)