Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Previewing cloud-hosted geospatial data before downloading it

Why this workflow matters

Downloading a full imagery archive before you know whether it is even useful wastes time and disk space. Being able to search, preview, and inspect metadata first, and only download what you actually need, is a core skill for the data-acquisition side of your project, and it connects directly back to L02 – Data acquisition.


Core idea

Microsoft Planetary Computer hosts a large volume of geospatial data through a SpatioTemporal Asset Catalog (STAC) API. The geoai package wraps that API so you can search, visualize, and selectively download data without leaving Python.


Workflow

A. Browse available collections

pc_collection_list() returns every available STAC collection as a table, which is a useful way to see what exists before writing a specific search.

import geoai

collections = geoai.pc_collection_list()
print(f"Total collections: {len(collections)}")
collections.head(10)

Scanning the full table gets unwieldy once you know roughly what you want. Pass filter_by with a keyword to match against the collection id and narrow the results down to the collection you are after:

collections = geoai.pc_collection_list(filter_by={"id": "sentinel-3"})
print(f"Total collections: {len(collections)}")
collections.head(10)

B. Search for items

pc_stac_search() takes a collection ID, a bounding box in [west, south, east, north] format, and a time range. Each result is a STAC item with metadata and links to the underlying data.

landsat_items = geoai.pc_stac_search(
    collection="landsat-c2-l2",
    bbox=[121.4919, -30.7738, 121.5081, -30.7662], #  Western Australia, Kalgoorlie Super Pit
    time_range="2024-09-01/2024-11-30",
    query={"eo:cloud_cover": {"lt": 10}},
    max_items=10,
)
landsat_items

A single time_range only covers one continuous interval, so it cannot express “the same Sep–Nov window, repeated across several years” in one call. Loop over the years instead and concatenate the results into a single list:

years = [2021, 2022, 2023, 2024]
landsat_items = []
for y in years:
    landsat_items += geoai.pc_stac_search(
        collection="landsat-c2-l2",
        bbox=[121.4919, -30.7738, 121.5081, -30.7662],
        time_range=f"{y}-09-01/{y}-11-30",
        query={"eo:cloud_cover": {"lt": 10}},
        max_items=10,
    )

C. Check what data is in an item

A STAC item bundles several STAC assets, image bands, metadata, and more, and asset keys differ between collections, so it is worth checking before you write code that assumes a particular key exists.

geoai.pc_item_asset_list(landsat_items[0])

D. Preview without downloading it

view_pc_item() streams tiles from Planetary Computer’s tile server directly onto a leafmap map, so you can inspect a search result before committing to a download.

geoai.view_pc_item(
    item=landsat_items[0], 
    assets=["swir22", "nir08", "red"],
    backend="ipyleaflet",
)

E. Compute an index on the fly

For multispectral collections like Landsat, you can pass an expression to compute a spectral index server-side, without downloading anything. Here is the NDWI, a common measure of surface water:

geoai.view_pc_item(
    item=landsat_items[0],
    expression="(green-nir08)/(green+nir08)", 
    rescale="-0.5,0.5",     # 
    colormap_name="rdylbu",
    name="NDVI",
    backend="ipyleaflet",
)

F. Download only what you need

Once you have confirmed an item is useful, download specific assets rather than the whole collection.

geoai.pc_stac_download(
    landsat_items[0], 
    output_dir="../data/raw/Landsat", 
    assets=["nir08", "red"]
)

pc_stac_download() has no bounding-box or clipping option: it streams whichever assets you list straight to disk at full extent. The bbox you passed to pc_stac_search() only filtered which items matched spatially, it has no effect on what gets downloaded.

To actually restrict the download to a bounding box, read the (cloud-optimized) asset with rioxarray and clip it before writing to disk — this uses COG range requests, so it only pulls the bytes covering your bbox rather than the whole scene:

import os
import planetary_computer as pc
import rioxarray as rxr

bbox=[121.4919, -30.7738, 121.5081, -30.7662]  # west, south, east, north

def download_clipped(items, assets, bbox, output_dir="../data/raw/Landsat"):
    os.makedirs(output_dir, exist_ok=True)
    paths = {}
    for item in items:
        signed = pc.sign(item)
        for asset_key in assets:
            if asset_key not in signed.assets:
                continue
            url = signed.assets[asset_key].href
            da = rxr.open_rasterio(url, masked=True)
            clipped = da.rio.clip_box(*bbox, crs="EPSG:4326")
            out_path = os.path.join(output_dir, f"{item.id}_{asset_key}_clip.tif")
            clipped.rio.to_raster(out_path, driver="COG")
            paths.setdefault(item.id, {})[asset_key] = out_path
    return paths

clipped_paths = download_clipped(landsat_items, ["nir08", "red"], bbox)

G. Apply to other collections

The search → check assets → preview pattern from steps B–D works the same way for any collection in the catalog. The two dropdowns below work through it for NAIP aerial imagery and for a land-cover classification collection.


Python reactivation

Search results behave like a list of objects (landsat_items[0]), and filters are passed as dictionaries (query={"eo:cloud_cover": {"lt": 10}}), the same nested-dictionary pattern you used for JSON-like structures in SDS210. If a search returns nothing, check your bounding box and time range before assuming the collection has no data.


Common pitfalls


Mini task

Search a Planetary Computer collection using the rough bounding box of your own project’s study area. List the available assets for the first result, and preview it on a map.


Further reading


Key takeaways