Skip to content

Data layer

Body-agnostic building blocks shared by every instrument dataset: STAC and PDS ODE search, windowed raster reads, the target grid, and the disposable cache. Three sources back instrument datasets — see Instrument datasets for which each instrument uses:

  • STAC (astrofetch.data.stac): the USGS Astrogeology Analysis Ready Data catalog, searched via pystac-client.
  • PDS ODE (astrofetch.data.ode): the NASA PDS Orbital Data Explorer REST API, for instruments (LROC, LOLA, M3, ...) the STAC catalog does not carry.
  • Fixed mosaics: a handful of instruments are published as one global (or near-global) file rather than many searchable items; these are read directly from a well-known URL in astrofetch.data.endpoints, no search.

All three reproject through the same windowed-read path and share the same sample-dict contract.

Target grid

astrofetch.data.grid

Target grid definition and unit helpers (body-agnostic).

A :class:TargetGrid is the common raster every layer is reprojected onto so that channels coregister exactly. Phase 1 uses a plate-carree (equirectangular) grid in the IAU 2015 Moon geographic CRS; data/raster.py reprojects each source COG — whatever its native projection, equirectangular or polar stereographic — onto it. Keeping the grid geographic means the bbox a caller passes and the crs a sample advertises are the same coordinate system.

BBox module-attribute

BBox = tuple[float, float, float, float]

(west, south, east, north) in degrees.

GEOGRAPHIC_CRS module-attribute

GEOGRAPHIC_CRS = 'IAU_2015:30100'

Ocentric IAU 2015 Moon geographic CRS (degrees); the target grid CRS.

MOON_RADIUS_M module-attribute

MOON_RADIUS_M = 1737400.0

IAU 2015 Moon sphere radius in metres (IAU code 30100; see any ARD COG WKT).

TargetGrid dataclass

A fixed output raster: a bbox rasterized to width x height pixels.

Parameters:

Name Type Description Default
bbox BBox

(west, south, east, north) in degrees, west < east, south < north.

required
width int

output width in pixels.

required
height int

output height in pixels.

required
crs str

grid CRS; defaults to the IAU 2015 Moon geographic CRS.

GEOGRAPHIC_CRS

transform property

transform: Affine

Affine mapping pixel (col, row) to grid CRS coordinates.

meters_to_degrees

meters_to_degrees(
    meters: float, latitude: float
) -> tuple[float, float]

Approximate the (dlon, dlat) degree span of a ground distance in metres.

Spherical arc length on the IAU 2015 Moon sphere, used only to size a sampling window from patch_size * resolution; the exact pixel geometry is fixed later by :class:TargetGrid, so this need only be close. Longitude degrees shrink with cos(latitude).

Parameters:

Name Type Description Default
meters float

ground distance in metres.

required
latitude float

latitude in degrees where the span is measured.

required

Returns:

Type Description
tuple[float, float]

(dlon, dlat) span in degrees.

Raster reads

astrofetch.data.raster

Windowed COG reads reprojected onto a :class:TargetGrid (rasterio).

Reads only the region of a Cloud Optimized GeoTIFF that covers the grid — via HTTP range requests against the right overview level — reprojects it onto the grid's CRS and resolution, applies the band's scale/offset to recover physical values, and returns the data with a boolean validity mask (False where the source had nodata or simply does not cover the grid).

All map-projection and resampling math is delegated to rasterio/GDAL (AGENTS rule 1): this module wires it up, it does not reimplement it.

read_window

read_window(
    href: str,
    grid: TargetGrid,
    band: int = 1,
    resampling: Resampling = Resampling.bilinear,
    nodata_override: float | None = None,
) -> tuple[np.ndarray, np.ndarray]

Read one COG band, reprojected onto grid, in physical units.

Parameters:

Name Type Description Default
href str

COG URL or local path.

required
grid TargetGrid

output grid; defines CRS, size, and extent.

required
band int

1-based band index to read.

1
resampling Resampling

resampling used when reprojecting to the grid.

bilinear
nodata_override float | None

nodata value to use in place of the source's own (which may be unset). Needed for a handful of PDS products that omit nodata from their label even though the raster does not cover its full extent (unwarped pixels would otherwise silently read back as valid zeros); prefer the source's own declared nodata whenever a product provides one.

None

Returns:

Type Description
ndarray

(image, mask) where image is a (grid.height, grid.width)

ndarray

float32 array of physical values (scale/offset applied) with invalid

tuple[ndarray, ndarray]

pixels set to 0, and mask is a same-shaped bool array, True

tuple[ndarray, ndarray]

where the pixel is valid.

Raises:

Type Description
EndpointError

the COG could not be opened or read.

read_full

read_full(
    href: str,
    window: Window | None = None,
    bands: list[int] | None = None,
) -> tuple[np.ndarray, np.ndarray]

Read a raster's own pixels with no reprojection, in physical units.

Unlike :func:read_window, this does not warp onto a :class:TargetGrid -- it reads the source in its own native geometry, for rasters that have no map projection to warp to (e.g. raw camera-frame swaths). Used by :mod:astrofetch.moon.granules.

Parameters:

Name Type Description Default
href str

raster URL or local path.

required
window Window | None

pixel window (col_off, row_off, width, height) to read; None reads the full raster.

None
bands list[int] | None

1-based band indices to read; None reads every band.

None

Returns:

Type Description
ndarray

(image, mask) where image is a (bands, height, width)

ndarray

float32 array of physical values (per-band scale/offset applied)

tuple[ndarray, ndarray]

with invalid pixels set to 0, and mask is a same-shaped bool

tuple[ndarray, ndarray]

array, True where the pixel is valid.

Raises:

Type Description
EndpointError

the raster could not be opened or read.

astrofetch.data.stac

STAC search against the USGS ARD catalog (pystac-client), politely.

Wraps pystac-client with a retrying, backed-off HTTP session — AGENTS rule 5: never hammer archive servers — and normalizes failures into :class:EndpointError so callers never have to know pystac-client's exception surface. A single client is reused across searches (one polite connection pool, not one per request).

catalog cached

catalog(root: str = STAC_API_ROOT) -> Client

Open (and memoize) the STAC API client for root.

Raises:

Type Description
EndpointError

the catalog root could not be opened.

find_asset_hrefs

find_asset_hrefs(
    collection: str,
    asset_key: str,
    bbox: BBox,
    max_items: int = 20,
    root: str = STAC_API_ROOT,
) -> list[str]

Return hrefs of one asset across items of collection intersecting bbox.

Parameters:

Name Type Description Default
collection str

STAC collection id to search.

required
asset_key str

asset key to pull from each matching item, e.g. "dtm".

required
bbox BBox

(west, south, east, north) in degrees.

required
max_items int

cap on items returned; bounds the request volume per read.

20
root str

STAC API root; defaults to the configured USGS ARD catalog.

STAC_API_ROOT

Returns:

Type Description
list[str]

Asset hrefs, one per intersecting item (possibly empty if none overlap).

Raises:

Type Description
EndpointError

the search failed or a matched item lacks asset_key.

astrofetch.data.ode

Product search against the NASA PDS Orbital Data Explorer (ODE), politely.

Mirrors :mod:astrofetch.data.stac: a single retrying, backed-off HTTP session (AGENTS rule 5) and failures normalized into :class:EndpointError, so callers never have to know ODE's JSON quirks — a lone result comes back as a dict instead of a list, an empty result is the string "No Products Found" instead of an empty list, and errors are HTTP 200 responses with an error message in the body.

ODEFile

Bases: NamedTuple

One file attached to an ODE product.

type instance-attribute

type: str

ODE file role, e.g. "Product", "Browse", "Derived".

ODEProduct

Bases: NamedTuple

One ODE product: its id, files, footprint, and raw metadata.

bbox instance-attribute

bbox: BBox | None

(west, south, east, north) in degrees, -180 to 180; None when the footprint is not representable as a simple bbox (crosses the antimeridian, or spans exactly 360 degrees of longitude).

metadata instance-attribute

metadata: dict[str, Any]

Raw per-product ODE metadata, unmodified.

query_products

query_products(
    ihid: str,
    iid: str,
    pt: str,
    bbox: BBox,
    max_products: int = 20,
    product_id: str | None = None,
    root: str = ODE_API_ROOT,
) -> list[ODEProduct]

Search ODE for products of one instrument and product type in bbox.

Parameters:

Name Type Description Default
ihid str

ODE instrument host id, e.g. "LRO".

required
iid str

ODE instrument id, e.g. "LROC".

required
pt str

ODE product type, e.g. "SDNDTM".

required
bbox BBox

(west, south, east, north) in degrees, -180 to 180.

required
max_products int

cap on products returned; bounds request volume and paging (fetched in pages of up to 100).

20
product_id str | None

ODE productid wildcard filter (* matches any substring), e.g. "*wac_gld100*". Some product types mix many unrelated products (rendered visualizations, per-orbit granules, other parameters) under one pt, so a bbox-only search can bury the products actually wanted far past any reasonable max_products cap; narrowing server-side with a product id pattern is what keeps that search small and polite (rule 5) instead of paging through everything.

None
root str

ODE API root; defaults to the configured endpoint.

ODE_API_ROOT

Returns:

Type Description
list[ODEProduct]

Matching products, possibly empty if none overlap bbox.

Raises:

Type Description
EndpointError

the query failed or ODE reported an error.

Example

from astrofetch.data.ode import query_products query_products("LRO", "LROC", "SDNDTM", bbox=(3.0, 25.5, 4.5, 26.5)) # doctest: +SKIP [ODEProduct(pdsid='sdp.nac_dtm.apollo15...', ...), ...]

match_files

match_files(
    files: tuple[ODEFile, ...],
    pattern: str,
    file_type: str | None = "Product",
) -> list[str]

Return URLs of files whose name matches pattern and file_type.

Parameters:

Name Type Description Default
files tuple[ODEFile, ...]

files to filter, typically product.files.

required
pattern str

regex, matched against the filename with fullmatch and case-insensitively (ODE filenames are inconsistently cased).

required
file_type str | None

required ODE file role, e.g. "Product"; None skips this filter.

'Product'

Returns:

Type Description
list[str]

Matching URLs, sorted by filename for deterministic ordering.

find_file_urls

find_file_urls(
    ihid: str,
    iid: str,
    pt: str,
    pattern: str,
    bbox: BBox,
    max_products: int = 20,
    file_type: str | None = "Product",
    product_id: str | None = None,
    root: str = ODE_API_ROOT,
) -> list[str]

Return file URLs matching pattern across products in bbox.

The ODE analogue of :func:astrofetch.data.stac.find_asset_hrefs: a product bundle can contain many files (data, browse, derived), so this searches products then filters their files by name and role. A product with no matching file is not an error — masks report coverage truthfully instead.

Parameters:

Name Type Description Default
ihid str

ODE instrument host id, e.g. "LRO".

required
iid str

ODE instrument id, e.g. "LROC".

required
pt str

ODE product type, e.g. "SDNDTM".

required
pattern str

regex matched against filenames, case-insensitive.

required
bbox BBox

(west, south, east, north) in degrees, -180 to 180.

required
max_products int

cap on products searched.

20
file_type str | None

required ODE file role; None skips this filter.

'Product'
product_id str | None

ODE productid wildcard filter; see :func:query_products.

None
root str

ODE API root; defaults to the configured endpoint.

ODE_API_ROOT

Returns:

Type Description
list[str]

Matching URLs, product order then filename order.

Raises:

Type Description
EndpointError

the search failed or ODE reported an error.

Cache

astrofetch.data.cache

Disposable local cache for reprojected layer windows.

Never load-bearing (AGENTS rule 4): every entry is re-fetchable from the archive and safe to delete at any time. A key is a hash of the layer id and the exact target grid, so an identical request skips the network on a warm cache while any change to bbox, size, or CRS misses cleanly.

WindowCache

Filesystem cache of (image, mask) arrays keyed by layer and grid.

Parameters:

Name Type Description Default
root str | Path | None

cache directory; defaults to :func:default_cache_dir.

None

get

get(
    layer: str, grid: TargetGrid
) -> tuple[np.ndarray, np.ndarray] | None

Return the cached (image, mask) for layer/grid, or None.

put

put(
    layer: str,
    grid: TargetGrid,
    image: ndarray,
    mask: ndarray,
) -> None

Store (image, mask) for layer/grid, replacing any entry.

clear

clear() -> None

Delete every cache entry. The cache is disposable; this is always safe.

default_cache_dir

default_cache_dir() -> Path

Cache root: $ASTROFETCH_CACHE if set, else an XDG cache subdir.

Endpoints

astrofetch.data.endpoints

External archive endpoints — the ONLY place URLs live (AGENTS rule 2).

Every COG asset href is discovered through the STAC API at request time, so the API root below is the single external URL AstroFetch hard-codes. When a service moves (as QuickMap's domain once did), this is the only file to change.

STAC_API_ROOT module-attribute

STAC_API_ROOT = 'https://stac.astrogeology.usgs.gov/api/'

USGS Astrogeology Analysis Ready Data STAC API root (pystac-client entry).

ODE_API_ROOT module-attribute

ODE_API_ROOT = 'https://oderest.rsl.wustl.edu/live2/'

NASA PDS Orbital Data Explorer (ODE) REST API root, Washington Univ. St. Louis. Used to search PDS3/PDS4 products (LROC, LOLA, M3, ...) by instrument and bounding box; the USGS ARD STAC catalog does not carry these instruments. Last verified 2026-07-20.

LROC_WAC_MOSAIC_100M_URL module-attribute

LROC_WAC_MOSAIC_100M_URL = "https://asc-pds-services.s3.us-west-2.amazonaws.com/mosaic/Lunar_LRO_LROC-WAC_Mosaic_global_100m_June2013.tif"

LRO LROC WAC global morphology mosaic, 100 m/px, equirectangular.

Not a Cloud Optimized GeoTIFF (striped, no overviews): windowed reads at native resolution (100 m) are efficient; heavily downsampled reads are not. Last verified 2026-07-20.

LOLA_DEM_128_URL module-attribute

LOLA_DEM_128_URL = "https://pds-geosciences.wustl.edu/lro/lro-l-lola-3-rdr-v1/lrolol_1xxx/data/lola_gdr/cylindrical/float_img/ldem_128_float.lbl"

LOLA global DEM, 128 px/degree (~237 m/px at the equator), float32 metres above the IAU 2015 Moon reference sphere. Detached PDS3 label; GDAL's PDS driver resolves the sibling .img over HTTPS. Last verified 2026-07-20.

SLDEM2015_URL module-attribute

SLDEM2015_URL = "https://pds-geosciences.wustl.edu/lro/lro-l-lola-3-rdr-v1/lrolol_1xxx/data/sldem2015/global/float_img/sldem2015_128_60s_60n_000_360_float.lbl"

SLDEM2015: LOLA + Kaguya Terrain Camera co-registered DEM, 128 px/degree, float32 metres. Source coverage is 60S-60N only (not a bug); windows outside that band read back with mask all False. Last verified 2026-07-20.

Errors

astrofetch.errors

AstroFetch exception hierarchy.

Public errors live here so callers can except astrofetch.errors.EndpointError without importing internal modules.

AstroFetchError

Bases: Exception

Base class for every error AstroFetch raises on purpose.

EndpointError

Bases: AstroFetchError

A remote archive endpoint failed or returned something unusable.

Carries the offending endpoint (a collection id, URL, or asset href) and always points back at astrofetch/data/endpoints.py — the single place external URLs are configured — so a moved or flaky service is easy to trace.