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