Files
settled-reach/tooling/planet-gen/sol_data/mercury.py
T
jpmschweitzerandClaude Opus 4.6 18bdb1ed3d feat(assets): add Sol system handcrafted terrain pipeline
Custom pipeline for GJ-0 (Sol) that imports real NASA/USGS
planetary data instead of procedural generation. Produces the
same output format (heightmap.png, globe.png, markers.json).

Real data bodies:
- Earth: ETOPO2022 elevation + WorldClim climate + 14 rivers
- Mars: MOLA DEM + ferric biome classes + terraformed water
- Luna: LOLA DEM + lunar biome palette

Procedural fallback for Mercury, Venus, Phobos, Deimos.
Synthetic elevation from albedo for Io, Europa, Ganymede,
Callisto, Titan, Enceladus. Gas giants use existing renderer.

New biome classes 34-36 (ferric_dust/highland/lowland) for
Mars iron oxide surface. Earth features: 50 cities (smart
scatter by continent), 15 named rivers, oceans, mountains.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 22:22:18 +02:00

121 lines
4.8 KiB
Python

"""
Mercury (GJ0b) terrain builder.
Data source:
- Elevation: MESSENGER DEM from USGS Astrogeology
665m/px global DEM, GeoTIFF.
Mercury properties:
- Min elevation: ~-5380 m
- Max elevation: ~4480 m
- No atmosphere, no water
- Extreme temperature range: ~100K (night) to ~700K (day)
- body_type: "planet", planet_class: "barren", atmosphere: "none"
"""
import numpy as np
from pathlib import Path
from sol_data.download import ensure_cached
from sol_data.shared import (
GRID_W, GRID_H,
load_tiff_as_array, load_raw_binary,
resample_to_grid, normalize_01,
compute_hillshade, assemble_terrain,
temperature_grid_analytical,
)
# MESSENGER DEM — try PDS binary first (compact), fall back to USGS GeoTIFF
MESSENGER_PDS_URL = "https://pds-geosciences.wustl.edu/messenger/mess-h-mdis_mla-6-dem-elevation-v1/messdmdem_1001/data/global_dem_16ppd.img"
MESSENGER_PDS_FILE = "messenger_dem_16ppd.img"
MESSENGER_PDS_W = 5760
MESSENGER_PDS_H = 2880
# USGS GeoTIFF fallback (~506 MB, but PIL-loadable)
MESSENGER_TIFF_URL = "https://planetarymaps.usgs.gov/mosaic/Mercury_Messenger_USGS_DEM_Global_665m_v2.tif"
MESSENGER_TIFF_FILE = "Mercury_Messenger_USGS_DEM_Global_665m_v2.tif"
MERCURY_MIN_ELEV_M = -5380.0
MERCURY_MAX_ELEV_M = 4480.0
MERCURY_EQUATORIAL_TEMP_K = 440.0 # mean dayside
MERCURY_POLAR_TEMP_K = 200.0
def _load_messenger() -> np.ndarray:
"""Load MESSENGER DEM, return elevation in metres."""
# Try PDS binary first (compact ~33 MB)
try:
path = ensure_cached(MESSENGER_PDS_URL, MESSENGER_PDS_FILE)
print(f" loading MESSENGER PDS: {path}")
arr = load_raw_binary(str(path), MESSENGER_PDS_W, MESSENGER_PDS_H,
dtype=">i2", offset=0)
arr[arr > 20000] = 0.0
arr[arr < -20000] = 0.0
print(f" MESSENGER range: [{arr.min():.0f}, {arr.max():.0f}] m")
return arr
except Exception as e:
print(f" PDS load failed ({e}), trying USGS GeoTIFF...")
# Fallback: USGS GeoTIFF (~506 MB)
try:
path = ensure_cached(MESSENGER_TIFF_URL, MESSENGER_TIFF_FILE)
print(f" loading MESSENGER GeoTIFF: {path}")
arr = load_tiff_as_array(str(path))
arr[arr < -20000] = 0.0
print(f" MESSENGER shape: {arr.shape}, range: [{arr.min():.0f}, {arr.max():.0f}] m")
return arr
except Exception as e2:
print(f" GeoTIFF also failed ({e2}), using procedural")
return None
def build_terrain(body_def: dict) -> dict:
"""Build Mercury terrain dict from MESSENGER data."""
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from planet_simulation import compute_biome
print(" Mercury: loading MESSENGER data...")
# ── 1. Elevation ────────────────────────────────────────────────────
raw = _load_messenger()
if raw is None:
import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from planet_simulation import simulate
return simulate(body_def)
from sol_data.shared import greenwich_to_dateline
shifted = greenwich_to_dateline(raw)
elevation_m = resample_to_grid(shifted, GRID_H, GRID_W, order=1)
elevation = normalize_01(elevation_m, MERCURY_MIN_ELEV_M, MERCURY_MAX_ELEV_M)
sea_level = 0.0
surface_water = np.zeros((GRID_H, GRID_W), dtype=bool)
# ── 2. Temperature ──────────────────────────────────────────────────
temperature_K = temperature_grid_analytical(
base_T_K=MERCURY_EQUATORIAL_TEMP_K,
elevation=elevation,
lapse_rate_K_per_unit=20.0,
lat_gradient_K=240.0,
)
temperature_K = np.maximum(temperature_K, 100.0)
# ── 3. Moisture ─────────────────────────────────────────────────────
moisture = np.zeros((GRID_H, GRID_W), dtype=np.float32)
# ── 4. Biome ────────────────────────────────────────────────────────
biome = compute_biome(body_def, elevation, sea_level, surface_water,
temperature_K, moisture)
# ── 5. Hillshade ────────────────────────────────────────────────────
hillshade = compute_hillshade(elevation)
return assemble_terrain(
elevation=elevation, temperature_K=temperature_K,
moisture=moisture, biome=biome,
surface_water=surface_water, hillshade=hillshade,
rivers=[], sea_level=sea_level,
)