Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created ISS Tracker #976

Merged
merged 9 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions iss_tracker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ISS Tracker 🛰️
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.

## How to Run
- Install required modules: `pip install -r requirements.txt`
- Run the script: `python3 tracker.py`
- Wait for the map to launch.

## Key Concepts
### What is a TLE?
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)).

### What is a Geocentric Position?
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)).

### What is a Subpoint Position?
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)).

### What is International Date Line (IDL)?
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)).

### What libraries are used in the script?
- `Skyfield` ([Documentation](https://rhodesmill.org/skyfield/))
- `Folium` ([Documentation](https://python-visualization.github.io/folium/latest/index.html))
- `Selenium` ([Documentation](https://www.selenium.dev/documentation/))
- `Datetime` ([Documentation](https://docs.python.org/3/library/datetime.html))
- `Time` ([Documentation](https://docs.python.org/3/library/time.html))
- `OS` ([Documentation](https://docs.python.org/3/library/os.html))

## Output
![Screenshot_1](https://github.com/user-attachments/assets/1027863f-fe7a-46ee-abb6-daef4b6a12a3)
![Screenshot_2](https://github.com/user-attachments/assets/4ee308a3-41b1-4bb0-b02a-e394f090444b)

## Author
Mohammad Bazargan ([BazarganDev](https://github.com/BazarganDev))

3 changes: 3 additions & 0 deletions iss_tracker/data_files/iss_zarya_tle.tle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ISS (ZARYA)
1 25544U 98067A 24284.58548122 .00037521 00000+0 67055-3 0 9998
2 25544 51.6398 103.8131 0008430 61.8768 54.0890 15.49615403476441
113 changes: 113 additions & 0 deletions iss_tracker/map/tracker_map.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>

<meta http-equiv="content-type" content="text/html; charset=UTF-8" />

<script>
L_NO_TOUCH = false;
L_DISABLE_3D = false;
</script>

<style>html, body {width: 100%;height: 100%;margin: 0;padding: 0;}</style>
<style>#map {position:absolute;top:0;bottom:0;right:0;left:0;}</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css"/>

<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
#map_b93ce63ad7602b7cbbbff28b6a0eb107 {
position: relative;
width: 100.0%;
height: 100.0%;
left: 0.0%;
top: 0.0%;
}
.leaflet-container { font-size: 1rem; }
</style>

</head>
<body>


<div class="folium-map" id="map_b93ce63ad7602b7cbbbff28b6a0eb107" ></div>

</body>
<script>


var map_b93ce63ad7602b7cbbbff28b6a0eb107 = L.map(
"map_b93ce63ad7602b7cbbbff28b6a0eb107",
{
center: [-24.49466279314546, 174.8304784523187],
crs: L.CRS.EPSG3857,
zoom: 2,
zoomControl: true,
preferCanvas: false,
}
);





var tile_layer_b0f2ec3d4acb367411dae569d44686fd = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{"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}
);


tile_layer_b0f2ec3d4acb367411dae569d44686fd.addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);


var marker_b8fc8ee22221dd8c06d6f1968af1f3ad = L.marker(
[-24.49466279314546, 174.8304784523187],
{}
).addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);


var icon_1b762bdfb77549cf0eab72ad55ae177e = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "satellite", "iconColor": "white", "markerColor": "red", "prefix": "fa"}
);
marker_b8fc8ee22221dd8c06d6f1968af1f3ad.setIcon(icon_1b762bdfb77549cf0eab72ad55ae177e);


var popup_415b721edb3f756ae8509f6ab6dd3a7c = L.popup({"maxWidth": "100%"});



var html_22cf792e75002a312b5bbcf16702a93a = $(`<div id="html_22cf792e75002a312b5bbcf16702a93a" style="width: 100.0%; height: 100.0%;">International Space Station (ZARYA)</div>`)[0];
popup_415b721edb3f756ae8509f6ab6dd3a7c.setContent(html_22cf792e75002a312b5bbcf16702a93a);



marker_b8fc8ee22221dd8c06d6f1968af1f3ad.bindPopup(popup_415b721edb3f756ae8509f6ab6dd3a7c)
;




marker_b8fc8ee22221dd8c06d6f1968af1f3ad.bindTooltip(
`<div>
ISS (Lat: -24.49466279314546, Lon: 174.8304784523187)
</div>`,
{"sticky": true}
);


var poly_line_fd95aaab57813a5edd7da1823d63b112 = L.polyline(
[[-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]],
{"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}
).addTo(map_b93ce63ad7602b7cbbbff28b6a0eb107);

</script>
</html>
4 changes: 4 additions & 0 deletions iss_tracker/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
folium==0.17.0
selenium==4.25.0
skyfield==1.49

122 changes: 122 additions & 0 deletions iss_tracker/tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Import necessary modules
from datetime import datetime, timedelta
from skyfield.iokit import parse_tle_file
from skyfield.api import load
from selenium import webdriver
import folium
import time
import os


# Constants
TLE_FILENAME = "data_files/iss_zarya_tle.tle"
TLE_URL = "https://celestrak.org/NORAD/elements/gp.php?CATNR=25544&FORMAT=TLE"
MAP_FILENAME = "map/tracker_map.html"
MAP_ZOOM_START = 2
ORBIT_DURATION_MINUTES = 90
UPDATE_INTERVAL_SECONDS = 60


# Functions
def download_tle_file():
"""
If the TLE file is missing or is outdated, download the latest data.
"""
if not load.exists(TLE_FILENAME) or load.days_old(TLE_FILENAME) > 1.0:
try:
load.download(TLE_URL, filename=TLE_FILENAME)
except Exception as e:
print(f"ERROR: Failed to download the TLE data.{e}")
exit(1)


def load_satellite_data():
"""
Load the satellite data from the TLE file.
"""
with load.open(TLE_FILENAME) as f:
satellites = list(parse_tle_file(f, load.timescale()))
# Index ISS (ZARYA) by NORADID number.
return {sat.model.satnum: sat for sat in satellites}[25544]


def create_map(sat_lat, sat_lon):
"""
Create a new map with the ISS's current position.
"""
# Create a map centered onto the ISS position.
iss_map = folium.Map(
location=[sat_lat, sat_lon],
zoom_start=MAP_ZOOM_START
)
# Pinpoint the satellite's current position on the map.
folium.Marker(
location=[sat_lat, sat_lon],
tooltip=f"ISS (Lat: {sat_lat}, Lon: {sat_lon})",
popup="International Space Station (ZARYA)",
icon=folium.Icon(color="red", icon="satellite", prefix="fa")
).add_to(iss_map)
return iss_map


def predict_orbit(satellite, current_time):
"""
Predict the orbit of the satellite by predicting its future poitions.
ISS completes one orbit around the Earth in approximately 90 minutes.
"""
# Add current position of the satellite
current_sat_lat = satellite.at(current_time).subpoint().latitude.degrees
current_sat_lon = satellite.at(current_time).subpoint().longitude.degrees
orbit_coordinates = [(current_sat_lat, current_sat_lon)]
for i in range(1, ORBIT_DURATION_MINUTES + 1):
future_time = current_time + timedelta(minutes=i)
future_geocentric_pos = satellite.at(future_time)
future_sub_pos = future_geocentric_pos.subpoint()
future_sat_lat = future_sub_pos.latitude.degrees
future_sat_lon = future_sub_pos.longitude.degrees
# Longitude Adjustment: Check for large jumps in longitude.
if abs(future_sat_lon - orbit_coordinates[-1][1]) > 180:
if future_sat_lon < orbit_coordinates[-1][1]:
future_sat_lon += 360
else:
future_sat_lon -= 360
# Add the fixed coordinates to the list of orbit coordinates.
orbit_coordinates.append((future_sat_lat, future_sat_lon))
return orbit_coordinates


def main():
download_tle_file()
satellite = load_satellite_data()
driver = webdriver.Firefox()
driver.get(f"file:///{os.path.abspath(MAP_FILENAME)}")
while True:
current_time = datetime.utcnow()
t = load.timescale().utc(
current_time.year,
current_time.month,
current_time.day,
current_time.hour,
current_time.minute,
current_time.second
)
geocentric_pos = satellite.at(t)
sub_pos = geocentric_pos.subpoint()
sat_lat = sub_pos.latitude.degrees
sat_lon = sub_pos.longitude.degrees
iss_map = create_map(sat_lat, sat_lon)
orbit_coordinates = predict_orbit(satellite, t)
folium.PolyLine(
locations=orbit_coordinates,
color="black",
weight=1,
dash_array="5"
).add_to(iss_map)
iss_map.save(MAP_FILENAME)
driver.refresh()
time.sleep(UPDATE_INTERVAL_SECONDS)


# Ensure the "main" function is only executed when the script is run directly.
if __name__ == "__main__":
main()