High-Precision Ephemeris
This example demonstrates using the JPL Development Ephemeris (DE) to compute high-precision positions of the Sun, Moon, and planets.
Overview
Planetary ephemerides provide high-accuracy positions essential for:
Deep space navigation: Spacecraft trajectory planning
Astronomy: Telescope pointing and observation scheduling
Satellite operations: Eclipse and conjunction predictions
Time systems: Planetary aberration corrections
Ephemeris Versions
- DE405 (1997)
JPL Planetary Ephemeris covering 1997-2050
- DE430 (2013)
Extended coverage from 1550-2650
- DE440 (2020)
Latest JPL ephemeris with improved accuracy
Examples Covered
- Sun Position
Heliocentric distance variation (Earth’s orbital eccentricity)
Perihelion and aphelion distances
Position in ICRF (International Celestial Reference Frame)
- Moon Position
Earth-centered position and velocity
Perigee and apogee variations
Lunar orbital ellipticity
- Planetary Positions
All major planets: Mercury through Neptune
Heliocentric coordinates
Ecliptic longitude and latitude
- Solar System Barycenter
Center of mass of the solar system
Jupiter’s gravitational influence
Reference for high-precision astrometry
- Reference Frames
ICRF (default inertial frame)
Ecliptic frame transformations
Earth-centered coordinates
Code Highlights
The example demonstrates:
Sun position queries with
sun_position()Moon position queries with
moon_position()Planet positions with
planet_position()Barycenter calculations with
barycenter_position()Julian date conversions with
jd_to_cal()Ephemeris version selection with
DEEphemeris()
Source Code
1"""High-precision ephemeris queries for celestial bodies.
2
3This example demonstrates using the JPL Development Ephemeris (DE) to compute
4high-precision positions of the Sun, Moon, and planets. The ephemeris kernel
5data provides accuracy to within kilometers for major solar system bodies.
6
7The example covers:
81. Basic Sun and Moon position queries
92. Planet position queries for all major planets
103. Barycenter calculations for multi-body systems
114. Different reference frame options (ICRF, ecliptic, Earth-centered)
125. Comparing different ephemeris versions (DE405, DE430, DE440)
136. Computing distances and velocities
14"""
15
16from pathlib import Path
17
18import numpy as np
19import plotly.graph_objects as go
20from plotly.subplots import make_subplots
21
22from pytcl.astronomical import (
23 DEEphemeris,
24 barycenter_position,
25 jd_to_cal,
26 moon_position,
27 planet_position,
28 sun_position,
29)
30from pytcl.astronomical.relativity import AU
31
32SHOW_PLOTS = True
33OUTPUT_DIR = Path("docs/_static/images/examples")
34OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
35
36
37def plot_sun_earth_moon_positions(
38 jd: float, title: str = "Sun-Earth-Moon System Configuration"
39) -> None:
40 """Plot Sun, Earth, and Moon positions in 3D."""
41 earth_pos = np.array([1.0, 0.0, 0.0]) * AU # Earth at ~1 AU
42 r_sun, _ = sun_position(jd)
43 r_moon, _ = moon_position(jd)
44
45 fig = go.Figure()
46
47 # Sun (scaled down for visibility)
48 fig.add_trace(
49 go.Scatter3d(
50 x=[r_sun[0] / AU],
51 y=[r_sun[1] / AU],
52 z=[r_sun[2] / AU],
53 mode="markers+text",
54 marker=dict(size=12, color="yellow", symbol="circle"),
55 text=["Sun"],
56 textposition="top center",
57 name="Sun",
58 hovertemplate="<b>Sun</b><br>Distance from origin: "
59 f"{np.linalg.norm(r_sun)/AU:.3f} AU<extra></extra>",
60 )
61 )
62
63 # Earth
64 fig.add_trace(
65 go.Scatter3d(
66 x=[earth_pos[0] / AU],
67 y=[earth_pos[1] / AU],
68 z=[earth_pos[2] / AU],
69 mode="markers+text",
70 marker=dict(size=8, color="blue", symbol="circle"),
71 text=["Earth"],
72 textposition="top center",
73 name="Earth",
74 hovertemplate="<b>Earth</b><br>Distance from Sun: "
75 f"{np.linalg.norm(earth_pos - r_sun)/AU:.3f} AU<extra></extra>",
76 )
77 )
78
79 # Moon (displaced from Earth for visibility)
80 moon_distance = np.linalg.norm(r_moon) / 1e6
81 fig.add_trace(
82 go.Scatter3d(
83 x=[r_moon[0] / AU],
84 y=[r_moon[1] / AU],
85 z=[r_moon[2] / AU],
86 mode="markers+text",
87 marker=dict(size=5, color="gray", symbol="circle"),
88 text=["Moon"],
89 textposition="top center",
90 name="Moon",
91 hovertemplate="<b>Moon</b><br>Distance from Earth: "
92 f"{moon_distance:.1f} km<extra></extra>",
93 )
94 )
95
96 # Orbital connections
97 fig.add_trace(
98 go.Scatter3d(
99 x=[r_sun[0] / AU, earth_pos[0] / AU],
100 y=[r_sun[1] / AU, earth_pos[1] / AU],
101 z=[r_sun[2] / AU, earth_pos[2] / AU],
102 mode="lines",
103 line=dict(color="orange", width=2),
104 hoverinfo="skip",
105 showlegend=False,
106 )
107 )
108
109 fig.add_trace(
110 go.Scatter3d(
111 x=[earth_pos[0] / AU, r_moon[0] / AU],
112 y=[earth_pos[1] / AU, r_moon[1] / AU],
113 z=[earth_pos[2] / AU, r_moon[2] / AU],
114 mode="lines",
115 line=dict(color="lightblue", width=1, dash="dash"),
116 hoverinfo="skip",
117 showlegend=False,
118 )
119 )
120
121 fig.update_layout(
122 title=title,
123 scene=dict(
124 xaxis_title="X (AU)",
125 yaxis_title="Y (AU)",
126 zaxis_title="Z (AU)",
127 aspectmode="data",
128 ),
129 hovermode="closest",
130 height=600,
131 showlegend=True,
132 )
133
134 if SHOW_PLOTS:
135 fig.show()
136 else:
137 fig.write_html(str(OUTPUT_DIR / "ephemeris_demo.html"))
138
139
140def plot_orbital_distances(
141 body_name: str, jd_start: float, num_points: int = 100
142) -> None:
143 """Plot distance variation over one year."""
144 if body_name.lower() == "sun":
145 pos_func = sun_position
146 title = "Sun's Distance from Earth (Eccentricity Effect)"
147 elif body_name.lower() == "moon":
148 pos_func = moon_position
149 title = "Moon's Distance from Earth (Orbital Variation)"
150 else:
151
152 def pos_func(jd: float) -> tuple:
153 """Get planet position for given Julian date."""
154 return planet_position(body_name, jd)
155
156 title = f"{body_name.capitalize()}'s Distance from Earth"
157
158 jd_array = np.linspace(jd_start, jd_start + 365.25, num_points)
159 distances = []
160 dates = []
161
162 for jd in jd_array:
163 r, _ = pos_func(jd)
164 distances.append(
165 np.linalg.norm(r) / AU
166 if body_name.lower() != "moon"
167 else np.linalg.norm(r) / 1e6
168 )
169 year, month, day, _, _, _ = jd_to_cal(jd)
170 dates.append(f"{year:04d}-{month:02d}-{day:02d}")
171
172 fig = go.Figure()
173
174 fig.add_trace(
175 go.Scatter(
176 x=dates,
177 y=distances,
178 mode="lines+markers",
179 name=f"{body_name} Distance",
180 line=dict(color="steelblue", width=2),
181 marker=dict(size=4),
182 hovertemplate="<b>Date:</b> %{x}<br><b>Distance:</b> %{y:.3f} "
183 + ("AU" if body_name.lower() != "moon" else "km")
184 + "<extra></extra>",
185 )
186 )
187
188 y_label = "Distance (AU)" if body_name.lower() != "moon" else "Distance (km)"
189 fig.update_layout(
190 title=title,
191 xaxis_title="Date",
192 yaxis_title=y_label,
193 hovermode="x unified",
194 height=500,
195 plot_bgcolor="rgba(240,240,240,0.5)",
196 xaxis_tickangle=-45,
197 )
198
199 if SHOW_PLOTS:
200 fig.show()
201 else:
202 fig.write_html(str(OUTPUT_DIR / "ephemeris_demo_distance.html"))
203
204
205def example_sun_position():
206 """Query the Sun's position at specific times."""
207 print("=" * 70)
208 print("EXAMPLE 1: Sun Position Queries")
209 print("=" * 70)
210
211 # Create ephemeris object (defaults to DE440)
212 eph = DEEphemeris()
213
214 # J2000.0 epoch (January 1, 2000, 12:00 UT)
215 jd_j2000 = 2451545.0
216
217 # Query Sun's position
218 r_sun, v_sun = sun_position(jd_j2000)
219
220 print(f"\nJ2000.0 Epoch: JD {jd_j2000}")
221 print(f"Sun Position (ICRF):")
222 print(f" X: {r_sun[0]:15.3f} m = {r_sun[0]/AU:10.6f} AU")
223 print(f" Y: {r_sun[1]:15.3f} m = {r_sun[1]/AU:10.6f} AU")
224 print(f" Z: {r_sun[2]:15.3f} m = {r_sun[2]/AU:10.6f} AU")
225 print(f" Distance: {np.linalg.norm(r_sun)/AU:.6f} AU")
226
227 print(f"\nSun Velocity (ICRF):")
228 print(f" VX: {v_sun[0]:12.3f} m/s")
229 print(f" VY: {v_sun[1]:12.3f} m/s")
230 print(f" VZ: {v_sun[2]:12.3f} m/s")
231 print(f" Speed: {np.linalg.norm(v_sun):.3f} m/s")
232
233 # Compute Sun's distance variation over a year
234 print("\n" + "-" * 70)
235 print("Sun's Distance Throughout 2000:")
236 print("-" * 70)
237
238 distances = []
239 julian_dates = np.linspace(jd_j2000, jd_j2000 + 365.25, 13)
240
241 for jd in julian_dates:
242 year, month, day, _, _, _ = jd_to_cal(jd)
243 r, _ = sun_position(jd)
244 dist_au = np.linalg.norm(r) / AU
245 distances.append(dist_au)
246 print(f" {year:4d}-{month:2d}-{day:2d} Distance: {dist_au:.6f} AU")
247
248 print(f"\nPerigee (minimum): {min(distances):.6f} AU")
249 print(f"Apogee (maximum): {max(distances):.6f} AU")
250 print(
251 f"Variation: {max(distances) - min(distances):.6f} AU "
252 f"({100*(max(distances)-min(distances))/np.mean(distances):.2f}%)"
253 )
254
255 # Visualize the Sun's orbital distance throughout the year
256 plot_orbital_distances("sun", jd_j2000, num_points=365)
257
258
259def example_moon_position():
260 """Query the Moon's position and properties."""
261 print("\n" + "=" * 70)
262 print("EXAMPLE 2: Moon Position Queries")
263 print("=" * 70)
264
265 jd_j2000 = 2451545.0
266
267 # Query Moon's position
268 r_moon, v_moon = moon_position(jd_j2000)
269
270 print(f"\nJ2000.0 Epoch: JD {jd_j2000}")
271 print(f"Moon Position (Earth-centered ICRF):")
272 print(f" X: {r_moon[0]:12.3f} m = {r_moon[0]/1e6:10.1f} km")
273 print(f" Y: {r_moon[1]:12.3f} m = {r_moon[1]/1e6:10.1f} km")
274 print(f" Z: {r_moon[2]:12.3f} m = {r_moon[2]/1e6:10.1f} km")
275 print(f" Distance: {np.linalg.norm(r_moon)/1e6:.1f} km")
276
277 print(f"\nMoon Velocity (Earth-centered ICRF):")
278 print(
279 f" Speed: {np.linalg.norm(v_moon):.3f} m/s = {np.linalg.norm(v_moon)*86400/1e3:.1f} km/day"
280 )
281
282 # Lunar distance variation (orbital ellipticity)
283 print("\n" + "-" * 70)
284 print("Moon's Distance Variation (showing orbital ellipticity):")
285 print("-" * 70)
286
287 distances = []
288 times = np.linspace(0, 27.32, 27) # ~lunar month in days
289 julian_dates = jd_j2000 + times
290
291 for jd in julian_dates:
292 r, _ = moon_position(jd)
293 dist_km = np.linalg.norm(r) / 1e6
294 distances.append(dist_km)
295
296 print(f"Perigee (closest): {min(distances):.1f} km")
297 print(f"Apogee (farthest): {max(distances):.1f} km")
298 print(f"Mean distance: {np.mean(distances):.1f} km")
299 print(
300 f"Variation: {max(distances) - min(distances):.1f} km "
301 f"({100*(max(distances)-min(distances))/np.mean(distances):.1f}%)"
302 )
303
304 # Visualize the Sun-Earth-Moon configuration
305 jd_j2000 = 2451545.0
306 plot_sun_earth_moon_positions(jd_j2000)
307
308 # Visualize the Moon's orbital distance variation
309 plot_orbital_distances("moon", jd_j2000, num_points=365)
310
311
312def example_planet_positions():
313 """Query positions of all major planets."""
314 print("\n" + "=" * 70)
315 print("EXAMPLE 3: Planetary Positions")
316 print("=" * 70)
317
318 jd_j2000 = 2451545.0
319
320 planets = [
321 "mercury",
322 "venus",
323 "mars",
324 "jupiter",
325 "saturn",
326 "uranus",
327 "neptune",
328 ]
329
330 print(f"\nPlanetary Heliocentric Positions at J2000.0:")
331 print("-" * 70)
332 print(f"{'Planet':<10} {'Distance (AU)':<16} {'Longitude':<12} {'Latitude':<12}")
333 print("-" * 70)
334
335 for planet_name in planets:
336 try:
337 r, _ = planet_position(planet_name, jd_j2000)
338 dist_au = np.linalg.norm(r) / AU
339
340 # Compute ecliptic coordinates
341 lon = np.arctan2(r[1], r[0])
342 lat = np.arcsin(r[2] / np.linalg.norm(r))
343
344 print(
345 f"{planet_name:<10} {dist_au:<16.6f} {np.degrees(lon):>10.2f}° {np.degrees(lat):>10.2f}°"
346 )
347 except Exception as e:
348 print(f"{planet_name:<10} [Error: {str(e)}]")
349
350
351def example_barycenter():
352 """Compute solar system barycenter positions."""
353 print("\n" + "=" * 70)
354 print("EXAMPLE 4: Solar System Barycenter")
355 print("=" * 70)
356
357 jd_j2000 = 2451545.0
358
359 # Get Sun position relative to solar system barycenter
360 r_barycenter, v_barycenter = barycenter_position("sun", jd_j2000)
361
362 print(f"\nSolar System Barycenter at J2000.0:")
363 print(f" X: {r_barycenter[0]:12.3f} m")
364 print(f" Y: {r_barycenter[1]:12.3f} m")
365 print(f" Z: {r_barycenter[2]:12.3f} m")
366 print(f" Distance from origin: {np.linalg.norm(r_barycenter):.3f} m")
367 print(f" Velocity magnitude: {np.linalg.norm(v_barycenter):.6f} m/s")
368
369 # Compare with Jupiter position
370 r_jupiter, _ = planet_position("jupiter", jd_j2000)
371 print(f"\nComparison with Jupiter position:")
372 print(
373 f" Jupiter distance from barycenter: {np.linalg.norm(r_jupiter - r_barycenter):.3f} m"
374 )
375 print(f" This shows Jupiter's significant gravitational influence")
376
377
378def example_frame_transformations():
379 """Demonstrate reference frame options."""
380 print("\n" + "=" * 70)
381 print("EXAMPLE 5: Reference Frame Transformations")
382 print("=" * 70)
383
384 jd_j2000 = 2451545.0
385 eph = DEEphemeris()
386
387 print(f"\nSun's position in different reference frames at J2000.0:")
388 print("-" * 70)
389
390 # ICRF (default)
391 r_icrf, _ = eph.sun_position(jd_j2000, frame="ICRF")
392 print(f"ICRF Frame (International Celestial Reference Frame):")
393 print(f" X: {r_icrf[0]/AU:10.6f} AU")
394 print(f" Y: {r_icrf[1]/AU:10.6f} AU")
395 print(f" Z: {r_icrf[2]/AU:10.6f} AU")
396
397 # Ecliptic frame
398 try:
399 r_ecliptic, _ = eph.sun_position(jd_j2000, frame="ecliptic")
400 print(f"\nEcliptic Frame:")
401 print(f" X: {r_ecliptic[0]/AU:10.6f} AU")
402 print(f" Y: {r_ecliptic[1]/AU:10.6f} AU")
403 print(f" Z: {r_ecliptic[2]/AU:10.6f} AU (small, as expected)")
404 except NotImplementedError:
405 print("\nEcliptic frame transformation would be applied here")
406
407
408def example_time_series():
409 """Generate time series of object positions (useful for animation)."""
410 print("\n" + "=" * 70)
411 print("EXAMPLE 6: Time Series for Visualization")
412 print("=" * 70)
413
414 jd_start = 2451545.0 # J2000
415 dates = jd_start + np.linspace(0, 365, 13) # Monthly positions
416
417 sun_positions = []
418 moon_positions = []
419
420 print("\nComputing 12-month ephemeris...")
421 for jd in dates:
422 r_sun, _ = sun_position(jd)
423 r_moon, _ = moon_position(jd)
424 sun_positions.append(r_sun)
425 moon_positions.append(r_moon)
426
427 sun_positions = np.array(sun_positions)
428 moon_positions = np.array(moon_positions)
429
430 print(f"Computed {len(dates)} positions for Sun and Moon")
431 print(f"\nSun orbit statistics:")
432 print(f" Min distance: {np.min(np.linalg.norm(sun_positions, axis=1))/AU:.6f} AU")
433 print(f" Max distance: {np.max(np.linalg.norm(sun_positions, axis=1))/AU:.6f} AU")
434 print(f" Orbit plane:")
435 print(f" Min Z: {np.min(sun_positions[:, 2])/AU:.8f} AU")
436 print(f" Max Z: {np.max(sun_positions[:, 2])/AU:.8f} AU")
437
438 print(f"\nMoon orbit statistics:")
439 print(
440 f" Min distance: {np.min(np.linalg.norm(moon_positions, axis=1))/1e6:.1f} km"
441 )
442 print(
443 f" Max distance: {np.max(np.linalg.norm(moon_positions, axis=1))/1e6:.1f} km"
444 )
445
446
447def example_ephemeris_versions():
448 """Compare different ephemeris versions."""
449 print("\n" + "=" * 70)
450 print("EXAMPLE 7: Ephemeris Version Comparison")
451 print("=" * 70)
452
453 jd_test = 2451545.0
454
455 print(f"\nNote: Different ephemeris versions available:")
456 print(f" DE405: JPL Planetary Ephemeris, 1997-2050")
457 print(f" DE430: JPL Planetary Ephemeris, 1550-2650")
458 print(f" DE432s: Short version for limited time range")
459 print(f" DE440: Latest JPL ephemeris, 1550-2650")
460 print(f"\nDepending on jplephem version, different kernels can be loaded")
461 print(f"Default used in this example: DE440 (if available)")
462
463 eph = DEEphemeris(version="DE440")
464 r_sun, _ = eph.sun_position(jd_test)
465 print(f"\nSun position (DE440 at J2000.0): {np.linalg.norm(r_sun)/AU:.6f} AU")
466
467
468if __name__ == "__main__":
469 """Run all examples."""
470 print("\n")
471 print("╔" + "=" * 68 + "╗")
472 print("║" + " " * 68 + "║")
473 print("║" + " High-Precision Ephemeris Demonstrations".center(68) + "║")
474 print("║" + " " * 68 + "║")
475 print("╚" + "=" * 68 + "╝")
476
477 # Run examples
478 example_sun_position()
479 example_moon_position()
480 example_planet_positions()
481 example_barycenter()
482 example_frame_transformations()
483 example_time_series()
484 example_ephemeris_versions()
485
486 OUTPUT_DIR = Path("docs/_static/images/examples")
487 OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
488
489 print("\n" + "=" * 70)
490 print("All ephemeris examples completed successfully!")
491 print("=" * 70 + "\n")
Running the Example
python examples/ephemeris_demo.py
See Also
Orbital Mechanics - Orbital propagation
Relativistic Effects - Relativistic corrections
coordinate_systems - Coordinate transformations