Ordering and Delivery

This notebook demonstrates ordering and download with the orders api. In this notebook, we check authentication by requesting an orders list, then we create an order for two Analytic PSScene4Band images. We poll for order success then download images individually. And finally, we create, poll, and download the same order delivered as a single zip file.

Reference information can be found at Ordering & Delivery.


In [1]:
import json
import os
import pathlib
import time

import requests
from requests.auth import HTTPBasicAuth

Authenticating


In [2]:
# API Key stored as an env variable
PLANET_API_KEY = os.getenv('PL_API_KEY')

In [3]:
orders_url = 'https://api.planet.com/compute/ops/orders/v2'

Curl example

To check your orders list and make sure you have the permissions you need, uncomment the following line to run curl


In [4]:
# !curl -L -H "Authorization: api-key $PLANET_API_KEY" $orders_url

Requests example

In this notebook, we will be using requests to communicate with the orders v2 API. First, we will check our orders list to make sure authentication and communication is working as expected.

We want to get a response code of 200 from this API call. To troubleshoot other response codes, see the List Orders AOI reference.


In [5]:
auth = HTTPBasicAuth(PLANET_API_KEY, '')
response = requests.get(orders_url, auth=auth)
response


Out[5]:
<Response [200]>

Now we will list the orders we have created thus far. Your list may be empty if you have not created an order yet.


In [6]:
orders = response.json()['orders']
len(orders)


Out[6]:
17

Ordering

In this example, we will order two PSScene4Band analytic images. For variations on this kind of order, see Ordering Data.

In this order, we request an analytic bundle. A bundle is a group of assets for an item. The analytic bundle for the PSScene4Band item contains 3 assets: the analytic image, the analytic xml file, and the udm. See the Product bundles reference to learn about other bundles and other items.

Place Order


In [10]:
# set content type to json
headers = {'content-type': 'application/json'}

In [11]:
request = {  
   "name":"simple order",
   "products":[
      {  
         "item_ids":[  
            "20151119_025740_0c74",
            "20151119_025741_0c74"
         ],
         "item_type":"PSScene4Band",
         "product_bundle":"analytic"
      }
   ],
}

In [20]:
def place_order(request, auth):
    response = requests.post(orders_url, data=json.dumps(request), auth=auth, headers=headers)
    print(response)
    order_id = response.json()['id']
    print(order_id)
    order_url = orders_url + '/' + order_id
    return order_url

In [22]:
order_url = place_order(request, auth)


<Response [202]>
44bab5ef-c584-4a1f-b583-505cd93e8c89

Cancel an Order


In [23]:
# report order state
requests.get(order_url, auth=auth).json()['state']


Out[23]:
'running'

In [24]:
oid = oids[0]
response = requests.put(order_url, auth=auth)
response


Out[24]:
<Response [200]>

In [27]:
# report order state - it could take a little while to cancel
requests.get(order_url, auth=auth).json()['state']


Out[27]:
'cancelled'

Poll for Order Success


In [28]:
# re-order since we canceled our last order
order_url = place_order(request, auth)


<Response [202]>
e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7

In [29]:
def poll_for_success(order_url, auth, num_loops=30):
    count = 0
    while(count < num_loops):
        count += 1
        r = requests.get(order_url, auth=auth)
        response = r.json()
        state = response['state']
        print(state)
        end_states = ['success', 'failed', 'partial']
        if state in end_states:
            break
        time.sleep(10)
        
poll_for_success(order_url, auth)


running
running
running
running
running
running
running
running
running
success

View Results


In [30]:
r = requests.get(order_url, auth=auth)
response = r.json()
results = response['_links']['results']

In [31]:
[r['name'] for r in results]


Out[31]:
['e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/manifest.json',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/20151119_025741_0c74_metadata.json',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS.tif',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_DN_udm.tif',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_metadata.xml',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/manifest.json',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/20151119_025740_0c74_metadata.json',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS.tif',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_DN_udm.tif',
 'e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_metadata.xml']

Download

Downloading each asset individually


In [32]:
def download_results(results, overwrite=False):
    results_urls = [r['location'] for r in results]
    results_names = [r['name'] for r in results]
    print('{} items to download'.format(len(results_urls)))
    
    for url, name in zip(results_urls, results_names):
        path = pathlib.Path(os.path.join('data', name))
        
        if overwrite or not path.exists():
            print('downloading {} to {}'.format(name, path))
            r = requests.get(url, allow_redirects=True)
            path.parent.mkdir(parents=True, exist_ok=True)
            open(path, 'wb').write(r.content)
        else:
            print('{} already exists, skipping {}'.format(path, name))

In [33]:
download_results(results)


10 items to download
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/manifest.json to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/manifest.json
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/20151119_025741_0c74_metadata.json to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/20151119_025741_0c74_metadata.json
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS.tif to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS.tif
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_DN_udm.tif to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_DN_udm.tif
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_metadata.xml to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/2/files/PSScene4Band/20151119_025741_0c74/analytic/20151119_025741_0c74_3B_AnalyticMS_metadata.xml
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/manifest.json to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/manifest.json
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/20151119_025740_0c74_metadata.json to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/20151119_025740_0c74_metadata.json
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS.tif to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS.tif
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_DN_udm.tif to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_DN_udm.tif
downloading e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_metadata.xml to data/e1ae4b94-3e38-48e8-9cdb-1bc3bae259b7/1/files/PSScene4Band/20151119_025740_0c74/analytic/20151119_025740_0c74_3B_AnalyticMS_metadata.xml

Downloading as a single zip

To download all of the order assets as a single zip, the order request needs to be changed slightly with delivery instructions. After that, polling and downloading are the same.


In [34]:
zip_delivery = {"delivery": {"single_archive": True, "archive_type": "zip"}}
request_zip = request.copy()
request_zip.update(zip_delivery)
request_zip


Out[34]:
{'delivery': {'archive_type': 'zip', 'single_archive': True},
 'name': 'simple order',
 'products': [{'item_ids': ['20151119_025740_0c74', '20151119_025741_0c74'],
   'item_type': 'PSScene4Band',
   'product_bundle': 'analytic'}]}

In [35]:
order_url = place_order(request_zip, auth)


<Response [202]>
ad3a2d3f-c98a-4afe-acb5-37e7c48f3799

In [36]:
poll_for_success(order_url, auth)


running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
running
success

In [39]:
r = requests.get(order_url, auth=auth)
response = r.json()
results = response['_links']['results']

In [40]:
download_results(results)


2 items to download
data/ad3a2d3f-c98a-4afe-acb5-37e7c48f3799/1/manifest.json already exists, skipping ad3a2d3f-c98a-4afe-acb5-37e7c48f3799/1/manifest.json
data/ad3a2d3f-c98a-4afe-acb5-37e7c48f3799/1/files/output.zip already exists, skipping ad3a2d3f-c98a-4afe-acb5-37e7c48f3799/1/files/output.zip