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 usebbox:. - 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:
| Provider | Allowed hosts | Body |
|---|---|---|
pgda | pgda.gsfc.nasa.gov | moon |
zenodo | zenodo.org, www.zenodo.org | any; zip members |
uahirise | hirise.lpl.arizona.edu, www.uahirise.org, uahirise.org | mars |
copernicus_s3 | copernicus-dem-30m.s3.amazonaws.com, copernicus-dem-90m.s3.amazonaws.com | earth |
usgs_3dep | prd-tnm.s3.amazonaws.com | earth |
imbrium | imbrium.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 viazenodo), 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
cap:<lat><N|S>: spherical polar cap.POLYGON/MULTIPOLYGONWKT: projected metres in the resolver CRS; use for polar layers.- See Mars/Earth footprint guidance and Terrain Stacks bbox rules.
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 raiseMissingCRSErrororDemLoadErrorduring ingest. - Oversized patch: cropping outside the DEM raises
EmptyHeightmapError; reducepatch_size_mor changecenter_xy_m. - Missing attribution: user-supplied DEMs without it are refused at bake.
- Stale companion references:
sky:,lighting:, androcks:must name catalog entries; usenullto disable them.