Geophysical Models
This example demonstrates gravity and magnetic field models essential for high-precision navigation, geodesy, and aerospace applications.
Overview
Geophysical models are critical for:
Inertial navigation: Gravity compensation in INS
Geodesy: Height reference and surveying
Aerospace: Satellite orbit determination
Geophysics: Subsurface exploration
Gravity Models
- Normal Gravity (Somigliana)
Gravity variation with latitude
~0.5% increase from equator to poles
Due to Earth’s rotation and flattening
- WGS84 Gravity Model
Full gravity vector computation
Includes deflection of vertical
Essential for inertial navigation
- J2 Gravity Model
Simplified model using J2 oblateness term
Adequate for many applications
Faster computation than full models
- Geoid Height
Separation between geoid and ellipsoid
J2 approximation captures main flattening
Full models (EGM96/2008) for precision
- Gravity Anomalies
Free-air anomaly
Gravity disturbance
Used for geophysical exploration
- Tidal Effects
Solid Earth tide displacement (~30 cm)
Tidal gravity variations (~300 uGal)
Essential for precision gravimetry
Magnetic Field Models
- World Magnetic Model (WMM2020)
Standard model for navigation
Updated every 5 years
Valid 2020-2025
- IGRF-14 (IGRF-13 retained for reproducibility)
International Geomagnetic Reference Field
Historical (1900.0-2025.0) and predictive (2025-30) coefficients
Used for scientific applications
- Key Parameters
Declination: angle between true and magnetic north
Inclination: dip angle of field lines
Total intensity: field strength in nT
- Notable Features
South Atlantic Anomaly: weak field region
Magnetic poles: ~11° offset from geographic
Secular variation: field changes over time
Code Highlights
The example demonstrates:
Normal gravity with
normal_gravity_somigliana()WGS84 gravity with
gravity_wgs84()Geoid height with
geoid_height_j2()Free-air anomaly with
free_air_anomaly()Solid Earth tides with
solid_earth_tide_displacement()Magnetic field with
wmm()andigrf()Magnetic declination with
magnetic_declination()
Source Code
1"""
2Geophysical Models Example
3==========================
4
5This example demonstrates the geophysical models in PyTCL:
6
7Gravity Models:
8- Normal gravity (Somigliana formula)
9- WGS84 and J2 gravity models
10- Spherical harmonic expansions
11- Geoid height computation
12- Gravity anomalies and disturbances
13- Tidal effects (solid Earth, ocean loading)
14
15Magnetic Field Models:
16- World Magnetic Model (WMM2020)
17- International Geomagnetic Reference Field (IGRF-13)
18- Enhanced Magnetic Model (EMM)
19- Magnetic declination, inclination, and intensity
20
21These models are essential for high-precision navigation, geodesy,
22and aerospace applications.
23"""
24
25from pathlib import Path
26
27import numpy as np
28import plotly.graph_objects as go
29from plotly.subplots import make_subplots
30
31# Output directory for generated plots
32OUTPUT_DIR = Path(__file__).parent.parent / "docs" / "_static" / "images" / "examples"
33OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
34
35# Global flag to control plotting
36SHOW_PLOTS = True
37
38
39from pytcl.gravity import ( # Normal gravity; Gravity models; Geoid; Anomalies; Tidal effects; Constants
40 GRS80,
41 WGS84,
42 free_air_anomaly,
43 geoid_height,
44 geoid_height_j2,
45 gravity_anomaly,
46 gravity_disturbance,
47 gravity_j2,
48 gravity_wgs84,
49 normal_gravity,
50 normal_gravity_somigliana,
51 solid_earth_tide_displacement,
52 solid_earth_tide_gravity,
53 tidal_gravity_correction,
54)
55from pytcl.magnetism import ( # World Magnetic Model; IGRF
56 dipole_moment,
57 igrf,
58 igrf_declination,
59 igrf_inclination,
60 magnetic_declination,
61 magnetic_field_intensity,
62 magnetic_inclination,
63 magnetic_north_pole,
64 wmm,
65)
66
67
68def demo_normal_gravity():
69 """Demonstrate normal gravity computation."""
70 print("=" * 70)
71 print("Normal Gravity Demo")
72 print("=" * 70)
73
74 # Test locations at different latitudes
75 locations = [
76 ("Equator", 0.0, 0.0, 0.0),
77 ("Washington DC", 38.9, -77.0, 0.0),
78 ("North Pole", 90.0, 0.0, 0.0),
79 ("Mount Everest", 27.99, 86.93, 8848.0),
80 ("Dead Sea", 31.5, 35.5, -430.0),
81 ]
82
83 print("\nNormal gravity at various locations:")
84 print("-" * 60)
85 print(f"{'Location':<20} {'Lat':>8} {'Alt (m)':>10} {'g (m/s²)':>12}")
86 print("-" * 60)
87
88 for name, lat, lon, alt in locations:
89 # Normal gravity at sea level
90 g_somigliana = normal_gravity_somigliana(np.radians(lat))
91
92 # Gravity at altitude (free-air correction)
93 g_at_alt = normal_gravity(np.radians(lat), alt)
94
95 print(f"{name:<20} {lat:>8.2f} {alt:>10.1f} {g_at_alt:>12.6f}")
96
97 # Show latitude variation
98 print("\n--- Latitude Variation ---")
99 lats = np.array([0, 15, 30, 45, 60, 75, 90])
100 for lat in lats:
101 g = normal_gravity_somigliana(np.radians(lat))
102 print(f" {lat:>2}°: {g:.6f} m/s²")
103
104 print("\nNote: Gravity increases from equator to poles due to")
105 print("Earth's rotation (centrifugal) and flattening effects.")
106
107 # Plot gravity variation with latitude
108 if SHOW_PLOTS:
109 # High-resolution latitude array
110 lats_fine = np.linspace(0, 90, 181)
111 g_values = np.array(
112 [normal_gravity_somigliana(np.radians(lat)) for lat in lats_fine]
113 )
114
115 fig = make_subplots(
116 rows=1,
117 cols=2,
118 subplot_titles=(
119 "Normal Gravity vs Latitude (Somigliana)",
120 "Gravity Increase from Equator",
121 ),
122 )
123
124 # Left plot: Gravity vs latitude
125 fig.add_trace(
126 go.Scatter(
127 x=lats_fine,
128 y=g_values,
129 mode="lines",
130 name="Gravity",
131 line=dict(color="blue", width=2),
132 ),
133 row=1,
134 col=1,
135 )
136 # Mark equator and pole values
137 fig.add_hline(
138 y=g_values[0],
139 line_dash="dash",
140 line_color="red",
141 annotation_text=f"Equator: {g_values[0]:.4f}",
142 row=1,
143 col=1,
144 )
145 fig.add_hline(
146 y=g_values[-1],
147 line_dash="dash",
148 line_color="green",
149 annotation_text=f"Pole: {g_values[-1]:.4f}",
150 row=1,
151 col=1,
152 )
153
154 # Right plot: Gravity difference from equator
155 g_diff = (g_values - g_values[0]) * 1000 # mGal
156 fig.add_trace(
157 go.Scatter(
158 x=lats_fine,
159 y=g_diff,
160 mode="lines",
161 name="Δg",
162 line=dict(color="blue", width=2),
163 ),
164 row=1,
165 col=2,
166 )
167
168 fig.update_xaxes(title_text="Latitude (°)", range=[0, 90], row=1, col=1)
169 fig.update_yaxes(title_text="Normal Gravity (m/s²)", row=1, col=1)
170 fig.update_xaxes(title_text="Latitude (°)", range=[0, 90], row=1, col=2)
171 fig.update_yaxes(title_text="Δg from Equator (mGal)", row=1, col=2)
172
173 fig.update_layout(height=500, width=1200, showlegend=False)
174 fig.write_html(
175 str(OUTPUT_DIR / "geophysical_gravity_latitude.html"),
176 include_plotlyjs="cdn",
177 div_id="geophysical_gravity_latitude",
178 )
179 print("\n [Plot saved to geophysical_gravity_latitude.html]")
180
181
182def demo_gravity_models():
183 """Demonstrate different gravity models."""
184 print("\n" + "=" * 70)
185 print("Gravity Models Comparison Demo")
186 print("=" * 70)
187
188 # Test point: Washington DC
189 lat = np.radians(38.9)
190 lon = np.radians(-77.0)
191 alt = 0.0 # Sea level
192
193 print(f"\nLocation: Washington DC (38.9°N, 77.0°W)")
194 print("-" * 50)
195
196 # WGS84 gravity model
197 g_wgs84 = gravity_wgs84(lat, lon, alt)
198 print(f"\nWGS84 gravity model:")
199 print(f" Total gravity: {g_wgs84.magnitude:.6f} m/s²")
200 print(f" Down component: {g_wgs84.g_down:.6f} m/s²")
201 print(f" North component: {g_wgs84.g_north:.9f} m/s²")
202
203 # J2 gravity model (simpler, uses only J2 term)
204 g_j2 = gravity_j2(lat, lon, alt)
205 print(f"\nJ2 gravity model:")
206 print(f" Total gravity: {g_j2.magnitude:.6f} m/s²")
207 print(
208 f" Difference from WGS84: {(g_j2.magnitude - g_wgs84.magnitude) * 1e6:.2f} µGal"
209 )
210
211 # Compare at different altitudes
212 print("\n--- Gravity vs Altitude ---")
213 altitudes = [0, 1000, 10000, 100000, 400000] # meters
214 for alt in altitudes:
215 g = gravity_wgs84(lat, lon, alt)
216 print(f" {alt:>7} m: {g.magnitude:.6f} m/s²")
217
218 print("\nNote: Gravity decreases with altitude approximately as")
219 print("g(h) ~= g0(1 - 2h/R), about 3.1 mGal per meter at low altitudes.")
220
221 # Plot gravity vs altitude
222 if SHOW_PLOTS:
223 fig = make_subplots(
224 rows=1,
225 cols=2,
226 subplot_titles=(
227 "Gravity vs Altitude (WGS84)",
228 "Gravity Reduction with Altitude",
229 ),
230 )
231
232 # Altitude range from surface to ISS altitude
233 alts = np.linspace(0, 500000, 500) # 0 to 500 km
234 g_values = np.array([gravity_wgs84(lat, lon, a).magnitude for a in alts])
235
236 # Left plot: Gravity vs altitude
237 fig.add_trace(
238 go.Scatter(
239 x=alts / 1000,
240 y=g_values,
241 mode="lines",
242 name="Gravity",
243 line=dict(color="blue", width=2),
244 ),
245 row=1,
246 col=1,
247 )
248
249 # Right plot: Gravity reduction rate
250 g_reduction = (g_values[0] - g_values) / g_values[0] * 100 # percent
251 fig.add_trace(
252 go.Scatter(
253 x=alts / 1000,
254 y=g_reduction,
255 mode="lines",
256 name="Reduction",
257 line=dict(color="red", width=2),
258 ),
259 row=1,
260 col=2,
261 )
262
263 # Mark ISS at ~400 km
264 fig.add_hline(
265 y=g_reduction[400],
266 line_dash="dash",
267 line_color="green",
268 annotation_text=f"ISS (~400 km): {g_reduction[400]:.1f}% reduction",
269 row=1,
270 col=2,
271 )
272
273 fig.update_xaxes(title_text="Altitude (km)", row=1, col=1)
274 fig.update_yaxes(title_text="Gravity (m/s²)", row=1, col=1)
275 fig.update_xaxes(title_text="Altitude (km)", row=1, col=2)
276 fig.update_yaxes(title_text="Gravity Reduction (%)", row=1, col=2)
277
278 fig.update_layout(height=500, width=1200, showlegend=False)
279 fig.write_html(
280 str(OUTPUT_DIR / "geophysical_gravity_altitude.html"),
281 include_plotlyjs="cdn",
282 div_id="geophysical_gravity_altitude",
283 )
284 print("\n [Plot saved to geophysical_gravity_altitude.html]")
285
286
287def demo_geoid():
288 """Demonstrate geoid height computation."""
289 print("\n" + "=" * 70)
290 print("Geoid Height Demo")
291 print("=" * 70)
292
293 # Various latitudes (J2 geoid approximation is zonal - only latitude dependent)
294 locations = [
295 ("Equator", 0.0),
296 ("Mid-latitude (30N)", 30.0),
297 ("Mid-latitude (45N)", 45.0),
298 ("High latitude (60N)", 60.0),
299 ("Pole", 90.0),
300 ]
301
302 print("\nJ2 Geoid heights (zonal approximation - latitude dependent only):")
303 print("-" * 40)
304 print(f"{'Location':<25} {'Lat':>7} {'N (m)':>10}")
305 print("-" * 40)
306
307 for name, lat in locations:
308 # J2 geoid approximation - only depends on latitude
309 N = geoid_height_j2(np.radians(lat))
310 print(f"{name:<25} {lat:>7.1f} {N:>10.2f}")
311
312 print("\nNote: The J2 geoid model captures the main flattening effect.")
313 print("Real geoid variations are ±100m from the ellipsoid (need EGM96/2008).")
314
315
316def demo_gravity_anomalies():
317 """Demonstrate gravity anomaly computation."""
318 print("\n" + "=" * 70)
319 print("Gravity Anomalies Demo")
320 print("=" * 70)
321
322 # Simulated gravity survey
323 np.random.seed(42)
324
325 # Locations along a survey line
326 n_points = 5
327 lats = np.linspace(38.0, 39.0, n_points)
328 lons = np.full(n_points, -77.0)
329 alts = np.array([100, 150, 200, 180, 120]) # meters
330
331 # Simulated observed gravity (with anomaly)
332 g_normal = np.array(
333 [normal_gravity(np.radians(lat), alt) for lat, alt in zip(lats, alts)]
334 )
335 # Add a gravity anomaly (e.g., from subsurface density variation)
336 anomaly_true = np.array([0.0, 10.0, 25.0, 15.0, 5.0]) * 1e-5 # m/s²
337 g_observed = g_normal + anomaly_true
338
339 print("\nGravity survey along profile:")
340 print("-" * 65)
341 print(f"{'Point':>5} {'Lat':>7} {'Alt(m)':>7} {'g_obs':>12} {'FAA':>12}")
342 print("-" * 65)
343
344 for i in range(n_points):
345 lat_rad = np.radians(lats[i])
346 faa = free_air_anomaly(g_observed[i], lat_rad, alts[i])
347 print(
348 f"{i + 1:>5} {lats[i]:>7.2f} {alts[i]:>7.0f} "
349 f"{g_observed[i]:>12.6f} {faa * 1e5:>12.2f} mGal"
350 )
351
352 print("\nNote: Free-air anomaly removes the effect of elevation")
353 print("to reveal subsurface density variations.")
354
355
356def demo_tidal_effects():
357 """Demonstrate tidal effects on gravity and position."""
358 print("\n" + "=" * 70)
359 print("Tidal Effects Demo")
360 print("=" * 70)
361
362 # Observer location
363 lat = np.radians(38.9) # Washington DC
364 lon = np.radians(-77.0)
365
366 # Time (Julian date) - approximate
367 # Let's use a few different times
368 times = [
369 ("2025-01-01 00:00 UTC", 2460676.5),
370 ("2025-01-01 06:00 UTC", 2460676.75),
371 ("2025-01-01 12:00 UTC", 2460677.0),
372 ("2025-01-01 18:00 UTC", 2460677.25),
373 ]
374
375 print(f"\nLocation: Washington DC (38.9°N, 77.0°W)")
376 print("\nSolid Earth tide effects (displacement and gravity):")
377 print("-" * 60)
378
379 for time_str, jd in times:
380 # Solid Earth tide displacement
381 disp = solid_earth_tide_displacement(lat, lon, jd)
382
383 # Tidal gravity correction
384 dg = tidal_gravity_correction(lat, lon, jd)
385
386 print(f"\n{time_str}:")
387 print(
388 f" Displacement: ({disp[0] * 1000:.1f}, {disp[1] * 1000:.1f}, "
389 f"{disp[2] * 1000:.1f}) mm (N, E, Up)"
390 )
391 print(f" Gravity change: {dg * 1e8:.2f} µGal")
392
393 print("\nNote: Solid Earth tides cause displacements of ~30 cm")
394 print("and gravity changes of ~300 µGal peak-to-peak.")
395
396 # Plot tidal effects over 24 hours
397 if SHOW_PLOTS:
398 # Time array: 24 hours at 15-minute intervals
399 hours = np.linspace(0, 24, 97)
400 jd_start = 2460676.5 # 2025-01-01 00:00 UTC
401 jds = jd_start + hours / 24
402
403 # Compute displacements and gravity changes
404 disp_n = np.zeros_like(hours)
405 disp_e = np.zeros_like(hours)
406 disp_u = np.zeros_like(hours)
407 dg = np.zeros_like(hours)
408
409 for i, jd in enumerate(jds):
410 disp = solid_earth_tide_displacement(lat, lon, jd)
411 disp_n[i] = disp[0] * 1000 # mm
412 disp_e[i] = disp[1] * 1000
413 disp_u[i] = disp[2] * 1000
414 dg[i] = tidal_gravity_correction(lat, lon, jd) * 1e8 # µGal
415
416 fig = make_subplots(
417 rows=2,
418 cols=1,
419 subplot_titles=(
420 "Solid Earth Tide - Washington DC (2025-01-01)",
421 "Tidal Gravity Variation",
422 ),
423 shared_xaxes=True,
424 )
425
426 # Top plot: Displacement components
427 fig.add_trace(
428 go.Scatter(
429 x=hours,
430 y=disp_n,
431 mode="lines",
432 name="North",
433 line=dict(color="blue", width=1.5),
434 ),
435 row=1,
436 col=1,
437 )
438 fig.add_trace(
439 go.Scatter(
440 x=hours,
441 y=disp_e,
442 mode="lines",
443 name="East",
444 line=dict(color="green", width=1.5),
445 ),
446 row=1,
447 col=1,
448 )
449 fig.add_trace(
450 go.Scatter(
451 x=hours,
452 y=disp_u,
453 mode="lines",
454 name="Up",
455 line=dict(color="red", width=2),
456 ),
457 row=1,
458 col=1,
459 )
460 fig.add_hline(y=0, line_color="black", line_width=0.5, row=1, col=1)
461
462 # Bottom plot: Gravity change
463 fig.add_trace(
464 go.Scatter(
465 x=hours,
466 y=dg,
467 mode="lines",
468 name="Gravity",
469 line=dict(color="purple", width=2),
470 fill="tozeroy",
471 fillcolor="rgba(128,0,128,0.3)",
472 ),
473 row=2,
474 col=1,
475 )
476 fig.add_hline(y=0, line_color="black", line_width=0.5, row=2, col=1)
477
478 fig.update_xaxes(title_text="Hour (UTC)", range=[0, 24], row=2, col=1)
479 fig.update_yaxes(title_text="Displacement (mm)", row=1, col=1)
480 fig.update_yaxes(title_text="Gravity Change (µGal)", row=2, col=1)
481
482 fig.update_layout(height=600, width=1000)
483 fig.write_html(
484 str(OUTPUT_DIR / "geophysical_tides.html"),
485 include_plotlyjs="cdn",
486 div_id="geophysical_tides",
487 )
488 print("\n [Plot saved to geophysical_tides.html]")
489
490
491def demo_magnetic_field():
492 """Demonstrate magnetic field computation."""
493 print("\n" + "=" * 70)
494 print("Magnetic Field Models Demo")
495 print("=" * 70)
496
497 # Test locations
498 locations = [
499 ("Washington DC", 38.9, -77.0, 0.0),
500 ("London", 51.5, -0.1, 0.0),
501 ("Sydney", -33.9, 151.2, 0.0),
502 ("Magnetic North", 86.5, -175.3, 0.0),
503 ("Magnetic Equator", 0.0, 0.0, 0.0),
504 ("South Atlantic Anomaly", -25.0, -50.0, 0.0),
505 ]
506
507 # Use 2025 epoch
508 decimal_year = 2025.0
509
510 print(f"\nMagnetic field at various locations (WMM2020, {decimal_year}):")
511 print("-" * 75)
512 print(f"{'Location':<25} {'Dec':>8} {'Inc':>8} {'F (nT)':>10}")
513 print("-" * 75)
514
515 for name, lat, lon, alt in locations:
516 # Compute magnetic field using WMM
517 result = wmm(np.radians(lat), np.radians(lon), alt, decimal_year)
518
519 dec = np.degrees(result.D) # Declination
520 inc = np.degrees(result.I) # Inclination
521 F = result.F # Total intensity
522
523 print(f"{name:<25} {dec:>8.2f}° {inc:>8.2f}° {F:>10.0f}")
524
525 # Show magnetic declination map concept
526 print("\n--- Magnetic Declination Grid ---")
527 lats_grid = np.array([-60, -30, 0, 30, 60])
528 lons_grid = np.array([-120, -60, 0, 60, 120])
529
530 header = "Lat\\Lon"
531 print(f"{header:<8}", end="")
532 for lon in lons_grid:
533 print(f"{lon:>8}°", end="")
534 print()
535
536 for lat in lats_grid:
537 print(f"{lat:>6}° ", end="")
538 for lon in lons_grid:
539 dec = magnetic_declination(
540 np.radians(lat), np.radians(lon), 0.0, decimal_year
541 )
542 print(f"{np.degrees(dec):>8.1f}", end="")
543 print()
544
545 # Plot magnetic declination and field intensity maps
546 if SHOW_PLOTS:
547 # Create grid for plotting
548 lat_grid = np.linspace(-80, 80, 33)
549 lon_grid = np.linspace(-180, 180, 73)
550 LAT, LON = np.meshgrid(lat_grid, lon_grid)
551
552 # Compute declination and intensity on grid
553 DEC = np.zeros_like(LAT)
554 F = np.zeros_like(LAT)
555 for i in range(LAT.shape[0]):
556 for j in range(LAT.shape[1]):
557 result = wmm(
558 np.radians(LAT[i, j]), np.radians(LON[i, j]), 0.0, decimal_year
559 )
560 DEC[i, j] = np.degrees(result.D)
561 F[i, j] = result.F
562
563 fig = make_subplots(
564 rows=1,
565 cols=2,
566 subplot_titles=(
567 f"Magnetic Declination (WMM {decimal_year:.0f})",
568 f"Magnetic Field Intensity (WMM {decimal_year:.0f})",
569 ),
570 )
571
572 # Left plot: Magnetic declination
573 fig.add_trace(
574 go.Contour(
575 x=lon_grid,
576 y=lat_grid,
577 z=DEC.T,
578 colorscale="RdBu_r",
579 contours=dict(showlines=True),
580 colorbar=dict(title="Declination (°)", x=0.45),
581 ),
582 row=1,
583 col=1,
584 )
585
586 # Right plot: Total field intensity
587 fig.add_trace(
588 go.Contour(
589 x=lon_grid,
590 y=lat_grid,
591 z=F.T,
592 colorscale="Viridis",
593 colorbar=dict(title="Intensity (nT)", x=1.0),
594 ),
595 row=1,
596 col=2,
597 )
598
599 # Mark South Atlantic Anomaly region
600 fig.add_trace(
601 go.Scatter(
602 x=[-50],
603 y=[-25],
604 mode="markers",
605 marker=dict(symbol="star", size=15, color="red"),
606 name="South Atlantic Anomaly",
607 showlegend=True,
608 ),
609 row=1,
610 col=2,
611 )
612
613 fig.update_xaxes(title_text="Longitude (°)", range=[-180, 180], row=1, col=1)
614 fig.update_yaxes(title_text="Latitude (°)", range=[-80, 80], row=1, col=1)
615 fig.update_xaxes(title_text="Longitude (°)", range=[-180, 180], row=1, col=2)
616 fig.update_yaxes(title_text="Latitude (°)", range=[-80, 80], row=1, col=2)
617
618 fig.update_layout(height=500, width=1400)
619 fig.write_html(
620 str(OUTPUT_DIR / "geophysical_magnetic_field.html"),
621 include_plotlyjs="cdn",
622 div_id="geophysical_magnetic_field",
623 )
624 print("\n [Plot saved to geophysical_magnetic_field.html]")
625
626
627def demo_magnetic_properties():
628 """Demonstrate magnetic field properties and variations."""
629 print("\n" + "=" * 70)
630 print("Magnetic Field Properties Demo")
631 print("=" * 70)
632
633 decimal_year = 2025.0
634
635 # Magnetic pole location
636 pole_lat, pole_lon = magnetic_north_pole(decimal_year)
637 print(f"\nMagnetic North Pole location ({decimal_year}):")
638 print(f" Latitude: {np.degrees(pole_lat):.2f}°N")
639 print(f" Longitude: {np.degrees(pole_lon):.2f}°E")
640
641 # Earth's dipole moment (using WMM2020 coefficients)
642 moment = dipole_moment() # Uses default WMM2020 coefficients
643 print(f"\nEarth's dipole moment (WMM2020): {moment:.4e} A·m²")
644
645 # Compare WMM and IGRF at a test location
646 lat, lon, alt = np.radians(40.0), np.radians(-100.0), 0.0
647
648 wmm_result = wmm(lat, lon, alt, decimal_year)
649 igrf_result = igrf(lat, lon, alt, decimal_year)
650
651 print(f"\nModel comparison at (40°N, 100°W):")
652 print("-" * 40)
653 print(f"{'Component':<12} {'WMM':>12} {'IGRF':>12}")
654 print("-" * 40)
655 print(f"{'X (nT)':<12} {wmm_result.X:>12.1f} {igrf_result.X:>12.1f}")
656 print(f"{'Y (nT)':<12} {wmm_result.Y:>12.1f} {igrf_result.Y:>12.1f}")
657 print(f"{'Z (nT)':<12} {wmm_result.Z:>12.1f} {igrf_result.Z:>12.1f}")
658 print(f"{'F (nT)':<12} {wmm_result.F:>12.1f} {igrf_result.F:>12.1f}")
659
660 # Secular variation
661 print("\n--- Secular Variation ---")
662 print("Magnetic field changes over time due to core dynamics.")
663 years = [2020, 2022, 2024, 2025]
664 for year in years:
665 dec = magnetic_declination(lat, lon, alt, float(year))
666 print(f" {year}: declination = {np.degrees(dec):.2f}°")
667
668
669def demo_navigation_application():
670 """Demonstrate application to navigation."""
671 print("\n" + "=" * 70)
672 print("Navigation Application Demo")
673 print("=" * 70)
674
675 # Aircraft navigation scenario
676 lat = np.radians(40.0)
677 lon = np.radians(-74.0)
678 alt = 10000.0 # meters (cruise altitude)
679 decimal_year = 2025.0
680
681 print("\nAircraft navigation correction scenario:")
682 print(f" Position: 40°N, 74°W")
683 print(f" Altitude: {alt:.0f} m")
684
685 # Magnetic declination for compass correction
686 dec = magnetic_declination(lat, lon, alt, decimal_year)
687 print(f"\n Magnetic declination: {np.degrees(dec):.2f}°")
688 print(f" -> Add {np.degrees(dec):.2f}° to magnetic heading for true heading")
689
690 # Gravity for inertial navigation
691 g_result = gravity_wgs84(lat, lon, alt)
692 print(f"\n Local gravity: {g_result.magnitude:.6f} m/s²")
693 print(f" -> Used for vertical channel in INS")
694
695 # Deflection of vertical (simplified)
696 print(f"\n Gravity deflection (N): {g_result.g_north * 1e6:.2f} µrad")
697 print(f" -> Correction for inertial alignment")
698
699 # Geoid for altitude reference (J2 approximation - latitude only)
700 N = geoid_height_j2(lat)
701 print(f"\n Geoid undulation (J2 approx): {N:.1f} m")
702 print(f" -> Ellipsoid alt = GPS alt, Orthometric alt = GPS alt - N")
703
704
705def main():
706 """Run all demonstrations."""
707 print("\n" + "#" * 70)
708 print("# PyTCL Geophysical Models Example")
709 print("#" * 70)
710
711 # Gravity models
712 demo_normal_gravity()
713 demo_gravity_models()
714 demo_geoid()
715 demo_gravity_anomalies()
716 demo_tidal_effects()
717
718 # Magnetic field models
719 demo_magnetic_field()
720 demo_magnetic_properties()
721
722 # Application
723 demo_navigation_application()
724
725 print("\n" + "=" * 70)
726 print("Example complete!")
727 if SHOW_PLOTS:
728 print("Plots saved: geophysical_gravity_latitude.html,")
729 print(" geophysical_gravity_altitude.html,")
730 print(" geophysical_tides.html,")
731 print(" geophysical_magnetic_field.html")
732 print("=" * 70)
733
734
735if __name__ == "__main__":
736 main()
Running the Example
python examples/geophysical_models.py
See Also
INS/GNSS Navigation - INS/GNSS integration
Navigation and Geodesy - Geodetic calculations
Magnetism Models - Magnetic field details