lunar_toolkit.reference

Reference data for the Artemis III candidate regions: PGDA LOLA 5 m site DEMs and Bertone et al. SfS DEMs.

PS_SRS = '+proj=stere +lat_0=-90 +lat_ts=-90 +lon_0=0 +x_0=0 +y_0=0 +R=1737400 +units=m +no_defs' module-attribute

South polar stereographic on the 1737.4 km sphere (PGDA and Bertone et al. rasters).

ASP_CSV_FORMAT = '1:easting 2:northing 3:height_above_datum' module-attribute

--csv-format for write_asp_csv output (use with --csv-srs PS_SRS).

NAC_FOCAL_MM = {'LEFT': 699.62, 'RIGHT': 701.57} module-attribute

LROC NAC focal lengths (mm) of the ISIS / ALE camera models, by name_sensor suffix.

PsBox(x0, y0, x1, y1) dataclass

Axis-aligned box in south polar stereographic metres (PS_SRS).

Parameters:
  • x0 (float) –

    West, south, east and north edges (x0 < x1, y0 < y1).

  • y0 (float) –

    West, south, east and north edges (x0 < x1, y0 < y1).

  • x1 (float) –

    West, south, east and north edges (x0 < x1, y0 < y1).

  • y1 (float) –

    West, south, east and north edges (x0 < x1, y0 < y1).

bounds property

(xmin, ymin, xmax, ymax).

projwin property

GDAL / ASP projwin order: (ulx, uly, lrx, lry).

center_latlon property

Centre as (lat, lon) degrees, lon in −180..180.

ring_lonlat(step_m=100.0)

Closed (lon, lat) ring of the box edges, sampled every step_m metres.

aoi(step_m=100.0)

The box as a lon/lat Aoi (edges straight in PS).

Parameters:
  • step_m (float, default: 100.0 ) –

    Vertex spacing along the edges; the AOI matches the box to well under a metre once geometry projects it back.

wkt(step_m=100.0)

POLYGON WKT of ring_lonlat (lon/lat degrees).

wkt_ps()

POLYGON WKT of the corners in PS metres.

PgdaSite(name, description, extent) dataclass

A PGDA LOLA 5 m/px site DEM (Barker et al. 2021): name, description and PS extent.

Region(id, name, bbox, center, pgda_site, zenodo_file, zenodo_size, zenodo_md5) dataclass

A 2022 Artemis III candidate landing region as processed by Bertone et al. (2026).

bbox (min_lon, min_lat, max_lon, max_lat) and center (lon, lat, the bbox midpoint) come from the Zenodo README; roi is the polar stereographic square the SfS DEM covers, which is the extent of the matching PGDA LOLA 5 m site DEM (pgda_site).

projwin property

(ulx, uly, lrx, lry) of roi in PS metres.

bbox_projwin property

projwin of the PS envelope of the lon/lat bbox (a wedge; larger than roi).

aoi(step_m=100.0)

roi as an Aoi.

bbox_aoi(densify_deg=0.05)

The lon/lat bbox as an AOI, densified so it stays a wedge in PS.

HttpRangeFile(url, timeout=60.0)

Bases: RawIOBase

Read-only, seekable view of a remote file through HTTP Range requests.

Lets zipfile.ZipFile read a remote zip's central directory or a single member without downloading the archive.

ps_latlon(x, y)

Inverse of south-hemisphere ps_xy: (lat, lon) degrees.

get_region(key)

Look up a region by id, name, slug or PGDA site (case-insensitive).

11, "11", "Malapert Massif", "malapert-massif", "malapert" and "Site23" all give region 11. A partial name must match exactly one region.

regions_table()

All regions as a table (one row per region).

bbox_ps_box(bbox, samples=200)

PS envelope of a lon/lat (min_lon, min_lat, max_lon, max_lat) box (south).

crop_window(region, size_km=2.5, center=None, snap_m=5.0)

Square PS window of size_km centred on a region.

Parameters:
  • region ((Region, int or str)) –

    Anything get_region accepts.

  • size_km (float, default: 2.5 ) –

    Side length (2.5 km is the Bertone et al. uncertainty-ensemble crop).

  • center ((lat, lon), default: None ) –

    Centre in degrees; default the centre of the region's Region.roi.

  • snap_m (float, default: 5.0 ) –

    Snap the edges to multiples of this many metres so the window lines up with the 5 m PGDA / SfS pixel grid; None keeps them exact.

Returns:
  • PsBox –

    Use .projwin for ASP / GDAL and .aoi() for searches.

parse_pgda_index(html)

site → kind → [url, ...] from the PGDA product page HTML.

Links may be absolute, root-relative or relative to PGDA_ROOT (DM2/Clones/DM2_final_adj_5mpp_0001_err.tif); clones are sorted by number.

pgda_index(refresh=False, cache_dir=REFERENCE_CACHE_DIR, html=None)

PGDA LOLA 5 m file index, parsed from the product page and cached.

Parameters:
  • refresh (bool, default: False ) –

    Re-fetch PGDA_PRODUCT_PAGE even when a cached index exists.

  • cache_dir (Path, default: REFERENCE_CACHE_DIR ) –

    Where pgda_lola_5mpp_index.json is kept.

  • html (str, default: None ) –

    Parse this page text instead of fetching it (nothing is cached).

pgda_urls(site_or_region, kinds=('surf',), clones=None, index=None)

kind → [url, ...] for one PGDA site.

Parameters:
  • site_or_region ((Region, int or str)) –

    A PGDA site name ("Site23") or anything get_region accepts.

  • kinds (iterable of str, default: ('surf',) ) –

    Members of PGDA_KINDS.

  • clones (int or iterable of int, default: None ) –

    With "clones": the first n clones, or these clone numbers (1-based); default all 100.

  • index (dict, default: None ) –

    A pgda_index result (default: the cached index).

fetch(url, dest, expected_md5=None, force=False, show_progress=True, retries=4)

Resumable download of url to dest, skipped when dest exists.

Throttling replies (403 / 429) and server errors are retried with back-off; the .part file is resumed each time.

download_pgda(site_or_region, kinds=('surf',), out_dir=Path('.'), clones=None, force=False, show_progress=True, index=None)

Download PGDA LOLA 5 m files of one site by kind (sequentially; resumable).

Files land in out_dir/<site>/ (clones in out_dir/<site>/Clones/); existing ones are kept unless force. Arguments as in pgda_urls.

Returns:
  • dict –

    kind → [local path, ...].

read_xyzi(path, box=None)

Read a PGDA .xyzi LOLA point cloud.

The file is little-endian float64 rows of X, Y, Z, RDRid with X/Y the polar stereographic coordinates and Z the height above the 1737.4 km sphere, all in km (the same Z as the *_surf.tif metres), and RDRid the LOLA RDR YYDOYHHMM.

Parameters:
  • path (Path) –

    The .xyzi file.

  • box (PsBox, default: None ) –

    Keep only points inside this window.

Returns:
  • DataFrame –

    Columns x, y, height (metres) and rdr_id (int64).

write_asp_csv(points, out, box=None)

Write LOLA points as an ASP CSV of easting, northing, height_above_datum.

Use with --csv-format ASP_CSV_FORMAT and --csv-srs PS_SRS (the 1737.4 km sphere is then the datum), e.g. pc_align --csv-format '1:easting 2:northing 3:height_above_datum' --csv-srs '+proj=stere +lat_0=-90 +lon_0=0 +R=1737400 +units=m' dem.tif lola.csv.

Parameters:
  • points (DataFrame or Path) –

    A read_xyzi table or an .xyzi path.

  • out (Path) –

    CSV to write; a # header line (ignored by ASP) names the columns.

  • box (PsBox, default: None ) –

    Keep only points inside this window.

parse_zenodo_files(record)

[{"key", "size", "md5", "url"}, ...] from a Zenodo record API response.

zenodo_files(refresh=False, cache_dir=REFERENCE_CACHE_DIR)

Files of the Bertone et al. Zenodo record (fetched once, then cached).

download_sdem(region, out_dir=Path('.'), force=False, show_progress=True)

Download a region's Bertone et al. zip (1–4 GB; resumable, MD5-checked).

sdem_members(source)

Members of a Bertone et al. zip: a local path, or a region (remote, central directory only).

Returns:
  • DataFrame –

    name (path inside the zip), size and compressed_size (bytes); macOS metadata and directories are left out.

select_members(names, patterns)

Zip member names matching any pattern.

A pattern with *, ? or [ is a glob against the full name and the base name ("SLOPE_MAPS/*_100.tif"); otherwise it is a case-insensitive substring of the base name ("GLDELEV", "README").

extract_sdem(source, members=('GLDELEV',), out_dir=Path('.'), force=False)

Extract selected members of a Bertone et al. zip.

Parameters:
  • source ((Path, Region, int or str)) –

    A downloaded zip, or a region whose Zenodo zip is then read remotely member by member (HTTP Range; only the selected members are fetched).

  • members (iterable of str, default: ('GLDELEV',) ) –

    select_members patterns, e.g. ("GLDELEV", "GLDSIGM", "README").

  • out_dir (Path, default: Path('.') ) –

    Destination; paths below the zip's top-level folder are kept (SLOPE_MAPS/A311_GLDTSLP_005_100.tif).

  • force (bool, default: False ) –

    Re-extract members that already exist.

Returns:
  • list of Path –

published_members(source, kind='GLDACAM')

Per-image members of a Bertone et al. zip: cameras (GLDACAM) or mapprojected images (GLDIMGP).

Returns:
  • DataFrame –

    product_id, name and size; regions 0, 7, 8 and 10 have no LROC-NAC-adjust/ cameras (the table is then empty).

parse_image_status(text)

Parse extra/image_status_*.csv: product_id and status.

Statuses are used_for_sfs (the SfS inputs, one GLDACAM camera each), not_aligned (dropped by bundle adjustment) and available_not_selected. The # key: value header lines go to attrs["summary"] (counts as ints).

image_status(source, out_dir=REFERENCE_CACHE_DIR / 'published', force=False)

A region's image status table (parse_image_status), extracted once to out_dir.

parse_tiff_extent(head)

Extent of a north-up GeoTIFF from its first bytes (header, IFD and georeferencing tags).

Reads ImageWidth / ImageLength with ModelPixelScale and ModelTiepoint (or ModelTransformation) from a classic TIFF whose IFD and tag values lie inside head (GDAL writes them first, so 16 KB is plenty for the Bertone et al. GLDIMGP maps).

published_extents(source, cache=None, head_bytes=16384)

PS extents of a region's mapprojected input images (LROC-NAC-maps/*_GLDIMGP_*.tif).

Only the start of each member is read (remotely: about 2 small HTTP Range requests per image), so this is a cheap way to find the images over a crop window. The maps are cropped to the valid pixels' bounding box within the ROI, so a diagonal strip's extent over-covers.

Parameters:
  • source ((Path, Region, int or str)) –

    A downloaded zip or a region (remote).

  • cache (Path, default: None ) –

    JSON file of the result, written once and reused.

  • head_bytes (int, default: 16384 ) –

    Bytes decompressed from each member.

Returns:
  • DataFrame –

    product_id, x0, y0, x1, y1 (PS metres).

images_covering(extents, box, min_fraction=0.0)

Product IDs whose extent overlaps box by more than min_fraction of the box area.

published_cameras(region, out_dir, images=None, window=None, source=None, force=False)

Extract the Bertone et al. bundle-adjusted NAC cameras (LROC-NAC-adjust/*_GLDACAM_*.json).

Each file is an ASP / USGS CSM linescan model state (model-name line, then JSON) with the bundle adjustment and LOLA registration baked in. It describes the full image ISIS-reduce-d by an integer factor to about 5 m/px (see published_reduce_factor and published_camera_native).

Parameters:
  • region ((Region, int or str)) –

    Anything get_region accepts.

  • out_dir (Path) –

    Destination; files land in out_dir/LROC-NAC-adjust/.

  • images (iterable of str, default: None ) –

    Only these product IDs.

  • window (PsBox, default: None ) –

    Only images whose mapprojected extent overlaps this box (published_extents, cached in out_dir).

  • source (Path, default: None ) –

    A downloaded zip instead of the remote Zenodo file.

  • force (bool, default: False ) –

    Re-extract existing files.

Returns:
  • dict –

    product_id -> camera path (sorted by product ID).

published_reduce_factor(camera)

reduce factor of a published camera: NAC focal length / its (scaled) m_focalLength.

published_camera_native(camera, native_size)

Published camera re-mapped to the full-resolution image ((samples, lines) of the EDR / cube).

The published states keep the detector summing and divide the focal length and detector-sample origin by the reduce factor f; their m_nLines / m_nSamples are the native size divided by f**2 (not f), which only affects usgscsm's initial ground-to-image guess. The result is a state for the native image with consistent sizes; use lunar_toolkit.csm.camera_for_cube or lunar_toolkit.csm.isis_reduce_with_csm to get any crop or reduction of it.