Skip to content

Commit 8707fae

Browse files
authored
Py Fix 5
1 parent 5b75e4b commit 8707fae

File tree

1 file changed

+80
-70
lines changed

1 file changed

+80
-70
lines changed

usr/local/bin/iss-tracker.py

Lines changed: 80 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,7 @@
1010
import matplotlib.pyplot as plt
1111
from PIL import Image, ImageTk
1212
import io
13-
14-
# Generate static world map background
15-
def generate_map():
16-
fig = plt.figure(figsize=(8, 4))
17-
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
18-
ax.set_global()
19-
ax.stock_img()
20-
ax.coastlines()
21-
buf = io.BytesIO()
22-
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
23-
buf.seek(0)
24-
plt.close(fig)
25-
return Image.open(buf)
26-
27-
# Get fresh ISS TLE
28-
def fetch_iss_tle():
29-
url = "https://celestrak.org/NORAD/elements/stations.txt"
30-
tle_data = requests.get(url).text
31-
lines = tle_data.split('\n')
32-
for i, line in enumerate(lines):
33-
if "ISS (ZARYA)" in line:
34-
return lines[i+1], lines[i+2]
35-
return None, None
36-
37-
# Convert ECI to ECEF (lat/lon)
38-
def eci_to_latlon(pos, jd):
39-
T = (jd - 2451545.0) / 36525.0
40-
GMST = 280.46061837 + 360.98564736629 * (jd - 2451545.0) + 0.000387933 * T**2 - T**3 / 38710000.0
41-
GMST = GMST % 360.0
42-
theta = math.radians(GMST)
43-
44-
x = pos[0]*math.cos(theta) + pos[1]*math.sin(theta)
45-
y = -pos[0]*math.sin(theta) + pos[1]*math.cos(theta)
46-
z = pos[2]
47-
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-
55-
# Convert lat/lon to map x/y
56-
def latlon_to_xy(lat, lon, width, height):
57-
x = (lon + 180) * (width / 360)
58-
y = (90 - lat) * (height / 180)
59-
return x, y
60-
61-
# Handle longitudes wrapping across ±180
62-
def smooth_longitude(prev_lon, curr_lon):
63-
delta = curr_lon - prev_lon
64-
if delta > 180:
65-
curr_lon -= 360
66-
elif delta < -180:
67-
curr_lon += 360
68-
return curr_lon
13+
import threading
6914

7015
class ISSTrackerApp:
7116
def __init__(self, root):
@@ -74,25 +19,60 @@ def __init__(self, root):
7419
self.canvas = Canvas(root, width=800, height=400, bg="black")
7520
self.canvas.pack()
7621

77-
self.image = generate_map().resize((800, 400))
22+
self.image = self.generate_map().resize((800, 400))
7823
self.tk_image = ImageTk.PhotoImage(self.image)
79-
80-
self.load_tle()
24+
self.canvas.create_image(0, 0, anchor="nw", image=self.tk_image)
8125

8226
self.time_offset = timedelta(seconds=0)
27+
self.satellite = None
28+
self.last_tle_update = None
29+
30+
self.load_tle()
31+
self.schedule_tle_refresh()
8332

8433
self.root.bind("<Left>", self.go_back)
8534
self.root.bind("<Right>", self.go_forward)
8635
self.root.bind("<space>", self.reset_time)
8736

8837
self.update_display()
8938

39+
def generate_map(self):
40+
fig = plt.figure(figsize=(8, 4))
41+
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
42+
ax.set_global()
43+
ax.stock_img()
44+
ax.coastlines()
45+
buf = io.BytesIO()
46+
plt.savefig(buf, format='png', dpi=100, bbox_inches='tight')
47+
buf.seek(0)
48+
plt.close(fig)
49+
return Image.open(buf)
50+
9051
def load_tle(self):
91-
tle1, tle2 = fetch_iss_tle()
92-
if tle1 and tle2:
93-
self.satellite = Satrec.twoline2rv(tle1, tle2)
94-
else:
95-
self.satellite = None
52+
try:
53+
url = "https://celestrak.org/NORAD/elements/stations.txt"
54+
tle_data = requests.get(url, timeout=10).text
55+
lines = tle_data.split('\n')
56+
for i, line in enumerate(lines):
57+
if "ISS (ZARYA)" in line:
58+
tle1 = lines[i+1].strip()
59+
tle2 = lines[i+2].strip()
60+
self.satellite = Satrec.twoline2rv(tle1, tle2)
61+
self.last_tle_update = datetime.utcnow()
62+
print("TLE updated:", self.last_tle_update)
63+
return
64+
except Exception as e:
65+
print("Error updating TLE:", e)
66+
67+
def schedule_tle_refresh(self):
68+
# Refresh TLE every 10 minutes
69+
def tle_updater():
70+
while True:
71+
now = datetime.utcnow()
72+
if self.last_tle_update is None or (now - self.last_tle_update).total_seconds() > 600:
73+
self.load_tle()
74+
threading.Event().wait(60)
75+
threading.Thread(target=tle_updater, daemon=True).start()
9676

9777
def update_display(self):
9878
self.canvas.delete("all")
@@ -101,13 +81,13 @@ def update_display(self):
10181
now = datetime.utcnow() + self.time_offset
10282
jd, fr = jday(now.year, now.month, now.day, now.hour, now.minute, now.second + now.microsecond/1e6)
10383
e, pos, vel = self.satellite.sgp4(jd, fr)
104-
lat, lon = eci_to_latlon(pos, jd)
105-
iss_x, iss_y = latlon_to_xy(lat, lon, 800, 400)
84+
lat, lon = self.eci_to_latlon(pos, jd)
85+
iss_x, iss_y = self.latlon_to_xy(lat, lon, 800, 400)
10686

10787
# Draw ISS current position
10888
self.canvas.create_oval(iss_x-4, iss_y-4, iss_x+4, iss_y+4, fill="purple", tags="iss")
10989

110-
# Draw current orbit path (next 90 minutes only)
90+
# Draw current orbit path
11191
path_coords = []
11292
prev_lon = None
11393
for mins in range(-10, 90, 2):
@@ -116,13 +96,13 @@ def update_display(self):
11696
future_time.hour, future_time.minute,
11797
future_time.second + future_time.microsecond/1e6)
11898
e_fut, pos_fut, vel_fut = self.satellite.sgp4(jd_fut, fr_fut)
119-
lat_fut, lon_fut = eci_to_latlon(pos_fut, jd_fut)
99+
lat_fut, lon_fut = self.eci_to_latlon(pos_fut, jd_fut)
120100

121101
if prev_lon is not None:
122-
lon_fut = smooth_longitude(prev_lon, lon_fut)
102+
lon_fut = self.smooth_longitude(prev_lon, lon_fut)
123103
prev_lon = lon_fut
124104

125-
x, y = latlon_to_xy(lat_fut, lon_fut, 800, 400)
105+
x, y = self.latlon_to_xy(lat_fut, lon_fut, 800, 400)
126106
path_coords.append((x, y))
127107

128108
for i in range(len(path_coords)-1):
@@ -136,6 +116,36 @@ def update_display(self):
136116

137117
self.root.after(1000, self.update_display)
138118

119+
def eci_to_latlon(self, pos, jd):
120+
T = (jd - 2451545.0) / 36525.0
121+
GMST = 280.46061837 + 360.98564736629 * (jd - 2451545.0) + 0.000387933 * T**2 - T**3 / 38710000.0
122+
GMST = GMST % 360.0
123+
theta = math.radians(GMST)
124+
125+
x = pos[0]*math.cos(theta) + pos[1]*math.sin(theta)
126+
y = -pos[0]*math.sin(theta) + pos[1]*math.cos(theta)
127+
z = pos[2]
128+
129+
r = math.sqrt(x**2 + y**2 + z**2)
130+
lat = math.degrees(math.asin(z / r))
131+
lon = math.degrees(math.atan2(y, x))
132+
if lon > 180:
133+
lon -= 360
134+
return lat, lon
135+
136+
def latlon_to_xy(self, lat, lon, width, height):
137+
x = (lon + 180) * (width / 360)
138+
y = (90 - lat) * (height / 180)
139+
return x, y
140+
141+
def smooth_longitude(self, prev_lon, curr_lon):
142+
delta = curr_lon - prev_lon
143+
if delta > 180:
144+
curr_lon -= 360
145+
elif delta < -180:
146+
curr_lon += 360
147+
return curr_lon
148+
139149
def go_forward(self, event):
140150
self.time_offset += timedelta(minutes=2)
141151

0 commit comments

Comments
 (0)