Skip to content

Commit 6c099cb

Browse files
authored
Merge pull request planetlabs#1032 from planetlabs/ag/support-feature-references
Allow use of feature references in the sdk
2 parents 3cab8f0 + 862d92f commit 6c099cb

17 files changed

+411
-118
lines changed

docs/cli/cli-tips-tricks.md

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ title: More CLI Tips & Tricks
55
## About
66

77
This document shows off a range of more advanced command-line workflows, making use of a wider range
8-
of tools in the command-line & geospatial ecosystem. Some of them can be a pain to install, like
9-
GDAL/OGR, and several pop in and out of web tools, so these are kept out of the main tutorial
10-
section.
8+
of tools in the command-line & geospatial ecosystem. Some of them can be a pain to install, like
9+
GDAL/OGR, and several pop in and out of web tools, so these are kept out of the main tutorial
10+
section.
1111

1212
**WORK IN PROGRESS**: This document is still under construction, with a number of TODO’s remaining,
1313
but we are publishing as there’s a lot of good information here.
1414

1515
## Tools used
1616

17-
* **[GDAL/OGR](https://gdal.org)** - We’ll mostly use OGR, the vector tooling.
17+
* **[GDAL/OGR](https://gdal.org)** - We’ll mostly use OGR, the vector tooling.
1818
Great for things like format conversion and basic simplification.
1919
* **[Keplergl_cli](https://github.com/kylebarron/keplergl_cli#usage)** - Nice tool to call the
20-
awesome kepler.gl library from the commandline. Useful for visualization of large amounts of
20+
awesome kepler.gl library from the commandline. Useful for visualization of large amounts of
2121
geojson.
2222
* **[GeoJSON.io](https://geojson.io/)** - Simple tool to do editing of geojson, useful for creating
2323
AOI’s. It integrates with github, but the ability to save a GeoJSON to github doesn't seem to work so well.
2424
* **[Placemark.io](https://placemark.io)** - More advanced tool from the creator of GeoJSON.io, very
25-
nice for creating AOI’s and piping them in, with lots of rich geometry editing features.
25+
nice for creating AOI’s and piping them in, with lots of rich geometry editing features.
2626
* **[MapShaper](https://github.com/mbloch/mapshaper)** - Tool to do interactive simplification of
2727
GeoJSON, has a nice CLI.
2828
* **[STACTools](https://github.com/stac-utils/stactools)** - CLI for working with STAC data. There
@@ -34,14 +34,28 @@ future.
3434

3535
### Geometry Inputs
3636

37-
While the command-line can often be quicker than using a UI, one place that can be slower is
37+
While the command-line can often be quicker than using a UI, one place that can be slower is
3838
getting the geometry input for searching or clipping. Hand-editing GeoJSON is a huge pain, so most
39-
people will open up a desktop tool like QGIS or ArcGIS Pro and save the file. But there are a few
39+
people will open up a desktop tool like QGIS or ArcGIS Pro and save the file. But there are a few
4040
tools that can get you back into the CLI workflow more quickly.
4141

42+
#### Use the Features API
43+
Rather than using GeoJSON in the SDK, upload your GeoJSON to the [Features API](https://developers.planet.com/docs/apis/features/) and use references
44+
across the system with the sdk.
45+
References are used in the geometry block of our services in a GeoJSON blob like:
46+
```json
47+
"geometry":
48+
{
49+
"content": "pl:features/my/[collection-id]/[feature-id]",
50+
"type": "ref"
51+
}
52+
```
53+
Or as a string in a geometry option like `"pl:features/my/[collection-id]/[feature-id]"`
54+
55+
4256
#### Draw with GeoJSON.io
4357

44-
One great tool for quickly drawing on a map and getting GeoJSON output is
58+
One great tool for quickly drawing on a map and getting GeoJSON output is
4559
[GeoJSON.io](https://geojson.io). You can draw and save the file, but an even faster workflow
4660
is to use your operating system’s clipboard to command-line tools.
4761

@@ -62,7 +76,7 @@ pbpaste | planet data filter --geom - | planet data search SkySatCollect --filt
6276

6377
A really fantastic tool for working with GeoJSON is [Placemark](https://placemark.io). It is a
6478
commercial tool that you’ll have to pay for, but it’s got a really nice feature that makes it very
65-
compatible with command-line workflows. You can easily grab the URL of any individual GeoJSON
79+
compatible with command-line workflows. You can easily grab the URL of any individual GeoJSON
6680
feature and stream it in as your geometry using `curl`:
6781

6882
![Stream from Placemark](https://user-images.githubusercontent.com/407017/179412209-2365d79a-9260-47e5-9b08-9bc5b84b6ddc.gif)
@@ -80,8 +94,8 @@ let you pipe (`|`) the output more directly.
8094

8195
#### Copy GeoJSON to clipboard
8296

83-
One of the quicker routes to visualizing search output is to copy the output to your clipboard and paste into a
84-
tool that will take GeoJSON and visualize it.
97+
One of the quicker routes to visualizing search output is to copy the output to your clipboard and paste into a
98+
tool that will take GeoJSON and visualize it.
8599

86100
You can do this on GeoJSON.io:
87101

@@ -101,7 +115,7 @@ planet data filter --string-in strip_id 5743669 | planet data search PSScene --f
101115

102116
#### Post to Github as gist
103117

104-
Another easy option that is a bit more persistent is to post to Github using the
118+
Another easy option that is a bit more persistent is to post to Github using the
105119
[`gh` cli tool](https://github.com/cli/cli). Specifically using the `gist create` command.
106120

107121
The following command will get the latest SkySat image captured, upload to github, and open
@@ -135,13 +149,13 @@ planet data filter --string-in strip_id $stripid | planet data search PSScene --
135149

136150
One of the best tools to visualize large numbers of imagery footprints is a tool called [kepler.gl](https://kepler.gl/),
137151
which has a really awesome command-line version which is perfect for working with Planet’s CLI. To get the CLI go to
138-
[keplergl_cli](https://github.com/kylebarron/keplergl_cli) and follow the
152+
[keplergl_cli](https://github.com/kylebarron/keplergl_cli) and follow the
139153
[installation instructions](https://github.com/kylebarron/keplergl_cli#install). Be sure to get a Mapbox API key (from
140154
the [access tokens](https://account.mapbox.com/access-tokens/) page) - just sign up for a free account if you don't have
141155
one already. The kepler CLI won't work at all without getting one and setting it as the `MAPBOX_API_KEY` environment
142156
variable.
143157

144-
Once it’s set up you can just pipe any search command directly to `kepler` (it usually does fine even without
158+
Once it’s set up you can just pipe any search command directly to `kepler` (it usually does fine even without
145159
`planet collect` to go from ndgeojson to geojson). For example:
146160

147161
```console
@@ -179,9 +193,9 @@ curl -s https://api.placemark.io/api/v1/map/a0BWUEErqU9A1EDHZWHez/feature/91a073
179193

180194
#### Large Dataset Visualization
181195

182-
Oftentimes it can be useful to visualize a large amount of data, to really get a sense of the
183-
coverage and then do some filtering of the output. For this we recommend downloading the output
184-
to disk. Getting 20,000 skysat collects will take at least a couple of minutes, and will be over
196+
Oftentimes it can be useful to visualize a large amount of data, to really get a sense of the
197+
coverage and then do some filtering of the output. For this we recommend downloading the output
198+
to disk. Getting 20,000 skysat collects will take at least a couple of minutes, and will be over
185199
100 megabytes of GeoJSON on disk.
186200

187201
```console
@@ -255,7 +269,7 @@ Smaller ratios preserve the character of concave features better.
255269

256270
##### Simplification with OGR
257271

258-
The other thing you’ll likely want to do to visualize large amounts of data is to simplify it
272+
The other thing you’ll likely want to do to visualize large amounts of data is to simplify it
259273
some. Many simplification tools call for a 'tolerance', often set in degrees. For SkySat some useful values are:
260274

261275
| tolerance | result |
@@ -264,7 +278,7 @@ some. Many simplification tools call for a 'tolerance', often set in degrees. Fo
264278
| 0.01 | Messes with the shape a bit, but the footprint generally looks the same, with a couple vertices off. |
265279
| 0.1 | Mashes the shape, often into a triangle, but still useful for understanding broad coverage. |
266280

267-
It’s worth experimenting with options between these as well. The more simplification the easier it is for programs to
281+
It’s worth experimenting with options between these as well. The more simplification the easier it is for programs to
268282
render the results. `ogr2ogr` includes the ability to simplify any output:
269283

270284
```console
@@ -277,14 +291,14 @@ Alternative - use convex hull. TODO: test this, write it up
277291
ogr2ogr skysat-convex.gpkg skysat.geojson ogr2ogr -sql "select st_convexhull(geometry) from skysat" -dialect sqlite
278292
```
279293

280-
Other alternative for really big ones, centroid. GDAL should be able to do this, need to figure out the similar
294+
Other alternative for really big ones, centroid. GDAL should be able to do this, need to figure out the similar
281295
sql.
282296

283297
#### Simplification with Mapshaper
284298

285-
Another great tool is [Mapshaper](https://github.com/mbloch/mapshaper), which excels at simplification. It offers a
286-
web-based user interface to see the results of simplification, and also a command-line tool you can use if you
287-
find a simplification percentage you’re happy with. After you get it
299+
Another great tool is [Mapshaper](https://github.com/mbloch/mapshaper), which excels at simplification. It offers a
300+
web-based user interface to see the results of simplification, and also a command-line tool you can use if you
301+
find a simplification percentage you’re happy with. After you get it
288302
[installed](https://github.com/mbloch/mapshaper#installation) you can fire up the UI with:
289303

290304
```console
@@ -300,13 +314,13 @@ interface, or you can also run the command-line program:
300314
mapshaper -i footprints.geojson -simplify 15% -o simplified.geojson
301315
```
302316

303-
Once you find a simplification amount you’re happy with you can use it as a piped output.
317+
Once you find a simplification amount you’re happy with you can use it as a piped output.
304318

305319
```console
306320
planet data search --limit 20 SkySatCollect - | planet collect - | mapshaper -i - -simplify 15% -o skysat-ms2.geojson
307321
```
308322

309-
Mapshaper also has more simplification algorithms to try out, so we recommend diving into the
323+
Mapshaper also has more simplification algorithms to try out, so we recommend diving into the
310324
[CLI options](https://github.com/mbloch/mapshaper/wiki/Command-Reference).
311325

312326
#### Simplification with QGIS
@@ -316,7 +330,7 @@ Another good tool for simplification is QGIS.
316330
TODO: Flesh out this section, add in command-line qgis_processing option.
317331

318332
Other simplification options for large datasets:
319-
333+
320334
* Use QGIS, run 'convex hull' (Vector -> Geoprocessing -> Convex Hull). Good idea to convert to gpkg or shapefile before you open in qgis if large.
321335

322336
### Advanced jq
@@ -327,7 +341,7 @@ Other simplification options for large datasets:
327341
- get id by array number
328342

329343
```console
330-
planet orders list | jq -rs '.[3] | "\(.id) \(.created_on) \(.name) \(.state)"'
344+
planet orders list | jq -rs '.[3] | "\(.id) \(.created_on) \(.name) \(.state)"'
331345
```
332346
(limit can get the most recent, but not a second or third)
333347

planet/cli/data.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from typing import List, Optional
1616
from contextlib import asynccontextmanager
1717
from pathlib import Path
18-
1918
import click
2019

2120
from planet.reporting import AssetStatusBar
@@ -37,6 +36,7 @@
3736
from .io import echo_json
3837
from .options import limit, pretty
3938
from .session import CliSession
39+
from .validators import check_geom
4040

4141
valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(
4242
get_data_item_types())
@@ -281,6 +281,7 @@ def filter(ctx,
281281
@click.argument("item_types",
282282
type=types.CommaSeparatedString(),
283283
callback=check_item_types)
284+
@click.option("--geom", type=types.Geometry(), callback=check_geom)
284285
@click.option('--filter',
285286
type=types.JSON(),
286287
help="""Apply specified filter to search. Can be a json string,
@@ -293,7 +294,7 @@ def filter(ctx,
293294
show_default=True,
294295
help='Field and direction to order results by.')
295296
@pretty
296-
async def search(ctx, item_types, filter, limit, name, sort, pretty):
297+
async def search(ctx, item_types, geom, filter, limit, name, sort, pretty):
297298
"""Execute a structured item search.
298299
299300
This function outputs a series of GeoJSON descriptions, one for each of the
@@ -311,6 +312,7 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty):
311312
async with data_client(ctx) as cl:
312313

313314
async for item in cl.search(item_types,
315+
geometry=geom,
314316
search_filter=filter,
315317
name=name,
316318
sort=sort,
@@ -325,6 +327,7 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty):
325327
@click.argument("item_types",
326328
type=types.CommaSeparatedString(),
327329
callback=check_item_types)
330+
@click.option("--geom", type=types.Geometry(), callback=check_geom)
328331
@click.option(
329332
'--filter',
330333
type=types.JSON(),
@@ -339,7 +342,13 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty):
339342
is_flag=True,
340343
help='Send a daily email when new results are added.')
341344
@pretty
342-
async def search_create(ctx, item_types, filter, name, daily_email, pretty):
345+
async def search_create(ctx,
346+
item_types,
347+
geom,
348+
filter,
349+
name,
350+
daily_email,
351+
pretty):
343352
"""Create a new saved structured item search.
344353
345354
This function outputs a full JSON description of the created search,
@@ -349,6 +358,7 @@ async def search_create(ctx, item_types, filter, name, daily_email, pretty):
349358
"""
350359
async with data_client(ctx) as cl:
351360
items = await cl.create_search(item_types=item_types,
361+
geometry=geom,
352362
search_filter=filter,
353363
name=name,
354364
enable_email=daily_email)
@@ -485,6 +495,10 @@ async def search_delete(ctx, search_id):
485495
type=str,
486496
required=True,
487497
help='Name of the saved search.')
498+
@click.option("--geom",
499+
type=types.Geometry(),
500+
callback=check_geom,
501+
default=None)
488502
@click.option('--daily-email',
489503
is_flag=True,
490504
help='Send a daily email when new results are added.')
@@ -493,6 +507,7 @@ async def search_update(ctx,
493507
search_id,
494508
item_types,
495509
filter,
510+
geom,
496511
name,
497512
daily_email,
498513
pretty):
@@ -504,9 +519,10 @@ async def search_update(ctx,
504519
async with data_client(ctx) as cl:
505520
items = await cl.update_search(search_id,
506521
item_types,
507-
filter,
508-
name,
509-
daily_email)
522+
search_filter=filter,
523+
name=name,
524+
geometry=geom,
525+
enable_email=daily_email)
510526
echo_json(items, pretty)
511527

512528

planet/cli/subscriptions.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from .. import subscription_request
1414
from ..subscription_request import sentinel_hub
1515
from ..specs import get_item_types, validate_item_type, SpecificationException
16+
from .validators import check_geom
1617

1718
ALL_ITEM_TYPES = get_item_types()
1819
valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(ALL_ITEM_TYPES)
@@ -346,7 +347,8 @@ def request(name,
346347
@click.option(
347348
'--geometry',
348349
required=True,
349-
type=types.JSON(),
350+
type=types.Geometry(),
351+
callback=check_geom,
350352
help="""Geometry of the area of interest of the subscription that will be
351353
used to determine matches. Can be a string, filename, or - for stdin.""")
352354
@click.option('--start-time',
@@ -419,7 +421,8 @@ def request_catalog(item_types,
419421
@click.option(
420422
'--geometry',
421423
required=True,
422-
type=types.JSON(),
424+
type=types.Geometry(),
425+
callback=check_geom,
423426
help="""Geometry of the area of interest of the subscription that will be
424427
used to determine matches. Can be a string, filename, or - for stdin.""")
425428
@click.option('--start-time',

planet/cli/types.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ def convert(self, value, param, ctx) -> dict:
9393
return convdict
9494

9595

96+
class Geometry(click.ParamType):
97+
name = 'geom'
98+
99+
def __init__(self):
100+
self.types = [JSON(), CommaSeparatedString()]
101+
102+
def convert(self, value, param, ctx):
103+
for type in self.types:
104+
try:
105+
return type.convert(value, param, ctx)
106+
except click.BadParameter:
107+
continue
108+
109+
96110
class Field(click.ParamType):
97111
"""Clarify that this entry is for a field"""
98112
name = 'field'

planet/cli/validators.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2022 Planet Labs, PBC.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4+
# use this file except in compliance with the License. You may obtain a copy of
5+
# the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations under
13+
# the License.
14+
"""CLI Parameter validation"""
15+
from typing import Optional
16+
from planet import geojson
17+
18+
19+
def check_geom(ctx, param, geometry) -> Optional[dict]:
20+
"""Validates geometry as GeoJSON or feature ref(s)."""
21+
if isinstance(geometry, dict):
22+
return geojson.as_geom_or_ref(geometry)
23+
geoms = {}
24+
if geometry:
25+
for geom in geometry:
26+
geoms.update(geojson.as_geom_or_ref(geom))
27+
return geoms if geoms else None

0 commit comments

Comments
 (0)