Skip to content

Commit 3021b08

Browse files
authored
Merge pull request #976 from BazarganDev/main
Created ISS Tracker
2 parents 4d3ad1d + 3232ec0 commit 3021b08

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

iss_tracker/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# ISS Tracker 🛰️
2+
A Python script to fetch TLE data of the ISS (ZARYA) satellite, calculate its position, predict its orbit and visualize its trajectory on an interactive map. Thanks to [Celestrak](https://celestrak.org/) We can access to the TLE data of satellites, including International Space Station.
3+
4+
## How to Run
5+
- Install required modules: `pip install -r requirements.txt`
6+
- Run the script: `python3 tracker.py`
7+
- Wait for the map to launch.
8+
9+
## Key Concepts
10+
### What is a TLE?
11+
A Two-Line Element Set (TLE) is a standardized format used to describe the orbit of a satellite. It consists of two lines of data that include important orbital parameters, such as the satellite's position, velocity, and other relevant information at a specific time known as the epoch (Source: ["Two-line-element set"](https://en.wikipedia.org/wiki/Two-line_element_set)).
12+
13+
### What is a Geocentric Position?
14+
A geocentric position refers to a viewpoint or coordinate system that is centered on the Earth. In astronomy, it describes a model where the Earth is considered the center of the universe, with celestial bodies like the Sun and planets (or even satellites) orbiting around it (Source: ["Earth-centered, Earth-fixed coordinate system"](https://en.wikipedia.org/wiki/Earth-centered,_Earth-fixed_coordinate_system) and ["Geocentric model"](https://en.wikipedia.org/wiki/Geocentric_model)).
15+
16+
### What is a Subpoint Position?
17+
In Astronomy, the subpoint position (or subsatellite point) of a satellite refers to the point on the Earth's surface that is directly beneath the satellite at any given moment. It is defined by its geographical coordinates, latitude and longitude (Source: ["Geodesy for the Layman" by U.S. Geological Survey](https://www.ngs.noaa.gov/PUBS_LIB/Geodesy4Layman/TR80003D.HTM#ZZ9)). This point is significant because it represents the satellite's ground track, which is the path that the satellite appears to trace over the Earth's surface as it orbits (Source: ["Satellite Communications" by Dennis Roddy](https://books.google.com/books/about/Satellite_Communications_Fourth_Edition.html?id=2KEt_hFyjwgC)).
18+
19+
### What is International Date Line (IDL)?
20+
The International Date Line is an imaginary line that runs from the North Pole to the South Pole, primarily along the 180th meridian in the Pacific Ocean. It serves as the boundary between two consecutive calendar dates, meaning when you cross it, you either gain or lose a day depending on the direction you are traveling (Source: ["The international date line, explained"](https://www.livescience.com/44292-international-date-line-explained.html) and ["International Date Line"](https://www.britannica.com/topic/International-Date-Line)).
21+
22+
### What libraries are used in the script?
23+
- `Skyfield` ([Documentation](https://rhodesmill.org/skyfield/))
24+
- `Folium` ([Documentation](https://python-visualization.github.io/folium/latest/index.html))
25+
- `Selenium` ([Documentation](https://www.selenium.dev/documentation/))
26+
- `Datetime` ([Documentation](https://docs.python.org/3/library/datetime.html))
27+
- `Time` ([Documentation](https://docs.python.org/3/library/time.html))
28+
- `OS` ([Documentation](https://docs.python.org/3/library/os.html))
29+
30+
## Output
31+
![Screenshot_1](https://github.com/user-attachments/assets/1027863f-fe7a-46ee-abb6-daef4b6a12a3)
32+
![Screenshot_2](https://github.com/user-attachments/assets/4ee308a3-41b1-4bb0-b02a-e394f090444b)
33+
34+
## Author
35+
Mohammad Bazargan ([BazarganDev](https://github.com/BazarganDev))
36+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ISS (ZARYA)
2+
1 25544U 98067A 24284.58548122 .00037521 00000+0 67055-3 0 9998
3+
2 25544 51.6398 103.8131 0008430 61.8768 54.0890 15.49615403476441

iss_tracker/map/tracker_map.html

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
5+
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
6+
7+
<script>
8+
L_NO_TOUCH = false;
9+
L_DISABLE_3D = false;
10+
</script>
11+
12+
<style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>
13+
<style>#map {position:absolute;top:0;bottom:0;right:0;left:0;}</style>
14+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js"></script>
15+
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
16+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
17+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
18+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css"/>
19+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"/>
20+
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>
21+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css"/>
22+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
23+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>
24+
25+
<meta name="viewport" content="width=device-width,
26+
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
27+
<style>
28+
#map_b93ce63ad7602b7cbbbff28b6a0eb107 {
29+
position: relative;
30+
width: 100.0%;
31+
height: 100.0%;
32+
left: 0.0%;
33+
top: 0.0%;
34+
}
35+
.leaflet-container { font-size: 1rem; }
36+
</style>
37+
38+
</head>
39+
<body>
40+
41+
42+
<div class="folium-map" id="map_b93ce63ad7602b7cbbbff28b6a0eb107" ></div>
43+
44+
</body>
45+
<script>
46+
47+
48+
var map_b93ce63ad7602b7cbbbff28b6a0eb107 = L.map(
49+
"map_b93ce63ad7602b7cbbbff28b6a0eb107",
50+
{
51+
center: [-24.49466279314546, 174.8304784523187],
52+
crs: L.CRS.EPSG3857,
53+
zoom: 2,
54+
zoomControl: true,
55+
preferCanvas: false,
56+
}
57+
);
58+
59+
60+
61+
62+
63+
var tile_layer_b0f2ec3d4acb367411dae569d44686fd = L.tileLayer(
64+
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
65+
{"attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors", "detectRetina": false, "maxNativeZoom": 19, "maxZoom": 19, "minZoom": 0, "noWrap": false, "opacity": 1, "subdomains": "abc", "tms": false}
66+
);
67+
68+
69+
tile_layer_b0f2ec3d4acb367411dae569d44686fd.addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);
70+
71+
72+
var marker_b8fc8ee22221dd8c06d6f1968af1f3ad = L.marker(
73+
[-24.49466279314546, 174.8304784523187],
74+
{}
75+
).addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);
76+
77+
78+
var icon_1b762bdfb77549cf0eab72ad55ae177e = L.AwesomeMarkers.icon(
79+
{"extraClasses": "fa-rotate-0", "icon": "satellite", "iconColor": "white", "markerColor": "red", "prefix": "fa"}
80+
);
81+
marker_b8fc8ee22221dd8c06d6f1968af1f3ad.setIcon(icon_1b762bdfb77549cf0eab72ad55ae177e);
82+
83+
84+
var popup_415b721edb3f756ae8509f6ab6dd3a7c = L.popup({"maxWidth": "100%"});
85+
86+
87+
88+
var html_22cf792e75002a312b5bbcf16702a93a = $(`<div id="html_22cf792e75002a312b5bbcf16702a93a" style="width: 100.0%; height: 100.0%;">International Space Station (ZARYA)</div>`)[0];
89+
popup_415b721edb3f756ae8509f6ab6dd3a7c.setContent(html_22cf792e75002a312b5bbcf16702a93a);
90+
91+
92+
93+
marker_b8fc8ee22221dd8c06d6f1968af1f3ad.bindPopup(popup_415b721edb3f756ae8509f6ab6dd3a7c)
94+
;
95+
96+
97+
98+
99+
marker_b8fc8ee22221dd8c06d6f1968af1f3ad.bindTooltip(
100+
`<div>
101+
ISS (Lat: -24.49466279314546, Lon: 174.8304784523187)
102+
</div>`,
103+
{"sticky": true}
104+
);
105+
106+
107+
var poly_line_fd95aaab57813a5edd7da1823d63b112 = L.polyline(
108+
[[-24.49466279314546, 174.8304784523187], [-21.621759670448842, 177.41262905393498], [-18.700685873400722, 179.88510044569458], [-15.739687648306464, 182.26604285518965], [-12.74622977541998, 184.5723514473874], [-9.727157757642022, 186.81985369058035], [-6.688842065892132, 189.02351267892686], [-3.637307700241382, 191.19763664680653], [-0.5783525903980175, 193.3560897575556], [2.482341575191149, 195.51250232631867], [5.5391025126393965, 197.68048045805725], [8.586160936279613, 199.8738159745877], [11.617541011569852, 202.1066976343822], [14.626944056449638, 204.39392403561783], [17.607621832657372, 206.75111711086103], [20.552235768956205, 209.19493248700147], [23.45269861133681, 211.74325872683926], [26.299995478945643, 214.41539090537194], [29.083982406817643, 217.23215419753993], [31.793162624157702, 220.21593910160166], [34.41444470754525, 223.39059061650497], [36.932893261647926, 226.7810688551601], [39.331493006223745, 230.4127698935521], [41.590962112491255, 234.31036902883815], [43.689670630619595, 238.49603747948203], [45.603743135142956, 242.98691234214414], [47.30744544942126, 247.7918040752284], [48.773961408776586, 252.90734309829452], [49.97663859064275, 258.314106711376], [50.890702587848104, 263.9736616893207], [51.495302729208376, 269.82772007738515], [51.77558751276012, 275.8004652714232], [51.72438697288365, 281.80437954132236], [51.34308771413599, 287.7487397388982], [50.641462510733525, 293.54888748351556], [49.636501787586134, 299.1340493840284], [48.35055739189671, 304.4520940315297], [46.809233700082515, 309.4707792229043], [45.03942093494068, 314.17611172517786], [43.0677210780296, 318.5689797561918], [40.919355013664045, 322.6612034398833], [38.61751975013718, 326.4718175653756], [36.18310332158709, 330.02400984527765], [33.634650655065904, 333.34283416226015], [30.988486160673993, 336.4536396280102], [28.2589213382775, 339.381079929084], [25.458498349203822, 342.14855459045106], [22.5982389800872, 344.7779518549352], [19.68788181565584, 347.28959116638987], [16.73609937199809, 349.70229109634204], [13.750692516235175, 352.0335117052305], [10.738762758381748, 354.2995379020087], [7.706864770331067, 356.5156829566689], [4.6611423516539565, 358.6964999768141], [1.6074514134252051, 360.8559949116242], [-1.4485263616780886, 363.0078383337469], [-4.50117555073232, 365.16557548652867], [-7.544841132749383, 367.342835280184], [-10.573721193684085, 369.55353931008096], [-13.581754645582292, 371.81211163561574], [-16.562500389189434, 374.13368893935996], [-19.509004347281397, 376.53432857601285], [-22.413650894999385, 379.0312085447993], [-25.26799555414489, 381.64280801045044], [-28.06257663083526, 384.38904887954556], [-30.786705121061832, 387.29136715444633], [-33.428235226837884, 390.37266635387294], [-35.973322976278226, 393.65708361778826], [-38.40618869265097, 397.169472923572], [-40.70891143869174, 400.9344828695082], [-42.86130071689664, 404.97508821162705], [-44.840911932257725, 409.31044334893113], [-46.62329373029699, 413.95299190080704], [-48.18256853966267, 418.90492573605627], [-49.49243738393764, 424.15436334049406], [-50.52764743708105, 429.6719838266909], [-51.26585339591142, 435.4091831638401], [-51.6896531796579, 441.29888183422986], [-51.78843542070986, 447.2596737660014], [-51.55962099221179, 453.203043427122], [-51.00897688921455, 459.04224876091934], [-50.1499164448394, 464.7007800706127], [-49.00197553993526, 470.1184844182884], [-47.58884328179974, 475.25437735165156], [-45.93635889732174, 480.0862938692678], [-44.070787267839144, 484.6083090408083], [-42.01753104669398, 488.8270819321435], [-39.80029990584469, 492.75807748553245], [-37.44067185055923, 496.42225402090617], [-34.95794762802237, 499.8434641140519], [-32.36920029850538, 503.0465866461428]],
109+
{"bubblingMouseEvents": true, "color": "black", "dashArray": "5", "dashOffset": null, "fill": false, "fillColor": "black", "fillOpacity": 0.2, "fillRule": "evenodd", "lineCap": "round", "lineJoin": "round", "noClip": false, "opacity": 1.0, "smoothFactor": 1.0, "stroke": true, "weight": 1}
110+
).addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);
111+
112+
</script>
113+
</html>

iss_tracker/requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
folium==0.17.0
2+
selenium==4.25.0
3+
skyfield==1.49
4+

iss_tracker/tracker.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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

Comments
 (0)