44import logging
55import warnings
66from functools import partial
7- from typing import Optional
87
98import numpy as np
109from scipy import optimize
@@ -95,6 +94,16 @@ def compute_azimuth_elevation(top_s, top_e, top_z, rg_):
9594
9695 return np .rad2deg (az_ ), np .rad2deg (el_ )
9796
97+ def _look_from_ecef (pos , opos , lon_rad , lat_rad , theta ):
98+ """Compute azimuth/elevation from two ECEF positions."""
99+ rx = pos [0 ] - opos [0 ]
100+ ry = pos [1 ] - opos [1 ]
101+ rz = pos [2 ] - opos [2 ]
102+ rg_ = np .sqrt (rx * rx + ry * ry + rz * rz )
103+
104+ top_s , top_e , top_z = ecef_to_topocentric (rx , ry , rz , lat_rad , lon_rad , theta )
105+ return compute_azimuth_elevation (top_s , top_e , top_z , rg_ )
106+
98107def get_observer_look (sat_lon , sat_lat , sat_alt , utc_time , lon , lat , alt ):
99108 """Calculate observer's look angle to a satellite.
100109
@@ -109,27 +118,24 @@ def get_observer_look(sat_lon, sat_lat, sat_alt, utc_time, lon, lat, alt):
109118 :param alt: Observer altitude in km
110119 :return: (Azimuth, Elevation) in degrees
111120 """
112- # Get satellite and observer ECEF positions
113- (pos_x , pos_y , pos_z ), _ = astronomy .observer_position (utc_time , sat_lon , sat_lat , sat_alt )
114- (opos_x , opos_y , opos_z ), _ = astronomy .observer_position (utc_time , lon , lat , alt )
115-
116- lon = np .deg2rad (lon )
117- lat = np .deg2rad (lat )
118-
119- # Compute local sidereal time
120- theta = (astronomy .gmst (utc_time ) + lon ) % (2 * np .pi )
121-
122- # Vector from observer to satellite
123- rx = pos_x - opos_x
124- ry = pos_y - opos_y
125- rz = pos_z - opos_z
126- rg_ = np .sqrt (rx * rx + ry * ry + rz * rz )
127-
128- # Convert to topocentric coordinates
129- top_s , top_e , top_z = ecef_to_topocentric (rx , ry , rz , lat , lon , theta )
130-
131- # Compute azimuth and elevation
132- return compute_azimuth_elevation (top_s , top_e , top_z , rg_ )
121+ (pos_x , pos_y , pos_z ), _ = astronomy .observer_position (
122+ utc_time , sat_lon , sat_lat , sat_alt
123+ )
124+ (opos_x , opos_y , opos_z ), _ = astronomy .observer_position (
125+ utc_time , lon , lat , alt
126+ )
127+
128+ lon_rad = np .deg2rad (lon )
129+ lat_rad = np .deg2rad (lat )
130+ theta = (astronomy .gmst (utc_time ) + lon_rad ) % (2 * np .pi )
131+
132+ return _look_from_ecef (
133+ (pos_x , pos_y , pos_z ),
134+ (opos_x , opos_y , opos_z ),
135+ lon_rad ,
136+ lat_rad ,
137+ theta
138+ )
133139
134140
135141class Orbital :
@@ -257,7 +263,7 @@ def _find_single_crossing(
257263 horizon : float ,
258264 is_aos : bool ,
259265 max_search_min : float = 1440.0
260- ) -> Optional [ dt .datetime ] :
266+ ) -> dt .datetime | None :
261267 """Internal helper to find the single next horizon crossing time (AOS or AOL)."""
262268 elev_func = partial (self ._elevation , utc_time , lon , lat , alt , horizon )
263269
@@ -298,7 +304,7 @@ def find_aos(
298304 lat : float ,
299305 alt : float = 0 ,
300306 horizon : float = 0
301- ) -> Optional [ dt .datetime ] :
307+ ) -> dt .datetime | None :
302308 """Find the time of the next Acquisition of Signal (AOS) after utc_time (while rising)."""
303309 return self ._find_single_crossing (utc_time , lon , lat , alt , horizon , is_aos = True )
304310
@@ -309,7 +315,7 @@ def find_aol(
309315 lat : float ,
310316 alt : float = 0 ,
311317 horizon : float = 0
312- ) -> Optional [ dt .datetime ] :
318+ ) -> dt .datetime | None :
313319 """Find the time of the next Acquisition of Signal (AOL) after utc_time (while setting)."""
314320 return self ._find_single_crossing (utc_time , lon , lat , alt , horizon , is_aos = False )
315321
@@ -331,19 +337,17 @@ def get_observer_look(self, utc_time, lon, lat, alt):
331337 (pos_x , pos_y , pos_z ), _ = self .get_position (time_ref , normalize = False )
332338 (opos_x , opos_y , opos_z ), _ = astronomy .observer_position (time_ref , lon , lat , alt )
333339
334- rx = pos_x - opos_x
335- ry = pos_y - opos_y
336- rz = pos_z - opos_z
337- rg_ = np .sqrt (rx * rx + ry * ry + rz * rz )
338-
339340 lon_rad = np .deg2rad (lon )
340341 lat_rad = np .deg2rad (lat )
341-
342342 theta = (astronomy .gmst (time_ref ) + lon_rad ) % (2 * np .pi )
343343
344- top_s , top_e , top_z = ecef_to_topocentric (rx , ry , rz , lat_rad , lon_rad , theta )
345-
346- return compute_azimuth_elevation (top_s , top_e , top_z , rg_ )
344+ return _look_from_ecef (
345+ (pos_x , pos_y , pos_z ),
346+ (opos_x , opos_y , opos_z ),
347+ lon_rad ,
348+ lat_rad ,
349+ theta
350+ )
347351
348352 def get_orbit_number (self , utc_time , tbus_style = False , as_float = False ):
349353 """Calculate orbit number at specified time.
@@ -420,8 +424,8 @@ def get_next_passes(
420424 zcs = np .where (np .diff (np .sign (elev )))[0 ]
421425
422426 res : list [tuple [dt .datetime , dt .datetime , dt .datetime ]] = []
423- risetime : Optional [ dt .datetime ] = None
424- risemins : Optional [ float ] = None
427+ risetime : dt .datetime | None = None
428+ risemins : float | None = None
425429
426430 elev_func = partial (self ._elevation , utc_time , lon , lat , alt , horizon )
427431 elev_inv_func = partial (self ._elevation_inv , utc_time , lon , lat , alt , horizon )
@@ -435,7 +439,7 @@ def get_next_passes(
435439 risemins = horizon_mins
436440 else :
437441 falltime = horizon_time
438- fallmins : Optional [ float ] = horizon_mins
442+ fallmins : float | None = horizon_mins
439443
440444 if risetime is None or risemins is None or fallmins is None :
441445 continue
0 commit comments