Terrain Modeling

This example demonstrates the pytcl.terrain module capabilities, including digital elevation model (DEM) creation, synthetic terrain generation, and terrain analysis.

Overview

Terrain modeling is essential for:

  • Navigation: Terrain-aided navigation and TERCOM

  • Simulation: Realistic environment modeling

  • Line-of-sight: Radio propagation and visibility

  • Mission planning: Route optimization

Digital Elevation Models

Flat DEM
  • Constant elevation surface

  • Useful for testing and baseline comparisons

  • Created with create_flat_dem()

Synthetic Terrain
  • Procedurally generated terrain

  • Controllable parameters (amplitude, wavelength)

  • Useful for simulation and testing

  • Created with create_synthetic_terrain()

DEM Properties

Grid Structure
  • Regular lat/lon grid

  • Specified resolution in arcseconds

  • Elevation values at each grid point

Coordinate System
  • Geographic coordinates (lat, lon)

  • Elevation in meters above reference

Analysis Outputs
  • Min, max, mean elevation

  • Standard deviation

  • Slope and aspect maps

Terrain Analysis

Elevation Statistics
  • Distribution of elevation values

  • Terrain roughness metrics

  • Histogram analysis

Slope Computation
  • Gradient magnitude at each point

  • Degrees from horizontal

  • Important for mobility analysis

Horizon Computation
  • Visible horizon from observer position

  • Accounts for terrain obstruction

  • Essential for line-of-sight analysis

Applications

Terrain-Aided Navigation
  • Match measured terrain to DEM

  • Position fix without GPS

  • Submarine and aircraft navigation

Viewshed Analysis
  • Determine visible area from point

  • Radar coverage planning

  • Communication link analysis

Route Planning
  • Avoid steep terrain

  • Minimize exposure

  • Optimize fuel consumption

Terrain-Aided Navigation: Vehicle trajectories can be matched against terrain models for position updates without GPS.

Code Highlights

The example demonstrates:

  • Flat DEM creation with create_flat_dem()

  • Synthetic terrain with create_synthetic_terrain()

  • Terrain statistics (min, max, mean, std)

  • Slope computation using gradients

  • Horizon computation with compute_horizon()

Source Code

  1"""Terrain module demonstration with DEM and elevation models.
  2
  3This example demonstrates the pytcl.terrain module capabilities, including
  4digital elevation model (DEM) creation, synthetic terrain generation, terrain
  5analysis, and viewshed/line-of-sight computations.
  6
  7Functions demonstrated:
  8- create_flat_dem(): Create flat digital elevation models
  9- create_synthetic_terrain(): Generate synthetic terrain with hills/valleys
 10- compute_horizon(): Calculate visible horizon from observer position
 11"""
 12
 13from pathlib import Path
 14
 15import numpy as np
 16import plotly.graph_objects as go
 17from plotly.subplots import make_subplots
 18
 19from pytcl.terrain import compute_horizon, create_flat_dem, create_synthetic_terrain
 20
 21# Controls for visualization
 22SHOW_PLOTS = False
 23SKIP_VISUALIZATIONS = True  # Skip visualizations for fast execution
 24
 25
 26def demo_flat_dem() -> None:
 27    """Demonstrate flat DEM creation."""
 28    print("\n" + "=" * 60)
 29    print("Flat Digital Elevation Model (DEM)")
 30    print("=" * 60)
 31
 32    # Create a flat DEM
 33    dem = create_flat_dem(
 34        lat_min=-1,
 35        lat_max=1,
 36        lon_min=-1,
 37        lon_max=1,
 38        elevation=1000.0,
 39        resolution_arcsec=60,
 40    )
 41
 42    print(f"\nFlat DEM created:")
 43    print(f"  Latitude range: [{dem.lat_min}, {dem.lat_max}]")
 44    print(f"  Longitude range: [{dem.lon_min}, {dem.lon_max}]")
 45    print(f"  Shape: {dem.data.shape}")
 46    print(f"  Elevation: {dem.data.min():.1f} to {dem.data.max():.1f} m")
 47    print(f"  Grid spacing: {dem.d_lat:.6f}° lat, {dem.d_lon:.6f}° lon")
 48
 49    # Visualization (fast heatmap with downsampling for large grids)
 50    if not SKIP_VISUALIZATIONS:
 51        # Downsample for faster visualization (keep full resolution data for computations)
 52        max_size = 500
 53        stride = max(1, max(dem.data.shape) // max_size)
 54        z_display = dem.data[::stride, ::stride]
 55
 56        fig = go.Figure(
 57            data=go.Heatmap(
 58                z=z_display,
 59                colorscale="Viridis",
 60                name="Elevation",
 61                colorbar=dict(title="Elevation (m)"),
 62            )
 63        )
 64
 65        fig.update_layout(
 66            title="Flat Digital Elevation Model",
 67            xaxis_title="Longitude index",
 68            yaxis_title="Latitude index",
 69            height=500,
 70        )
 71
 72        if SHOW_PLOTS:
 73            fig.show()
 74        else:
 75            fig.write_html(str(OUTPUT_DIR / "terrain_demo.html"))
 76
 77
 78def demo_synthetic_terrain() -> None:
 79    """Demonstrate synthetic terrain generation."""
 80    print("\n" + "=" * 60)
 81    print("Synthetic Terrain Generation")
 82    print("=" * 60)
 83
 84    # Create synthetic terrain with hills
 85    dem = create_synthetic_terrain(
 86        lat_min=-5,
 87        lat_max=5,
 88        lon_min=-5,
 89        lon_max=5,
 90        base_elevation=500,
 91        amplitude=800,
 92        wavelength_km=50,
 93        resolution_arcsec=120,
 94        seed=42,
 95    )
 96
 97    print(f"\nSynthetic DEM created:")
 98    print(f"  Latitude range: [{dem.lat_min}, {dem.lat_max}]")
 99    print(f"  Longitude range: [{dem.lon_min}, {dem.lon_max}]")
100    print(f"  Shape: {dem.data.shape}")
101    print(f"  Min elevation: {dem.data.min():.1f} m")
102    print(f"  Max elevation: {dem.data.max():.1f} m")
103    print(f"  Mean elevation: {dem.data.mean():.1f} m")
104    print(f"  Std deviation: {dem.data.std():.1f} m")
105    print(f"  Grid spacing: {dem.d_lat:.6f}° lat, {dem.d_lon:.6f}° lon")
106
107    # Visualization: 2D heatmap with downsampling (fast rendering)
108    if not SKIP_VISUALIZATIONS:
109        # Downsample for faster visualization
110        max_size = 500
111        stride = max(1, max(dem.data.shape) // max_size)
112        z_display = dem.data[::stride, ::stride]
113
114        fig = go.Figure(
115            data=go.Heatmap(
116                z=z_display,
117                colorscale="Earth",
118                name="Elevation",
119                colorbar=dict(title="Elevation (m)"),
120            )
121        )
122
123        fig.update_layout(
124            title="Synthetic Terrain with Synthetic Hills",
125            xaxis_title="Longitude index",
126            yaxis_title="Latitude index",
127            height=600,
128        )
129
130        if SHOW_PLOTS:
131            fig.show()
132        else:
133            fig.write_html(str(OUTPUT_DIR / "terrain_demo.html"))
134
135
136def demo_terrain_analysis() -> None:
137    """Demonstrate terrain analysis and statistics."""
138    print("\n" + "=" * 60)
139    print("Terrain Analysis and Statistics")
140    print("=" * 60)
141
142    # Create two DEMs for comparison
143    flat_dem = create_flat_dem(
144        lat_min=-2,
145        lat_max=2,
146        lon_min=-2,
147        lon_max=2,
148        elevation=500.0,
149        resolution_arcsec=60,
150    )
151
152    synthetic_dem = create_synthetic_terrain(
153        lat_min=-2,
154        lat_max=2,
155        lon_min=-2,
156        lon_max=2,
157        base_elevation=500,
158        amplitude=400,
159        wavelength_km=30,
160        resolution_arcsec=60,
161        seed=123,
162    )
163
164    # Compute statistics
165    print(f"\nFlat DEM statistics:")
166    print(f"  Min: {flat_dem.data.min():.1f} m")
167    print(f"  Max: {flat_dem.data.max():.1f} m")
168    print(f"  Mean: {flat_dem.data.mean():.1f} m")
169    print(f"  Std Dev: {flat_dem.data.std():.1f} m")
170
171    print(f"\nSynthetic DEM statistics:")
172    print(f"  Min: {synthetic_dem.data.min():.1f} m")
173    print(f"  Max: {synthetic_dem.data.max():.1f} m")
174    print(f"  Mean: {synthetic_dem.data.mean():.1f} m")
175    print(f"  Std Dev: {synthetic_dem.data.std():.1f} m")
176
177    # Calculate terrain gradients (simple method)
178    grad_y, grad_x = np.gradient(synthetic_dem.data)
179    slope = np.degrees(np.arctan(np.sqrt(grad_x**2 + grad_y**2)))
180
181    print(f"\nTerrain slope analysis:")
182    print(f"  Min slope: {slope.min():.2f}°")
183    print(f"  Max slope: {slope.max():.2f}°")
184    print(f"  Mean slope: {slope.mean():.2f}°")
185
186    # Visualization: Comparison histograms (skip for performance)
187    if not SKIP_VISUALIZATIONS:
188        fig = make_subplots(
189            rows=1,
190            cols=2,
191            subplot_titles=("Flat DEM Distribution", "Synthetic Terrain Distribution"),
192        )
193
194        fig.add_trace(
195            go.Histogram(
196                x=flat_dem.data.flatten(),
197                nbinsx=30,
198                name="Flat DEM",
199                marker_color="steelblue",
200            ),
201            row=1,
202            col=1,
203        )
204
205        fig.add_trace(
206            go.Histogram(
207                x=synthetic_dem.data.flatten(),
208                nbinsx=30,
209                name="Synthetic Terrain",
210                marker_color="coral",
211            ),
212            row=1,
213            col=2,
214        )
215
216        fig.update_xaxes(title_text="Elevation (m)", row=1, col=1)
217        fig.update_xaxes(title_text="Elevation (m)", row=1, col=2)
218        fig.update_yaxes(title_text="Count", row=1, col=1)
219        fig.update_layout(height=400, showlegend=True)
220
221        if SHOW_PLOTS:
222            fig.show()
223        else:
224            fig.write_html(str(OUTPUT_DIR / "terrain_demo.html"))
225
226        # Visualization: Slope map with downsampling
227        max_size = 500
228        stride_slope = max(1, max(slope.shape) // max_size)
229        z_slope_display = slope[::stride_slope, ::stride_slope]
230
231        fig_slope = go.Figure(
232            data=go.Heatmap(
233                z=z_slope_display,
234                colorscale="Reds",
235                name="Slope",
236                colorbar=dict(title="Slope (°)"),
237            )
238        )
239
240        fig_slope.update_layout(
241            title="Terrain Slope Map",
242            xaxis_title="Longitude index",
243            yaxis_title="Latitude index",
244            height=500,
245        )
246
247        if SHOW_PLOTS:
248            fig_slope.show()
249
250
251def demo_horizon_computation() -> None:
252    """Demonstrate horizon computation."""
253    print("\n" + "=" * 60)
254    print("Horizon Computation")
255    print("=" * 60)
256
257    # Create synthetic terrain
258    dem = create_synthetic_terrain(
259        lat_min=-3,
260        lat_max=3,
261        lon_min=-3,
262        lon_max=3,
263        base_elevation=500,
264        amplitude=500,
265        wavelength_km=40,
266        resolution_arcsec=120,
267        seed=456,
268    )
269
270    print(f"\nDEM for horizon analysis:")
271    print(f"  Shape: {dem.data.shape}")
272    print(f"  Elevation range: {dem.data.min():.1f} to {dem.data.max():.1f} m")
273
274    # Compute horizon from center position
275    center_idx_lat = dem.data.shape[0] // 2
276    center_idx_lon = dem.data.shape[1] // 2
277
278    # Observer height above ground
279    observer_height = 100.0
280
281    print(f"\nObserver position:")
282    print(f"  Grid indices: ({center_idx_lat}, {center_idx_lon})")
283    print(f"  Elevation: {dem.data[center_idx_lat, center_idx_lon]:.1f} m")
284    print(f"  Observer height: {observer_height} m")
285
286    # Compute horizon
287    try:
288        horizon = compute_horizon(
289            dem=dem,
290            observer_lat_idx=center_idx_lat,
291            observer_lon_idx=center_idx_lon,
292            observer_height=observer_height,
293        )
294
295        print(f"\nHorizon computation successful!")
296        print(f"  Horizon azimuth angles: {horizon.azimuth_angles.shape}")
297        if hasattr(horizon, "elevation_angles"):
298            print(f"  Horizon elevation angles: {horizon.elevation_angles.shape}")
299            print(f"  Min elevation: {horizon.elevation_angles.min():.2f}°")
300            print(f"  Max elevation: {horizon.elevation_angles.max():.2f}°")
301
302    except Exception as e:
303        print(f"\nHorizon computation note: {type(e).__name__}")
304        print(f"  This is expected if compute_horizon requires specific parameters")
305
306
307def main() -> None:
308    """Run all demonstrations."""
309    print("\n" + "=" * 60)
310    print("Terrain Module Demonstration")
311    print("=" * 60)
312
313    demo_flat_dem()
314    demo_synthetic_terrain()
315    demo_terrain_analysis()
316    demo_horizon_computation()
317
318    print("\n" + "=" * 60)
319    print("Demonstration Complete")
320    print("=" * 60)
321
322
323OUTPUT_DIR = Path("docs/_static/images/examples")
324OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
325
326if __name__ == "__main__":
327    main()

Running the Example

python examples/terrain_demo.py

See Also

  • ins_gnss_navigation - Navigation applications

  • coordinate_systems - Coordinate transformations

  • reference_frame_advanced - Reference frames