TerraLab/terrain/engine.py) computes a 360° topographic profile by raycasting from the observer’s position to distant mountains. Unlike simple viewshed algorithms, TerraLab accounts for Earth curvature, uses logarithmic depth bands for atmospheric perspective, and supports light pollution occlusion.
Algorithm Overview
1. Raycasting Fundamentals
For each azimuth angle (0–360°), the engine marches along a ray from the observer position, sampling terrain elevation at regular intervals:TerraLab/terrain/engine.py:732-742
2. Earth Curvature Correction
At large distances (>10 km), the spherical Earth causes terrain to drop below the geometric horizon. The correction formula is: Where:- = distance from observer (meters)
- m (mean Earth radius)
TerraLab/terrain/engine.py:626, 740
The curvature correction is critical for accurate horizon profiles in plains (e.g., Lleida) where distant Pyrenees peaks would otherwise appear much higher than reality.
Depth Bands
Logarithmic Partitioning
Instead of a single “max elevation per azimuth” value, TerraLab divides the horizon into depth bands for realistic atmospheric rendering:TerraLab/terrain/engine.py:23-56
Band Examples (20-band profile)
| Band ID | Min Distance | Max Distance | Purpose |
|---|---|---|---|
gnd_1_250 | 0 m | 250 m | Immediate foreground (cliffs, trees) |
near_750_1.5k | 750 m | 1.5 km | Local hills |
mid_5k_10k | 5 km | 10 km | Nearby mountains |
far_25k_50k | 25 km | 50 km | Distant ranges (Pyrenees) |
haze_100k_150k | 100 km | 150 km | Atmospheric haze limit |
TerraLab/terrain/engine.py:82-117
Band Quality Presets
DEM Data Pipeline
Tile Indexing
TerraLab supports tiled DEM datasets (ICGC 5×5 km tiles):TerraLab/terrain/engine.py:203-265
ESRI ASCII Grid Format
DEM tiles use the standard format:TerraLab/terrain/engine.py:428-454
Binary Caching
Parsed tiles are saved as.npy for instant reload:
TerraLab/terrain/engine.py:398-407, 457-460
Bilinear Interpolation
Elevation sampling uses bilinear interpolation for sub-pixel precision:TerraLab/terrain/engine.py:561-582
Bilinear interpolation smooths the horizon profile and prevents “staircase” artifacts when raycasting at oblique angles.
Adaptive Stepping
The raycasting step size adapts to distance for efficiency:TerraLab/terrain/engine.py:725-790
Rationale:
- Near field (0–50m): Geometric growth captures cliffs/walls at observer’s feet
- Mid field (50m–3km): Fine steps for detailed local topography
- Far field (>15km): Coarse steps acceptable due to atmospheric blur
Light Pollution Occlusion
During raycasting, the engine samples light pollution domes and accumulates their contribution per azimuth:TerraLab/terrain/engine.py:747-772
This produces per-azimuth light intensity arrays used by the astronomical renderer to compute directional limiting magnitude.
Profile Serialization
Baked profiles are saved as compressed NumPy archives:TerraLab/terrain/engine.py:155-196
Performance
Benchmarks (AMD Ryzen 7 5800H)
| Configuration | Azimuths | Bands | Max Distance | Time |
|---|---|---|---|---|
| Fast | 360 (1° res) | 10 | 50 km | ~3s |
| Normal | 720 (0.5° res) | 20 | 100 km | ~15s |
| High | 1440 (0.25° res) | 40 | 150 km | ~45s |
| Ultra | 1440 | 80 | 200 km | ~90s |
Bottlenecks
-
DEM I/O: First parse of ASCII tiles (~500ms each)
- Mitigation: Binary
.npycache reduces to under 10ms
- Mitigation: Binary
-
Tile Cache Misses: Searching index for each sample
- Mitigation: Spatial coherence optimization (last_tile)
-
Python GIL: Single-threaded raycasting loop
- Future: Parallelize azimuth chunks via
multiprocessing
- Future: Parallelize azimuth chunks via
TerraLab/terrain/engine.py:707-809
API Reference
Main Entry Point
TerraLab/terrain/engine.py:816-873
Classes
HorizonBaker: Core raycasting engineTileIndex: Spatial index for DEM tilesTileCache: LRU cache with binary serializationDemSampler: Bilinear elevation interpolationHorizonProfile: Serializable result container
Next Steps
- Light Pollution - How DVNL radiance affects limiting magnitude
- Astronomical Rendering - Using horizon profiles for occlusion