1+ # Import necessary modules
2+ from datetime import datetime , timedelta
3+ from skyfield .iokit import parse_tle_file
4+ from skyfield .api import load
5+ from selenium import webdriver
6+ import folium
7+ import time
8+ import os
9+
10+
11+ # Constants
12+ TLE_FILENAME = "data_files/iss_zarya_tle.tle"
13+ TLE_URL = "https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=TLE"
14+ MAP_FILENAME = "map/tracker_map.html"
15+ MAP_ZOOM_START = 2
16+ ORBIT_DURATION_MINUTES = 90
17+ UPDATE_INTERVAL_SECONDS = 60
18+
19+
20+ # Functions
21+ def download_tle_file ():
22+ """
23+ If the TLE file is missing or is outdated, download the latest data.
24+ """
25+ if not load .exists (TLE_FILENAME ) or load .days_old (TLE_FILENAME ) > 1.0 :
26+ try :
27+ load .download (TLE_URL , filename = TLE_FILENAME )
28+ except Exception as e :
29+ print (f"ERROR: Failed to download the TLE data.{ e } " )
30+ exit (1 )
31+
32+
33+ def load_satellite_data ():
34+ """
35+ Load the satellite data from the TLE file.
36+ """
37+ with load .open (TLE_FILENAME ) as f :
38+ satellites = list (parse_tle_file (f , load .timescale ()))
39+ # Index ISS (ZARYA) by NORADID number.
40+ return {sat .model .satnum : sat for sat in satellites }[25544 ]
41+
42+
43+ def create_map (sat_lat , sat_lon ):
44+ """
45+ Create a new map with the ISS's current position.
46+ """
47+ # Create a map centered onto the ISS position.
48+ iss_map = folium .Map (
49+ location = [sat_lat , sat_lon ],
50+ zoom_start = MAP_ZOOM_START
51+ )
52+ # Pinpoint the satellite's current position on the map.
53+ folium .Marker (
54+ location = [sat_lat , sat_lon ],
55+ tooltip = f"ISS (Lat: { sat_lat } , Lon: { sat_lon } )" ,
56+ popup = "International Space Station (ZARYA)" ,
57+ icon = folium .Icon (color = "red" , icon = "satellite" , prefix = "fa" )
58+ ).add_to (iss_map )
59+ return iss_map
60+
61+
62+ def predict_orbit (satellite , current_time ):
63+ """
64+ Predict the orbit of the satellite by predicting its future poitions.
65+ ISS completes one orbit around the Earth in approximately 90 minutes.
66+ """
67+ # Add current position of the satellite
68+ current_sat_lat = satellite .at (current_time ).subpoint ().latitude .degrees
69+ current_sat_lon = satellite .at (current_time ).subpoint ().longitude .degrees
70+ orbit_coordinates = [(current_sat_lat , current_sat_lon )]
71+ for i in range (1 , ORBIT_DURATION_MINUTES + 1 ):
72+ future_time = current_time + timedelta (minutes = i )
73+ future_geocentric_pos = satellite .at (future_time )
74+ future_sub_pos = future_geocentric_pos .subpoint ()
75+ future_sat_lat = future_sub_pos .latitude .degrees
76+ future_sat_lon = future_sub_pos .longitude .degrees
77+ # Longitude Adjustment: Check for large jumps in longitude.
78+ if abs (future_sat_lon - orbit_coordinates [- 1 ][1 ]) > 180 :
79+ if future_sat_lon < orbit_coordinates [- 1 ][1 ]:
80+ future_sat_lon += 360
81+ else :
82+ future_sat_lon -= 360
83+ # Add the fixed coordinates to the list of orbit coordinates.
84+ orbit_coordinates .append ((future_sat_lat , future_sat_lon ))
85+ return orbit_coordinates
86+
87+
88+ def main ():
89+ download_tle_file ()
90+ satellite = load_satellite_data ()
91+ driver = webdriver .Firefox ()
92+ driver .get (f"file:///{ os .path .abspath (MAP_FILENAME )} " )
93+ while True :
94+ current_time = datetime .utcnow ()
95+ t = load .timescale ().utc (
96+ current_time .year ,
97+ current_time .month ,
98+ current_time .day ,
99+ current_time .hour ,
100+ current_time .minute ,
101+ current_time .second
102+ )
103+ geocentric_pos = satellite .at (t )
104+ sub_pos = geocentric_pos .subpoint ()
105+ sat_lat = sub_pos .latitude .degrees
106+ sat_lon = sub_pos .longitude .degrees
107+ iss_map = create_map (sat_lat , sat_lon )
108+ orbit_coordinates = predict_orbit (satellite , t )
109+ folium .PolyLine (
110+ locations = orbit_coordinates ,
111+ color = "black" ,
112+ weight = 1 ,
113+ dash_array = "5"
114+ ).add_to (iss_map )
115+ iss_map .save (MAP_FILENAME )
116+ driver .refresh ()
117+ time .sleep (UPDATE_INTERVAL_SECONDS )
118+
119+
120+ # Ensure the "main" function is only executed when the script is run directly.
121+ if __name__ == "__main__" :
122+ main ()
0 commit comments