diff --git a/planet/cli/data.py b/planet/cli/data.py index 7916a943..d5082a33 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -58,11 +58,6 @@ def data(ctx, base_url): ctx.obj['BASE_URL'] = base_url -# TODO: filter(). -def geom_to_filter(ctx, param, value: Optional[dict]) -> Optional[dict]: - return data_filter.geometry_filter(value) if value else None - - def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]: # TODO: validate and normalize return data_filter.asset_filter(assets) if assets else None @@ -172,9 +167,14 @@ def _func(obj): DATETIME can be an RFC3339 or ISO 8601 string.""") @click.option('--geom', type=types.JSON(), - callback=geom_to_filter, - help="""Filter to items that overlap a given geometry. Can be a + help="""Filter to items with a given geometry. Can be a json string, filename, or '-' for stdin.""") +@click.option( + "--geom-relation", + type=click.Choice(["intersects", "contains", "within", "disjoint"]), + help="""Optional geometry search refinement, defaults to intersects. + May also be contains, within, or disjoint.""", +) @click.option('--number-in', type=click.Tuple([types.Field(), types.CommaSeparatedFloat()]), multiple=True, @@ -224,6 +224,7 @@ def filter(ctx, asset, date_range, geom, + geom_relation, number_in, nrange, string_in, @@ -245,9 +246,11 @@ def filter(ctx, permission = data_filter.permission_filter() if permission else None std_quality = data_filter.std_quality_filter() if std_quality else None + geometry = data_filter.geometry_filter(geom, geom_relation) + filter_options = (asset, date_range, - geom, + geometry, number_in, nrange, string_in, diff --git a/planet/data_filter.py b/planet/data_filter.py index ef7d10f0..0b11d20b 100644 --- a/planet/data_filter.py +++ b/planet/data_filter.py @@ -218,7 +218,7 @@ def update_filter(field_name: str, callback=_datetime_to_rfc3339) -def geometry_filter(geom: dict) -> dict: +def geometry_filter(geom: dict, relation: str = None) -> dict: """Create a GeometryFilter The GeometryFilter can be used to search for items with a footprint @@ -237,10 +237,17 @@ def geometry_filter(geom: dict) -> dict: Parameters: geom: GeoJSON describing the filter geometry, feature, or feature collection. + relation: Optional geometry search refinement, defaults to intersects. + May also be contains, within, or disjoint. """ geom_filter = _field_filter('GeometryFilter', field_name='geometry', config=geojson.validate_geom_as_geojson(geom)) + if relation: + allowed = {"intersects", "contains", "disjoint", "within"} + if relation not in allowed: + raise ValueError(f"relation must be one of {allowed}") + geom_filter["relation"] = relation return geom_filter diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 5021a1be..1048d4e2 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -173,7 +173,6 @@ def _func(filter1, filter2): return _func -@respx.mock def test_data_filter_defaults(invoke, assert_and_filters_equal): result = invoke(["filter"]) @@ -183,7 +182,6 @@ def test_data_filter_defaults(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), empty_filter) -@respx.mock def test_data_filter_permission(invoke, assert_and_filters_equal): result = invoke(["filter", "--permission"]) assert result.exit_code == 0 @@ -197,7 +195,6 @@ def test_data_filter_permission(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_std_quality(invoke, assert_and_filters_equal): result = invoke(["filter", '--std-quality']) assert result.exit_code == 0 @@ -230,7 +227,6 @@ def test_data_filter_asset(asset, expected, invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_date_range_success(invoke, assert_and_filters_equal): """Check filter is created correctly and that multiple options results in multiple filters""" @@ -262,13 +258,11 @@ def test_data_filter_date_range_success(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_date_range_invalid(invoke): result = invoke(["filter"] + '--date-range field gt 2021'.split()) assert result.exit_code == 2 -@respx.mock @pytest.mark.parametrize("geom_fixture", [('geom_geojson'), ('feature_geojson'), ('featurecollection_geojson')]) @@ -296,7 +290,16 @@ def test_data_filter_geom(geom_fixture, assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock +def test_data_filter_geom_relation(request, invoke): + geom = request.getfixturevalue("geom_geojson") + geom_str = json.dumps(geom) + result = invoke(["filter", f'--geom={geom_str}', '--geom-relation=disjoint']) + assert result.exit_code == 0 + + and_filter = json.loads(result.output) + assert and_filter["config"][0]["relation"] == "disjoint" + + def test_data_filter_number_in_success(invoke, assert_and_filters_equal): result = invoke(["filter"] + '--number-in field 1'.split() + @@ -317,14 +320,12 @@ def test_data_filter_number_in_success(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_number_in_badparam(invoke, assert_and_filters_equal): result = invoke(["filter"] + '--number-in field 1,str'.split()) assert result.exit_code == 2 -@respx.mock def test_data_filter_range(invoke, assert_and_filters_equal): """Check filter is created correctly, that multiple options results in multiple filters, and that floats are processed correctly.""" @@ -352,7 +353,6 @@ def test_data_filter_range(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_string_in(invoke, assert_and_filters_equal): result = invoke(["filter"] + '--string-in field foo'.split() + @@ -375,7 +375,6 @@ def test_data_filter_string_in(invoke, assert_and_filters_equal): assert_and_filters_equal(json.loads(result.output), expected_filt) -@respx.mock def test_data_filter_update(invoke, assert_and_filters_equal): """Check filter is created correctly and that multiple options results in multiple filters"""