Skip to content

Simplify subscription clip rules in the Subscriptions API SDK #1169

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/python/sdk-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ You will need your ACCESS_KEY_ID, SECRET_ACCESS_KEY, bucket and region name.
To subscribe to scenes that match a filter, use the `subscription_request` module to build a request, and
pass it to the `subscriptions.create_subscription()` method of the client.

By default, a request to create a subscription will not clip matching imagery which intersects the source geometry. To clip to the subscription source geometry, set `planet.subscription_request.build_request()` keyword argument `clip_to_source = True` as in the example below. To clip to a custom geometry, set `planet.subscription_request.build_request()` keyword argument `clip_to_source = False` (or omit it entirely to fall back on the default value), and instead configure the custom clip AOI with `planet.subscription_request.clip_tool()`.
By default, a request to create a subscription will not clip matching imagery which intersects the source geometry. To clip to the subscription source geometry, set `planet.subscription_request.build_request()` keyword argument `clip_to_source = True` as in the example below. Custom clip AOIs are no longer supported in subscriptions.

Warning: the following code will create a subscription, consuming quota based on your plan.

Expand Down
50 changes: 5 additions & 45 deletions planet/subscription_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ def build_request(name: str,
collection_id: A Sentinel Hub collection ID.
create_configuration: Automatically create a layer configuration for your collection.
clip_to_source: Whether or not to clip to the source geometry (defaults to False). If
True, a clip configuration that specifies the subscription source geometry as clip
AOI will be added to the list of requested tools. If True and 'clip_tool()' is
also specified, an exception will be raised. If False, no clip configuration
will be added to the list of requested tools unless 'clip_tool()' is specified.
True, a clip configuration will be added to the list of requested tools that
automatically clips to the subscription source geometry. If True and a clip tool is
also specified in the tools list, an exception will be raised. If False, no clip
configuration will be added to the list of requested tools.

Returns:
dict: a representation of a Subscriptions API request for
Expand Down Expand Up @@ -136,12 +136,7 @@ def build_request(name: str,
"clip_to_source option conflicts with a configured clip tool."
)
else:
tool_list.append({
'type': 'clip',
'parameters': {
'aoi': source['parameters']['geometry']
}
})
tool_list.append({'type': 'clip', 'parameters': {}})

details['tools'] = tool_list

Expand Down Expand Up @@ -638,41 +633,6 @@ def band_math_tool(b1: str,
return _tool('bandmath', parameters)


def clip_tool(aoi: Mapping) -> dict:
"""Specify a subscriptions API clip tool.

Imagery and udm files will be clipped to your area of interest. nodata
pixels will be preserved. Xml file attributes “filename”, “numRows”,
“numColumns” and “footprint” will be updated based on the clip results.

The clipped output files will have “_clip” appended to their file names. If
the clip aoi is so large that full scenes may be delivered without any
clipping, those files will not have “_clip” appended to their file name.

NOTE: To clip to the source geometry, set the 'clip_to_source' parameter
of 'planet.subscription_request.build_request()' to True instead of using
this tool.

Parameters:
aoi: GeoJSON polygon or multipolygon defining the clip area, with up to
500 vertices. The minimum geographic area of any polygon or
internal ring is one square meter.

Raises:
planet.exceptions.ClientError: If aoi is not a valid polygon or
multipolygon.
"""

valid_types = ['Polygon', 'MultiPolygon', 'ref']

geom = geojson.as_geom_or_ref(dict(aoi))
if geom['type'].lower() not in [v.lower() for v in valid_types]:
raise ClientError(
f'Invalid geometry type: {geom["type"]} is not in {valid_types}.')

return _tool('clip', {'aoi': geom})


def file_format_tool(file_format: str) -> dict:
"""Specify a subscriptions API file format tool.

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_subscriptions_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def test_request_base_clip_to_source(geom_fixture, request, invoke):
req = json.loads(result.output)
tool = req["tools"][0]
assert tool["type"] == "clip"
assert tool["parameters"]["aoi"] == geom
assert tool["parameters"] == {}


def test_request_catalog_success(mock_bundles, invoke, geom_geojson):
Expand Down
13 changes: 1 addition & 12 deletions tests/unit/test_subscription_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_build_request_clip_to_source_success(geom_geojson):
clip_to_source=True,
)
assert req["tools"][1]["type"] == "clip"
assert req["tools"][1]["parameters"]["aoi"] == geom_geojson
assert req["tools"][1]["parameters"] == {}


def test_build_request_clip_to_source_failure(geom_geojson):
Expand Down Expand Up @@ -499,17 +499,6 @@ def test_band_math_tool_invalid_pixel_type():
pixel_type="invalid")


def test_clip_tool_success(geom_geojson):
res = subscription_request.clip_tool(geom_geojson)
expected = {"type": "clip", "parameters": {"aoi": geom_geojson}}
assert res == expected


def test_clip_tool_invalid_type(point_geom_geojson):
with pytest.raises(exceptions.ClientError):
subscription_request.clip_tool(point_geom_geojson)


def test_file_format_tool_success():
res = subscription_request.file_format_tool('COG')

Expand Down