Skip to content

Catalog search

The UmbraCatalog is the entry point for discovery: it searches Umbra's static STAC catalog by area, date range, product type, place name, or polygon, and can read through a local index for fast, repeatable, offline queries.

A full scan of the open archive should not walk the bucket and should not page the hosted /search. Download the weekly snapshot with umbra index fetch (flags --db, --url, --insecure-url, --if-changed, and --force; --force only with --if-changed) from catalog.db, then search it with umbra search --local. The local index page says what each flag does. catalog.sha256 is the GNU sha256sum manifest beside that file. umbra index fetch checks catalog.db against it when the manifest is present, and warns and continues when the manifest is missing.

UmbraCatalog

UmbraCatalog(bucket=S3_BUCKET, region=S3_REGION, session=None, *, token=None, archive_url=CANOPY_ARCHIVE_URL, collections=None)

Client for searching Umbra SAR data.

By default this searches Umbra's open data by crawling the public S3 bucket (a static STAC catalog with no search API). Pass a Canopy token and the same :meth:search interface instead queries Umbra's authenticated commercial archive over its real STAC API (:data:~umbra_py.constants.CANOPY_ARCHIVE_URL)::

# open data (default) -- no account needed
UmbraCatalog().search(area="Centerfield", limit=5)

# commercial archive -- same call, one extra argument
UmbraCatalog(token="...").search(bbox=bbox, start="2024", limit=5)

Both paths yield :class:~umbra_py.UmbraItem objects, so every downstream verb (download, quicklook, change, chips, ...) works unchanged against either archive. That is the funnel made literal: a user onboarded on the free bucket is already holding the tool they'd use as a paying customer. Get a token from https://docs.canopy.umbra.space/.

search

search(*, bbox=None, intersects=None, start=None, end=None, product_types=None, area=None, fuzzy=False, polarizations=None, min_incidence=None, max_incidence=None, max_resolution=None, canonical=None, limit=None, max_per_task=None)

Yield items matching the filters.

Parameters

bbox: (min_lon, min_lat, max_lon, max_lat) footprint filter. intersects: A polygon geometry (the exterior-ring form from :func:umbra_py._geometry.parse_geometry); keep only items whose footprint intersects it. A tighter spatial filter than the rectangular bbox -- the standard STAC intersects. Combines with bbox (both must match) when both are given. start, end: Inclusive acquisition-date bounds. Accepts date / datetime objects or ISO YYYY-MM-DD strings. The walker still has to list each task to discover what's published in range. Named tasks/ is tens of directories; task-data/ is thousands of UUID directories, so an unconstrained live walk is slow -- provide limit to stop as soon as you have enough, and prefer a local index for repeats. product_types: Keep only items exposing at least one of these assets (e.g. ["GEC"]). area: Case-insensitive substring matched against each sar-data/tasks/<task>/ or sar-data/task-data/<id>/ directory name. Named campaigns (e.g. "Centerfield, Utah") live under tasks/; UUID collects live under task-data/. area="centerfield" returns just that named site's acquisitions. Non-matching task directories are skipped before they're listed, so this also makes the search much faster -- the ergonomic way to gather the co-located passes a change composite needs. Place / bbox search is what finds a UUID collect whose directory name is not a place. fuzzy: Widen area from a literal substring to a deterministic token-wise fuzzy match (:func:umbra_py.fuzzy.task_matches): word-order- and punctuation-independent, tolerant of a small typo, and a strict superset of the substring match (it never drops a result). So area="utah centerfield" or area="centrfield" still reaches "Centerfield, Utah". No model call -- the C1 deterministic first step. polarizations: Keep only items exposing at least one of these polarizations (case-insensitive, e.g. ["VV"]) -- the SAR-native filter that keeps a change comparison like-with-like (HH and VV image different physics). An item with no polarization metadata is excluded. min_incidence, max_incidence: Inclusive bounds (degrees) on the view incidence angle (:attr:UmbraItem.incidence_angle). An item with no incidence metadata is excluded when either bound is set. max_resolution: Keep only items at least this fine -- both range and azimuth resolution <= max_resolution metres. An item missing either resolution value is excluded. See :meth:UmbraItem.matches_filters for the exact acquisition-property semantics (a set filter excludes items lacking that property, matching the STAC Query extension). canonical: True keeps items that are the canonical copy of a republished collect; False keeps only the other copies. None (the default) keeps every item. A live sidecar has not been linked, so it counts as canonical until an index build stamps the field. See :mod:umbra_py.duplicates. limit: Stop after yielding this many items. max_per_task: Cap the number of items yielded from any one sar-data/tasks/<task>/ or sar-data/task-data/<id>/ directory. Each task is a tasking campaign over the same area, so max_per_task=1 swaps the usual "every revisit of a few sites" output for "one acquisition per distinct site" -- much better diversity on a map.

Notes

When this catalog was created with a Canopy token, the search runs against Umbra's commercial STAC API instead of the open bucket. The filters mean the same thing; bbox and the date bounds are sent to the API, while product_types and area/fuzzy are applied to the returned items (exactly as they are on the open-bucket path), so the interface is identical across both archives.

has_matching_task

has_matching_task(area, *, fuzzy=False)

Whether area matches a task directory (or a Canopy item).

Directory names only: a hit does not download a sidecar. The search that follows lists the prefixes again. A Canopy catalog has no task directories, so it asks :meth:search for one item.

get_item

get_item(item_id)

Fetch a single acquisition from the Canopy commercial archive by id.

The keyed-retrieval complement to :meth:search's listing: given a STAC item id, return that one :class:~umbra_py.UmbraItem, or None when the archive has no such item. It is implemented with the STAC API ids search extension over the same /archive/search endpoint :meth:search already POSTs to -- POST {"ids": [item_id], "limit": 1} -- so it introduces no new endpoint to guess and stays offline-testable against a mocked API, exactly like the search path. Bearer auth, the helpful 401/403 "token rejected" message and the 500 wrap are all inherited from :meth:_archive_page.

Requires a Canopy token. The open bucket is a static catalog with no id-to-item index, so a keyed lookup isn't meaningful there -- resolve an open-data item from its sidecar URL instead (:meth:UmbraItem.from_dict / umbra info <url>) or from a built index (:meth:umbra_py.CatalogIndex.get).