diff --git a/iss_tracker/README.md b/iss_tracker/README.md new file mode 100644 index 00000000..de49ffc1 --- /dev/null +++ b/iss_tracker/README.md @@ -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)) + diff --git a/iss_tracker/data_files/iss_zarya_tle.tle b/iss_tracker/data_files/iss_zarya_tle.tle new file mode 100644 index 00000000..f62f1bb4 --- /dev/null +++ b/iss_tracker/data_files/iss_zarya_tle.tle @@ -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 diff --git a/iss_tracker/map/tracker_map.html b/iss_tracker/map/tracker_map.html new file mode 100644 index 00000000..d7938d65 --- /dev/null +++ b/iss_tracker/map/tracker_map.html @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/iss_tracker/requirements.txt b/iss_tracker/requirements.txt new file mode 100644 index 00000000..7462737c --- /dev/null +++ b/iss_tracker/requirements.txt @@ -0,0 +1,4 @@ +folium==0.17.0 +selenium==4.25.0 +skyfield==1.49 + diff --git a/iss_tracker/tracker.py b/iss_tracker/tracker.py new file mode 100644 index 00000000..34329d1e --- /dev/null +++ b/iss_tracker/tracker.py @@ -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()