Skip to content
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
103 changes: 103 additions & 0 deletions docs/source/cli/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,109 @@ Requesting a resource for a feature in a collection::
planet analytics collections features get target-quad <collection_id or subscription_id> <feature_id>
planet analytics collections features get source-image-info <collection_id or subscription_id> <feature_id>

Orders Examples
-----------------

List all recent orders for the authenticated user::

planet orders list

Get the status of a single order by Order ID::

planet orders get <order ID>

Note that you may want to parse the JSON that's output into a more human
readable format. The cli does not directly provide options for this, but is
meant to be easily interoperable with other tools, e.g. `jq
<https://stedolan.github.io/jq/>`_.

To cancel a running order by given order ID::

planet orders cancel <order ID>

To download an order to your local machine::

planet orders download <order ID>

Optionally, a `--dest <path to destination>` flag may be specified too.

Creating an Order
..................

The minimal command to create a simple order looks something like::

planet orders create --name "my order" \
--id 20151119_025740_0c74,20151119_025741_0c74 \
--bundle visual --item-type psscene3band

If no toolchain or delivery details are specified, a basic order with download
delivery will be placed for the requested bundle including the item id(s) specified.

Additionally, optional toolchain & delivery details can be provided on the
command line, e.g.:::

planet orders create --name "my order" \
--id 20151119_025740_0c74,20151119_025741_0c74 \
--bundle visual --item-type psscene3band --zip order --email

This places the same order as above, and will also provide a .zip archive
download link for the full order, as well as email notification.

The Orders API allows you to specify a toolchain of operations to be performed
on your order prior to download. To read more about tools & toolchains, visit
`the docs <https://developers.planet.com/docs/orders/tools-toolchains/>`_ .

To add tool operations to your order, use the `--tools` option to specify a
json-formatted file containing an array (list) of the desired tools an their
settings.

.. note:: The json-formatted file must be formatted as an array (enclosed in square brackets), even if only specifying a single tool

For example, to apply the 3 tools `TOAR -> Reproject -> Tile` in sequence to an
order, you would create a `.json` file similar to the following::

[
{
"toar": {
"scale_factor": 10000
}
},
{
"reproject": {
"projection": "WGS84",
"kernel": "cubic"
}
},
{
"tile": {
"tile_size": 1232,
"origin_x": -180,
"origin_y": -90,
"pixel_size": 0.000027056277056,
"name_template": "C1232_30_30_{tilex:04d}_{tiley:04d}"
}
}
]


Similarly, you can also specify cloud delivery options on an order create
command with the `--cloudconfig <path to json file>` option. In this case, the
json file should contain the required credentials for your desired cloud
storage destination, for example::

{
"amazon_s3":{
"bucket":"foo-bucket",
"aws_region":"us-east-2",
"aws_access_key_id":"",
"aws_secret_access_key":"",
"path_prefix":""
}

You can find complete documentation of Orders API cloud storage delivery and
required credentials `in the docs here
<https://developers.planet.com/docs/orders/ordering-delivery/#delivery-to-cloud-storage_1>`_.

Integration With Other Tools
----------------------------

Expand Down
2 changes: 1 addition & 1 deletion planet/api/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.3.1'
__version__ = '1.3.2'
77 changes: 77 additions & 0 deletions planet/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,80 @@ def get_associated_resource_for_analytic_feature(self,
resource_type))
response = self._get(url).get_body()
return response

def get_orders(self):
'''Get information for all pending and completed order requests for
the current user.

:returns: :py:Class:`planet.api.models.Orders`
'''

# TODO filter 'completed orders', 'in progress orders', 'all orders'?
url = self._url('compute/ops/orders/v2')
orders = (self._get(url, models.Orders).get_body())
return orders

def get_individual_order(self, order_id):
'''Get order request details by Order ID.

:param order_id str: The ID of the Order
:returns: :py:Class:`planet.api.models.Order`
:raises planet.api.exceptions.APIException: On API error.
'''
url = self._url('compute/ops/orders/v2/{}'.format(order_id))
return self._get(url, models.Order).get_body()

def cancel_order(self, order_id):
'''Cancel a running order by Order ID.

:param order_id str: The ID of the Order to cancel
:returns: :py:Class:`planet.api.models.Order`
:raises planet.api.exceptions.APIException: On API error.
'''
url = self._url('compute/ops/orders/v2/{}'.format(order_id))
return self.dispatcher.response(models.Request(url, self.auth,
body_type=models.Order,
method='PUT')
).get_body()

def create_order(self, request):
'''Create an order.

:param asset:
:returns: :py:Class:`planet.api.models.Response` containing a
:py:Class:`planet.api.models.Body` of the asset.
:raises planet.api.exceptions.APIException: On API error.
'''
url = self._url('compute/ops/orders/v2')
body = json.dumps(request)
return self.dispatcher.response(models.Request(url, self.auth,
body_type=models.Order,
data=body,
method='POST')
).get_body()

def download_order(self, order_id, callback=None):
'''Download all items in an order.

:param order_id: ID of order to download
:returns: :py:Class:`planet.api.models.Response` containing a
:py:Class:`planet.api.models.Body` of the asset.
:raises planet.api.exceptions.APIException: On API error.
'''

url = self._url('compute/ops/orders/v2/{}'.format(order_id))

order = self._get(url, models.Order).get_body()
locations = order.get_locations()
return self._get(locations, models.JSON, callback=callback)

def download_location(self, location, callback=None):
'''Download an item in an order.

:param location: location URL of item
:returns: :py:Class:`planet.api.models.Response` containing a
:py:Class:`planet.api.models.Body` of the asset.
:raises planet.api.exceptions.APIException: On API error.
'''

return self._get(location, models.JSON, callback=callback)
49 changes: 48 additions & 1 deletion planet/api/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,13 +504,60 @@ def stats(self):
return stats


def create(client, mosaic=False, **kw):
class _OrderDownloadStage(_DStage):
def _task(self, t):
return t

def _do(self, task):
func = self._write_tracker(task, None)
writer = write_to_file(self._dest, func, overwrite=False)
self._downloads += 1
self._results.put((task,
self._client.download_location(task, writer)))


class _OrderDownloader(_Downloader):
def activate(self, items, asset_types):
pass

def _init(self, items, asset_types, dest):
client = self._client
dstage = _OrderDownloadStage(items, client, asset_types, dest)
self._dest = dest
self._stages.append(dstage)
self._apply_opts(vars())
self._completed = 0

def stats(self):
stats = {
'paging': False,
'activating': 0,
'pending': 0,
'complete': 0,
'downloading': 0,
'downloaded': '0.0MB',
}
if not self._stages:
return stats

dstage = self._stages[0]
mb_written = '%.2fMB' % (dstage._written / float(1024**2))
stats['downloading'] = dstage._downloads - self._completed
stats['downloaded'] = mb_written
stats['pending'] = dstage.work()
stats['complete'] = self._completed
return stats


def create(client, mosaic=False, order=False, **kw):
'''Create a Downloader with the provided client.

:param mosaic bool: If True, the Downloader will fetch mosaic quads.
:returns: :py:Class:`planet.api.downloader.Downloader`
'''
if mosaic:
return _MosaicDownloader(client, **kw)
elif order:
return _OrderDownloader(client, **kw)
else:
return _Downloader(client, **kw)
34 changes: 34 additions & 0 deletions planet/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,37 @@ class WFS3Features(AnalyticsPaged):
# differences in the structure of the response envelope result in paging
# slightly differently.
ITEM_KEY = 'features'


class Orders(Paged):
ITEM_KEY = 'orders'


class Order(JSON):
LINKS_KEY = '_links'
RESULTS_KEY = 'results'
LOCATION_KEY = 'location'

def get_results(self):
links = self.get()[self.LINKS_KEY]
results = links.get(self.RESULTS_KEY, None)
return results

def get_locations(self):
results = self.get_results()
locations = [r[self.LOCATION_KEY] for r in results]
return locations

def items_iter(self, limit):
'''Get an iterator of the 'items' in each order.
The iterator yields the individual items in the order.

:param int limit: The number of 'items' to limit to.
:return: iter of items in page
'''

locations = iter(self.get_locations())

# if limit is not None:
# locations = itertools.islice(locations, limit)
return locations
21 changes: 21 additions & 0 deletions planet/scripts/item_asset_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

_item_types = None
_asset_types = None
_bundles = None

# Default values here are used as a fallback
# In case the API fails to respond or takes too long.
Expand Down Expand Up @@ -62,6 +63,18 @@
'udm2', 'visual', 'visual_xml'
]

DEFAULT_BUNDLES = [u'all', u'all_udm2', u'analytic', u'analytic_sr',
u'analytic_sr_udm2', u'analytic_udm2', u'basic_analytic',
u'basic_analytic_nitf', u'basic_analytic_nitf_udm2',
u'basic_analytic_udm2', u'basic_panchromatic',
u'basic_panchromatic_dn', u'basic_uncalibrated_dn',
u'basic_uncalibrated_dn_nitf',
u'basic_uncalibrated_dn_nitf_udm2',
u'basic_uncalibrated_dn_udm2', u'panchromatic',
u'panchromatic_dn', u'panchromatic_dn_udm2',
u'pansharpened', u'pansharpened_udm2', u'uncalibrated_dn',
u'uncalibrated_dn_udm2', u'visual']


def _get_json_or_raise(url, timeout=11):
api_key = find_api_key()
Expand All @@ -88,3 +101,11 @@ def get_asset_types():
data = _get_json_or_raise(ASSET_TYPE_URL)
_asset_types = [a['id'] for a in data['asset_types']]
return _asset_types


def get_bundles():
global _bundles
if _bundles is None:
_bundles = DEFAULT_BUNDLES
# TODO if/when bundles defs are served by API we can grab them here
return _bundles
11 changes: 9 additions & 2 deletions planet/scripts/opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
NumberIn,
Range,
SortSpec,
StringIn
StringIn,
Bundle
)


Expand Down Expand Up @@ -77,11 +78,17 @@ def limit_option(default):
)

item_type_option = click.option(
'--item-type', multiple=True, required=True, type=ItemType(), help=(
'--item-type', multiple=False, required=True, type=ItemType(), help=(
'Specify item type(s)'
)
)

bundle_option = click.option(
'--bundle', multiple=False, required=True, type=Bundle(), help=(
'Specify bundle(s)'
)
)

asset_type_option = click.option(
'--asset-type', multiple=True, required=True, type=AssetType(), help=(
'Specify asset type(s)'
Expand Down
13 changes: 12 additions & 1 deletion planet/scripts/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from planet.api.utils import geometry_from_json
from planet.api.utils import strp_lenient
from planet.scripts.item_asset_types import get_item_types, get_asset_types, \
DEFAULT_ITEM_TYPES, DEFAULT_ASSET_TYPES
get_bundles, DEFAULT_ITEM_TYPES, DEFAULT_ASSET_TYPES, DEFAULT_BUNDLES


metavar_docs = {
Expand Down Expand Up @@ -118,6 +118,17 @@ def get_remote_choices(self):
return self.choices


class Bundle(_LenientChoice):
name = 'bundle'
allow_all = True

def __init__(self):
_LenientChoice.__init__(self, DEFAULT_BUNDLES)

def get_remote_choices(self):
return get_bundles()


class ItemType(_LenientChoice):
name = 'item-type'
allow_all = True
Expand Down
Loading