33import tkinter as tk
44from tkinter import Canvas
55import requests
6- from sgp4 .api import Satrec , WGS72 , jday
6+ from sgp4 .api import Satrec , jday
77from datetime import datetime , timedelta
88import math
99import cartopy .crs as ccrs
@@ -23,14 +23,6 @@ def generate_map():
2323 plt .close (fig )
2424 return Image .open (buf )
2525
26- def get_pi_location ():
27- try :
28- response = requests .get ("http://ip-api.com/json/" )
29- data = response .json ()
30- return data ['lat' ], data ['lon' ]
31- except :
32- return 0 , 0
33-
3426def fetch_iss_tle ():
3527 url = "https://celestrak.org/NORAD/elements/stations.txt"
3628 tle_data = requests .get (url ).text
@@ -40,6 +32,26 @@ def fetch_iss_tle():
4032 return lines [i + 1 ], lines [i + 2 ]
4133 return None , None
4234
35+ def eci_to_latlon (pos , jd ):
36+ # Calculate GMST (Greenwich Mean Sidereal Time)
37+ T = (jd - 2451545.0 ) / 36525.0
38+ GMST = 280.46061837 + 360.98564736629 * (jd - 2451545.0 ) + 0.000387933 * T ** 2 - T ** 3 / 38710000.0
39+ GMST = GMST % 360.0
40+ theta = math .radians (GMST )
41+
42+ # ECI to ECEF rotation
43+ x = pos [0 ]* math .cos (theta ) + pos [1 ]* math .sin (theta )
44+ y = - pos [0 ]* math .sin (theta ) + pos [1 ]* math .cos (theta )
45+ z = pos [2 ]
46+
47+ # Convert to lat/lon
48+ r = math .sqrt (x ** 2 + y ** 2 + z ** 2 )
49+ lat = math .degrees (math .asin (z / r ))
50+ lon = math .degrees (math .atan2 (y , x ))
51+ if lon > 180 :
52+ lon -= 360
53+ return lat , lon
54+
4355def latlon_to_xy (lat , lon , width , height ):
4456 x = (lon + 180 ) * (width / 360 )
4557 y = (90 - lat ) * (height / 180 )
@@ -56,7 +68,6 @@ def __init__(self, root):
5668 self .tk_image = ImageTk .PhotoImage (self .image )
5769 self .canvas .create_image (0 , 0 , anchor = "nw" , image = self .tk_image )
5870
59- self .lat , self .lon = get_pi_location ()
6071 self .load_tle ()
6172
6273 self .time_offset = timedelta (seconds = 0 )
@@ -78,45 +89,45 @@ def update_display(self):
7889 self .canvas .delete ("markers" )
7990 self .canvas .create_image (0 , 0 , anchor = "nw" , image = self .tk_image )
8091
81- x , y = latlon_to_xy ( self . lat , self .lon , 800 , 400 )
82- self . canvas . create_oval ( x - 4 , y - 4 , x + 4 , y + 4 , fill = "red" , tags = "markers" )
83-
84- if self . satellite :
85- now = datetime . utcnow () + self . time_offset
86- jd , fr = jday ( now . year , now . month , now . day , now . hour , now . minute , now . second + now . microsecond / 1e6 )
87- e , pos , vel = self . satellite . sgp4 ( jd , fr )
88- r = math . sqrt ( pos [ 0 ] ** 2 + pos [ 1 ] ** 2 + pos [ 2 ] ** 2 )
89- iss_lat = math . degrees ( math . asin ( pos [ 2 ] / r ))
90- iss_lon = math . degrees ( math . atan2 ( pos [ 1 ], pos [ 0 ]))
91- if iss_lon > 180 :
92- iss_lon -= 360
93-
94- iss_x , iss_y = latlon_to_xy ( iss_lat , iss_lon , 800 , 400 )
95- self . canvas . create_oval ( iss_x - 4 , iss_y - 4 , iss_x + 4 , iss_y + 4 , fill = "purple" , tags = "markers" )
96-
97- for mins in range ( 5 , 90 , 5 ):
98- future_time = now + timedelta ( minutes = mins )
99- jd_fut , fr_fut = jday (
100- future_time . year , future_time . month , future_time . day ,
101- future_time . hour , future_time . minute ,
102- future_time . second + future_time . microsecond / 1e6
103- )
104- e_fut , pos_fut , vel_fut = self . satellite . sgp4 ( jd_fut , fr_fut )
105- r_fut = math . sqrt ( pos_fut [ 0 ] ** 2 + pos_fut [ 1 ] ** 2 + pos_fut [ 2 ] ** 2 )
106- lat_fut = math . degrees ( math . asin ( pos_fut [ 2 ] / r_fut ))
107- lon_fut = math . degrees ( math . atan2 ( pos_fut [ 1 ], pos_fut [ 0 ]))
108- if lon_fut > 180 :
109- lon_fut -= 360
110- path_x , path_y = latlon_to_xy ( lat_fut , lon_fut , 800 , 400 )
111- self .canvas .create_oval ( path_x - 1 , path_y - 1 , path_x + 1 , path_y + 1 , fill = "blue " , tags = "markers" )
92+ now = datetime . utcnow () + self .time_offset
93+ jd , fr = jday ( now . year , now . month , now . day , now . hour , now . minute , now . second + now . microsecond / 1e6 )
94+
95+ # Current ISS position
96+ e , pos , vel = self . satellite . sgp4 ( jd , fr )
97+ lat , lon = eci_to_latlon ( pos , jd )
98+ iss_x , iss_y = latlon_to_xy ( lat , lon , 800 , 400 )
99+ self . canvas . create_oval ( iss_x - 4 , iss_y - 4 , iss_x + 4 , iss_y + 4 , fill = "purple" , tags = "markers" )
100+
101+ # Future path
102+ path_coords = []
103+ for mins in range ( 0 , 90 , 2 ):
104+ future_time = now + timedelta ( minutes = mins )
105+ jd_fut , fr_fut = jday (
106+ future_time . year , future_time . month , future_time . day ,
107+ future_time . hour , future_time . minute ,
108+ future_time . second + future_time . microsecond / 1e6
109+ )
110+ e_fut , pos_fut , vel_fut = self . satellite . sgp4 ( jd_fut , fr_fut )
111+ lat_fut , lon_fut = eci_to_latlon ( pos_fut , jd_fut )
112+ path_x , path_y = latlon_to_xy ( lat_fut , lon_fut , 800 , 400 )
113+ path_coords . append (( path_x , path_y ))
114+
115+ # Draw blue path line
116+ for i in range ( len ( path_coords ) - 1 ):
117+ self . canvas . create_line ( path_coords [ i ][ 0 ], path_coords [ i ][ 1 ],
118+ path_coords [ i + 1 ][ 0 ], path_coords [ i + 1 ][ 1 ],
119+ fill = "blue" , width = 2 , tags = "markers" )
120+
121+ # Draw time label
122+ self .canvas .create_text ( 400 , 20 , text = now . strftime ( "%Y-%m-%d %H:%M:%S UTC" ), font = ( "Arial" , 16 ) , fill = "white " , tags = "markers" )
112123
113124 self .root .after (1000 , self .update_display )
114125
115126 def go_forward (self , event ):
116- self .time_offset += timedelta (minutes = 10 )
127+ self .time_offset += timedelta (minutes = 2 )
117128
118129 def go_back (self , event ):
119- self .time_offset -= timedelta (minutes = 10 )
130+ self .time_offset -= timedelta (minutes = 2 )
120131
121132 def reset_time (self , event ):
122133 self .time_offset = timedelta (seconds = 0 )
0 commit comments