Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Adding Terrain Sites + Catalog Entries

Add v1 sites, v2 layers, HDRI skydomes, rock sets, and lighting presets to srb/terrain/manifest.yaml (v1) or srb/terrain/catalog/<body>.yaml (v2).

Adding a new terrain site

Use public-domain or compatibly licensed DEMs (USGS, NASA, ESA HiRISE-derived). Record URL, SHA-256, CRS (e.g. IAU_Moon_2015), and ground sampling distance.

sites:
  my_site:
    body: moon # or mars, earth
    name: "My Site (display name)"
    coords: { lat: 20.5, lon: 30.7 }
    sources:
      dem: { provider: usgs_astro, url: "https://example.org/dem.tif", sha256: "<64-char hex>", crs: IAU_Moon_2015, gsd_m: 2.0, license: "Public Domain (NASA/USGS)", attribution: "USGS Astrogeology / LROC NAC" }
      # Also supported: lroc_pds, pds_geosciences, openplanetary, local.
    patches:
      default: { center_xy_m: [256, 256], size_m: 200, lod: medium }
    sky: lunar_default_low # or null
    lighting: lunar_default # or null
    rocks: lunar_basalt_set # or null
    augment_preset: lunar_default
srb asset validate-manifest
srb asset download terrain my_site

Adding a v2 source layer

Catalog entries are reviewed, pinned data: download each product, verify SHA-256, and review its footprint. srb/terrain/catalog/refresh.py proposes candidates from NAC_DTMS_180.SHP, ODE, and USGS STAC in a separate review file; promote them manually. Coverage is curated: v1 requests use the v1 path; declared v2 sites error if the base is unavailable or uncovered; uncovered environment region requests use procedural terrain, but srb asset download terrain --lat --lon --size-m errors. See Terrain Stacks and catalog status.

Authoring Mars/Earth entries

  • Non-polar footprints: bbox:<lonmin>,<latmin>,<lonmax>,<latmax> in degrees. The form accepts 0–360°E; see bbox parsing and antimeridian rules.
  • Polar layers: WKT POLYGON(...) coordinates in the resolver’s canonical projected CRS. Non-polar requests use a request-specific CRS, so use bbox:.
  • Polar caps: cap:<lat><N|S>.

Use layers in declared-stack sites or resolve arbitrary regions without a manifest entry using RealTerrain(body="moon", lat_deg=..., lon_deg=..., size_m=...) or srb asset download terrain --lat --lon --size-m [--deshade]. Resolution, identity, normalization, and fallback rules are in Terrain Stacks.

Schema

srb.terrain.catalog_schema.SourceLayer validates entries:

body: moon
layers:
  - { layer_id: ldem_87s, body: moon, kind: dem, product_family: "LOLA/Kaguya merged polar DEM",
      provider: pgda, urls: ["https://pgda.gsfc.nasa.gov/data/LOLA_GDR/CYLINDRICAL/IMG/ldem_87s_10m.tif"],
      sha256: { "ldem_87s_10m.tif": "<64-char hex>" }, native_gsd_m: 10.0,
      footprint: "cap:83S", alignment: { source: assumed_zero }, license: "Public Domain (NASA/GSFC)",
      attribution: "NASA GSFC PGDA", size_bytes: 1_400_000_000, access: cog_remote,
      priority_class: 1, allow_large_download: false }
# kind: dem | color | mask | quality; footprint may use WKT; access: cog_remote | download | zip_member

provider accepts v1 providers (usgs_astro, lroc_pds, pds_geosciences, openplanetary, local) plus:

ProviderAllowed hostsBody
pgdapgda.gsfc.nasa.govmoon
zenodozenodo.org, www.zenodo.organy; zip members
uahirisehirise.lpl.arizona.edu, www.uahirise.org, uahirise.orgmars
copernicus_s3copernicus-dem-30m.s3.amazonaws.com, copernicus-dem-90m.s3.amazonaws.comearth
usgs_3depprd-tnm.s3.amazonaws.comearth
imbriumimbrium.mit.edu (HTTPS only)moon

Providers use case-insensitive host allowlists; ports and user information are ignored. Hosts match exactly, except openplanetary, which also accepts its configured domains’ subdomains. Add hosts in srb/terrain/sources/*.py, not the catalog. uahirise, copernicus_s3, and usgs_3dep support only unauthenticated HTTP(S) GETs, so products that need credentials cannot be curated with them; Mars USGS CTX and HRSC-MOLA use usgs_astro.

priority_class — lower wins

Sort by synthetic status, priority_class, native GSD, then layer_id. Lower values win; set explicitly. The lowest-ranked covering layer is base.

radiometry: synthetic — the super-resolution slot

Use radiometry: synthetic only for model-generated products. Footprint search skips them unless RegionRequest.allow_synthetic=True or TerrainSpec.allow_synthetic=True; they rank after real layers. Declared stacks can reference them without opt-in. This flag affects cache identity. See Terrain Stacks.

access — how a layer’s bytes are fetched

  • cog_remote: fetch and verify the complete source, then read a local window; the label remains for catalog compatibility, and large sources need consent.
  • download: fetch one plain file whole (BaseHttpProvider.fetch).
  • zip_member: fetch the archive whole (currently exercised via zenodo), then extract specifically pinned members.

sha256 keys

Hash the basename of urls[0] (query and fragment stripped). cog_remote and download need exactly one such hash; zip_member needs the archive hash and each extracted member path:

access: zip_member
urls: ["https://zenodo.org/records/<id>/files/<archive>.zip"]
sha256:
  "<archive>.zip": "<64 hex>"
  "dtm/<region>_dem.tif": "<64 hex>"

Compute hashes with curl -L "<url>" | sha256sum. Placeholder hashes ("f" * 64) fail validation before download.

allow_large_download + SRB_TERRAIN_LARGE_OK

V2 fetches estimated above 1 GiB require both allow_large_download: true on the reviewed layer and SRB_TERRAIN_LARGE_OK=1 at fetch time. The variable is per-run, not catalog data. V1 is unchanged: allow_large_download: true alone suffices. No prompt appears; srb asset stack consent is preview only. See Real Terrain Assets.

mask_encoding — required in practice for every kind: mask layer

Without mask_encoding, fusion treats finite nonzero raw values as masked. Example pgda_lpsr_85s_60m: int16 20000 (shadowed) maps to 1.0, -32768 (nodata) to NaN, and other values (in practice -20000) to 0.0:

mask_encoding:
  masked_values: [20000]
  nodata_values: [-32768]

Decoded at ingest; values use exact equality. Non-finite or duplicate values and unknown keys fail validation. Check raster values: documentation may differ.

footprint — three forms, and the CRS requirement

Polar requests use canonical body CRS; others use a request-specific local stereographic CRS. A missed footprint errors if a declared-stack site has no base coverage. Arbitrary-region behavior: Terrain Stacks.

Validate

srb asset stack --body moon --lat -89.46 --lon 137.3 --size 2000
srb asset stack --body moon --lat -89.46 --lon 137.3 --size 2000 --json

Malformed sha256, unrecognized footprints, duplicate layer_id, or unknown providers raise pydantic.ValidationError at load time, before planning.

Adding a v2 site with a declared stack

Add reviewed layers to srb/terrain/catalog/<body>.yaml first. A v2 site in srb/terrain/manifest.yaml sets version: 2; existing v1 sites remain valid. Declare dem_stack (exactly one role: base, optional role: inset) and, optionally, a color_stack of the same shape instead of v1 sources.dem/sources.ortho:

version: 2
sites:
  connecting_ridge:
    body: moon
    name: "Connecting Ridge (v2 fused)"
    coords: { lat: -89.46, lon: 137.3 }
    sources:
      dem_stack:
        - { layer_id: ldem_87s, role: base }
        - { layer_id: nac_dtm_esall_cr1, role: inset }
      color_stack:
        - { layer_id: cm_avg_87s, role: base }
        - { layer_id: shadowcam_cmosaic_cr1, role: inset }
    patches:
      default: { center_xy_m: [0, 10309.97], size_m: 2000, lod: medium }
      # Or use center_latlon: [-89.66, 0.0] instead of center_xy_m.
    sky: lunar_default_low
    lighting: lunar_default
    rocks: lunar_basalt_set
    deshade: true # optional; bake illumination-corrected colour by default

IDs must exist in the body’s catalog. role selects the base; StackRef.max_gsd_m can exclude coarse layers. deshade: true is described in Terrain Stacks. V2 bakes include fusion in meta.json, provenance.tif, and per-layer attribution. Unreachable insets are dropped with a warning; an unreachable base raises. Validate and bake as above. See fusion engine.

The connecting_ridge IDs are illustrative, not shipped. See catalog status per body.

Adding a new HDRI skydome

Add to srb/terrain/hdri/catalog.yaml:

skydomes:
  my_dome: { body: moon, url: "https://example.org/dome.hdr", sha256: "<64-char hex>", format: hdr, intensity: 1.0, resize_to: 2048 }
  # format: hdr | png | exr; include license and attribution.

EXR needs optional imageio[freeimage] (pip install srb[terrain-extras]); PNG and HDR work out of the box.

Adding a new rock set

Add to srb/terrain/rocks/catalog.yaml:

sets:
  my_set:
    body: moon
    rocks:
      rock_a: { url: "https://example.org/rock_a.glb", sha256: "<64-char hex>", format: glb, target_face_count: 5000 }
      # format: glb | obj | ply | stl; include license and attribution.

Each rock is decimated to target_face_count (default 5000), duplicate vertices welded, recentered at its centroid, and its AABB diagonal rescaled to 1.0. Scale it at scatter time.

Adding a new lighting preset

Add to srb/terrain/lights/catalog.yaml (pure config; no fetch):

presets:
  my_preset:
    body: moon
    sun_elevation_deg: 30
    sun_azimuth_deg: 90
    sun_intensity_lux: 130000
    sun_color_temperature_k: 5800
    sun_angular_diameter_deg: 0.53
    earthshine_intensity_lux: 200 # optional
    earthshine_color_temperature_k: 7500
    license: "Public Domain"
    attribution: "Synthesized from ephemeris X"

Sun pose can come from a site ephemeris or be synthesized.

Common mistakes

  • Wrong CRS: use IAU 2015 names (IAU_Moon_2015, IAU_Mars_2015); misspellings raise MissingCRSError or DemLoadError during ingest.
  • Oversized patch: cropping outside the DEM raises EmptyHeightmapError; reduce patch_size_m or change center_xy_m.
  • Missing attribution: user-supplied DEMs without it are refused at bake.
  • Stale companion references: sky:, lighting:, and rocks: must name catalog entries; use null to disable them.