Example Usage

from capella_console_client import CapellaConsoleClient

authenticate

interactive prompt

# you will be prompted for console user (user@email.com)/ password before authenticating
client = CapellaConsoleClient()

# chatty client
client = CapellaConsoleClient(verbose=True)

provide user credentials

from getpass import getpass

# user credentials on api.capellaspace.com
email = input("your email on api.capellaspace.com:").strip()
pw = getpass("your password on api.capellaspace.com:").strip()

# authenticate with user and password
client = CapellaConsoleClient(email=email, password=pw)

JWT

# already have a valid JWT token? no problem
token_client = CapellaConsoleClient(token="<token>", verbose=True)

# don't want to validate the token (saves an API call)
bold_token_client = CapellaConsoleClient(token="<token>", no_token_check=True)

token refresh with auto retry

# issued access tokens have an expiration of 1h
client = CapellaConsoleClient(email=email, password=pw, verbose=True)

# DON'T RUN THIS ;)
import time
time.sleep(60 * 60)
# token expired in the interim

# capella-console-client will refresh your access token and retry the failed request
me = client.whoami()
assert me is not None
print("All good")

Output

2021-10-07 11:00:24,590 - 🛰️  Capella Space 🐐 - INFO - successfully authenticated as user@capellaspace.com
2021-10-07 11:00:24,690 - root - ERROR - Request: GET https://api.capellaspace.com/user - Status 401 - Response: {'error': {'message': 'Invalid token.', 'code': 'INVALID_TOKEN'}}
2021-10-07 11:00:24,690 - 🛰️  Capella Space 🐐 - INFO - refreshing access token
All good

visualize search results

from pathlib import Path
import json

results = client.search(
    instrument_mode="spotligh",
    product_type="GEO",
    sortby="-datetime"
)
# store stac items in geojson FeatureCollection
feature_collection = results.to_feature_collection()

# write to disk
feature_collection_path = Path('CAPELLA_SP_GEOs.geojson')
feature_collection_path.write_text(json.dumps(feature_collection))

# open e.g. in QGIS

order products

Issue the following snippets to submit a (purchasing) order by providing STAC items or STAC ids.

# submit order with stac items
order_id = client.submit_order(items=capella_spotlight_olympic_NP_geo)

# alternatively order by STAC ids
first_two_ids = [item["id"] for item in capella_spotlight_olympic_NP_geo[:2]]
order_id = client.submit_order(stac_ids=first_two_ids)

# since orders expire you can alternatively check prior if an active order already exists
# instead of creating a new order - charges won't be applied twice anyways
order_id = client.submit_order(items=capella_spotlight_olympic_NP_geo,
                               check_active_orders=True)

download

Download assets of previously ordered products to local disk.

# download all products of an order to /tmp
product_paths = client.download_products(
    order_id=order_id,
    local_dir="/tmp",
)

# 🕒 don't like parallel downloads? 🕒 - set threaded = False in order to fetch the product assets serially
product_paths = client.download_products(
    order_id=order_id,
    local_dir="/tmp",
    threaded=False
)

# ⌛ like to watch progress bars? ⌛ - set show_progress = True in order to get feedback on download status (time remaining, transfer stats, ...)
product_paths = client.download_products(
    order_id=order_id,
    local_dir="/tmp",
    show_progress=True,
)

# the client is respectful of your local files and does not override them by default
# but can be instructed to do so
local_thumb_path = client.download_products(
    order_id=order_id,
    local_dir="/tmp",
    show_progress=True,
    override=True
)

Output

2021-06-21 20:28:16,734 - 🛰️  Capella Space 🐐 - INFO - downloading product CAPELLA_C03_SP_SLC_HH_20210621202423_20210621202425 to /tmp/CAPELLA_C03_SP_SLC_HH_20210621202423_20210621202425
CAPELLA_C03_SP_GEO_HH_20210603175705_20210603175729_thumb.png       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0%  211.3/211.3 KB    499.7 kB/s   0:00:00
CAPELLA_C03_SP_GEO_HH_20210619045726_20210619045747_thumb.png       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0%  307.1/307.1 KB    1.4 MB/s     0:00:00
CAPELLA_C03_SP_GEO_HH_20210619180117_20210619180140_thumb.png       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0%  271.6/271.6 KB    1.1 MB/s     0:00:00
CAPELLA_C03_SP_GEO_HH_20210627180259_20210627180321_extended.json   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0%    20,426/-1 bytes   200.2 kB/s   0:00:00
CAPELLA_C03_SP_GEO_HH_20210603175705_20210603175729_extended.json   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0%    21,536/-1 bytes   293.8 kB/s   0:00:00
CAPELLA_C03_SP_GEO_HH_20210619180117_20210619180140_extended.json   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0%    20,650/-1 bytes   122.0 kB/s   0:00:00
CAPELLA_C03_SP_GEO_HH_20210627180259_20210627180321_thumb.png       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0%  316.7/316.7 KB    1.3 MB/s     0:00:00
CAPELLA_C03_SP_GEO_HH_20210603175705_20210603175729.tif             ━╸━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.6%    13.2/237.4 MB     2.2 MB/s     0:01:42
CAPELLA_C03_SP_GEO_HH_20210619045726_20210619045747_extended.json   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0%    22,002/-1 bytes   196.9 kB/s   0:00:00
CAPELLA_C03_SP_GEO_HH_20210627180259_20210627180321.tif             ━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.0%    11.0/360.9 MB     1.9 MB/s     0:03:04
CAPELLA_C03_SP_GEO_HH_20210619045726_20210619045747.tif             ╸━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.7%    9.8/359.0 MB      1.8 MB/s     0:03:18

By default the respective product assets are saved into separate product directories, i.e.

/tmp/<stac_id_1>/<stac_id_1>.tif
/tmp/<stac_id_1>/<stac_id_1>_thumb.png
/tmp/<stac_id_1>/<stac_id_1>_extended.json
/tmp/<stac_id_2>/<stac_id_2>.tif
...

If you prefer a flat hierarchy set separate_dirs to False:

product_paths = client.download_products(
    order_id=order_id,
    separate_dirs=False,
)

download products filtered by product type

# download only GEO product
product_paths = client.download_products(
   order_id=order_id,
   product_types=["GEO"]
)

# download only SLC and GEO product
product_paths = client.download_products(
   order_id=order_id,
   product_types=["SLC", "GEO"]
)

download products filtered by asset type

# download only thumbnails
product_paths = client.download_products(
   order_id=order_id,
   include=["thumbnail"]
)

# 'include' / 'exclude' can also be a string if only one provided
product_paths = client.download_products(
   order_id=order_id,
   include="thumbnail"
)

# download only raster (VV or HH)
product_paths = client.download_products(
   order_id=order_id,
   include="raster"
)

# download all assets except raster
product_paths = client.download_products(
   order_id=order_id,
   exclude="raster"
)

# explicit DENY overrides explicit ALLOW --> the following would only fetch thumbnails
product_paths = client.download_products(
   order_id=order_id,
   include=["raster", "thumbnail"]
   exclude="raster"
)

order and download products of a tasking request

Requirement: you have previously issued a tasking request that is in ‘completed’ state

tasking_request_id = "27a71826-7819-48cc-b8f2-0ad10bee0f97"  # NOTE: provide valid tasking_request_id

# download ALL products
product_paths = client.download_products(
    tasking_request_id=tasking_request_id,
)

# download only GEO product
product_paths = client.download_products(
    tasking_request_id=tasking_request_id,
    product_types=["GEO"]
)

order and download products of a collect

collect_id = "27a71826-7819-48cc-b8f2-0ad10bee0f97"  # NOTE: provide valid collect_id

# download ALL products
product_paths = client.download_products(
    collect_id=collect_id,
)

# download only GEC product
product_paths = client.download_products(
    collect_id=collect_id,
    product_types=["GEC"],
)

review order

If you would like to review the cost of an order before you submission, issue:

order_details = client.review_order(items=capella_spotlight_olympic_NP_geo)
print(order_details['orderDetails']['summary'])

presigned items

In order to directly load assets (imagery or metadata) into memory you need to request signed S3 URLs first.

items_presigned = client.get_presigned_items(order_id)

# alternatively presigned assets can also be filtered - e.g. give me the presigned assets of 2 specific STAC ids
first_two_ids = [item["id"] for item in capella_spotlight_olympic_NP_geo[:2]]
items_presigned = client.get_presigned_items(order_id,
                                               stac_ids=first_two_ids)

# sort presigned assets by list of stac ids
sorted_stac_ids = sorted([s['id'] for s in capella_spotlight_olympic_NP_geo])
items_presigned_sorted = client.get_presigned_items(order_id,
                                                    sort_by=sorted_stac_ids)

See read imagery or read metadata for more information.

download single product

# download a specific product with download_product (SINGULAR)
product_paths = client.download_product(assets_presigned[0], local_dir="/tmp", override=True)

download single asset

single assets can be downloaded to gven paths

# download thumbnail
thumb_presigned_href = assets_presigned[0]["thumbnail"]["href"]
dest_path = "/tmp/thumb.png"
local_thumb_path = client.download_asset(thumb_presigned_href, local_path=dest_path)

# assets are saved into OS specific temp directory if `local_path` not provided
raster_presigned_href = assets_presigned[0]["HH"]["href"]
local_raster_path = client.download_asset(raster_presigned_href)


from pathlib import Path
assert local_thumb_path == Path(dest_path)

list orders

Issue the following snippet to view the ordering history

# list all orders
all_orders = client.list_orders()

# list all active orders
all_active_orders = client.list_orders(is_active=True)

# list specific order(s) by order id
specific_order_id = all_orders[0]["orderId"]
specific_orders = client.list_orders(order_ids=[specific_order_id])

create tasking request

Create a tasking request with basic parameters

# Create basic tasking request with a geometry (only required parameter)
client.create_tasking_request(
    geometry=geojson.Polygon(
        [
            [
                [11.148216220469152, 49.59672249842626],
                [11.148216220469152, 49.55415435337187],
                [11.219621049225651, 49.55415435337187],
                [11.219621049225651, 49.59672249842626],
                [11.148216220469152, 49.59672249842626],
            ]
        ]
    )
)

# Add a couple of parameters to help you track/identify it better
client.create_tasking_request(
    geometry=geojson.Polygon(
        [
            [
                [11.148216220469152, 49.59672249842626],
                [11.148216220469152, 49.55415435337187],
                [11.219621049225651, 49.55415435337187],
                [11.219621049225651, 49.59672249842626],
                [11.148216220469152, 49.59672249842626],
            ]
        ]
    ),
    name="I<3SAR",
    description="My first tasking request"
)

create repeating tasking request

Create a repeating tasking request with basic parameters

# Create basic repeating tasking request with a geometry (only required parameter)
client.create_repeat_request(
    geometry=geojson.Polygon(
        [
            [
                [11.148216220469152, 49.59672249842626],
                [11.148216220469152, 49.55415435337187],
                [11.219621049225651, 49.55415435337187],
                [11.219621049225651, 49.59672249842626],
                [11.148216220469152, 49.59672249842626],
            ]
        ]
    )
)

# Add a couple of parameters to help you track/identify it better
client.create_repeat_request(
    geometry=geojson.Polygon(
        [
            [
                [11.148216220469152, 49.59672249842626],
                [11.148216220469152, 49.55415435337187],
                [11.219621049225651, 49.55415435337187],
                [11.219621049225651, 49.59672249842626],
                [11.148216220469152, 49.59672249842626],
            ]
        ]
    ),
    name="I<3SAR",
    description="My first repeat request"
)

# Note that you can only define either repeat_end OR repetition_count, not both. The following request will fail:
client.create_repeat_request(
    geometry=geojson.Polygon(
        [
            [
                [11.148216220469152, 49.59672249842626],
                [11.148216220469152, 49.55415435337187],
                [11.219621049225651, 49.55415435337187],
                [11.219621049225651, 49.59672249842626],
                [11.148216220469152, 49.59672249842626],
            ]
        ]
    ),
    name="I<3SAR",
    description="My first repeat request",
    repeat_start="2023-12-24 3:30 PM"
    repeat_end="2023-12-31 3:30 PM",
    repetition_count=23
)

search tasking request

tasking_request_id = "27a71826-7819-48cc-b8f2-0ad10bee0f97"  # provide valid taskingrequest_id

# get task info
task = client.get_task(tasking_request_id)

# was it completed?
client.is_task_completed(task)

advanced tasking request search

# get ALL completed tasking requests of user
user_completed_trs = client.list_tasking_requests(status="completed")

# get all COMPLETED tasking requests of ORG (requires org manager/ admin role)
org+completed_trs = client.list_tasking_requests(
    for_org=True,
    status="completed"
)

# get all completed tasking requests of org SUBMITTED AFTER 2022-12-01 (UTC)
org_completed_trs_submitted_dec_22 = client.list_tasking_requests(
    for_org=True,
    status="completed",
    submission_time__gt=datetime.datetime(2022, 12, 1)
)

read imagery

Given a presigned asset href (see presigned items) load imagery into memory

NOTE: requires rasterio (not part of this package)

import rasterio

# raster profile
raster_presigned_href = assets_presigned[0]["HH"]["href"]
with rasterio.open(raster_presigned_href) as ds:
    print(ds.profile)

# read chunk of raster
with rasterio.open(raster_presigned_href) as ds:
    chunk = ds.read(1, window=rasterio.windows.Window(2000, 2000, 7000, 7000))
print(chunk.shape)

# read thumbnail
thumb_presigned_href = assets_presigned[0]["thumbnail"]["href"]
with rasterio.open(thumb_presigned_href) as ds:
    thumb = ds.read(1)
print(thumb.shape)

read metadata

import httpx

# read extended metadata .json
metadata_presigned_href = assets_presigned[0]["metadata"]["href"]
metadata = httpx.get(metadata_presigned_href).json()