""" 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, )