An implementation question about altaz
See original GitHub issueI was testing out the altaz
functionality and have some trouble with the implementation. I hope you can clarify some of my confusion.
As a test scenario let us consider the following:
import numpy as np
from skyfield.api import EarthSatellite, load, wgs84
from datetime import datetime, timedelta
tle = """\
1 39444U 13066AE 22267.86615744 .00003125 00000+0 37269-3 0 9990
2 39444 97.6351 237.3665 0057839 66.8984 293.8312 14.83771785476266"""
sat = EarthSatellite(*tle.splitlines())
t0 = sat.epoch + timedelta(minutes=15)
print(wgs84.subpoint_of(sat.at(t0)))
# prints WGS84 latitude +55.5663 N longitude -93.0798 E elevation 0.0 m
loc = wgs84.latlon(55, -90)
alt, az, dist = (sat - loc).at(t0).altaz()
print(f'elevation: {alt.degrees} , azimuth: {az.degrees}, slant range: {dist.km}')
# prints elevation: 68.95630788820152 , azimuth: 289.1205055413545, slant range: 624.9926563478068
I then tried reproducing the same numbers using basic vector geometry. The idea is we have two vectors: Geocentric vector for the Earth station loc
and another Geocentric vector for sat
. Taking the difference, then the norm should ideally give us the slant range:
v = loc.at(t0).position.km
w = (sat - loc).at(t0).position.km
print(f'slant range: {np.linalg.norm(w)}')
# slant range: 624.9926563478072
So altaz and vector geometry calculations are basically equal up to machine precision, so far so good. To calculate the elevation angle we can calculate the dot product between w
(difference vector) and v
(location vector).
el = np.rad2deg (np.arccos ( np.dot(w,v) / np.linalg.norm(w) / np.linalg.norm(v) ) )
el = 90 - el # elevation angle is defined from the horizon
print(f'elevation angle: {el}')
# prints elevation angle: 68.89634982362499
So we have ~0.06 degree discrepancy in elevation angle. To compute the azimuth angle I follow the discussion here.
d = v / np.linalg.norm(v)
z = np.array([0,0,1])
e = np.cross(d,z)
e = e / np.linalg.norm(e) # normalize east vector
n = np.cross(d,e) # north pointing vector
p = np.cross(w,d) # this vector will be at 90 degrees offset to the projection vector to the tangential plane
p = p / np.linalg.norm(p)
minor = np.linalg.det( np.stack( (n[-2:], p[-2:]) ) ) # for -pi to pi coverage
sign = 1 if minor == 0 else np.sign(minor)
dot_p = np.clip(np.dot(n, p), -1.0, 1.0)
az = np.rad2deg(sign * np.arccos (dot_p ))
print(f'azimuth: {(az + 90) %360}')
# azimuth: 289.7260847243732
So in azimuth calculation we have ~0.6 degrees discrepancy. As a sanity check I also used pyproj
to get the geodetic azimuth angle.
from pyproj import Geod
g = Geod(ellps='WGS84')
s = wgs84.subpoint_of(sat.at(t0))
az, _, _ = g.inv(loc.longitude.degrees, loc.latitude.degrees, s.longitude.degrees, s.latitude.degrees)
print(f'pyproj azimuth: {az % 360}')
# prints pyproj azimuth: 289.1174382871312
This value is within 0.003 degrees of SkyField azimuth value so much better than my computation.
Digging through the code, I see that altaz is computed via here and here. Basically after applying a rotation, the spherical coordinates are reported as alt, az, dist
values.
Can you please explain what is incorrect with my approach? Am I missing something?
Issue Analytics
- State:
- Created a year ago
- Comments:8 (4 by maintainers)
Top GitHub Comments
Maybe—but for other reference frames the name would need to be different, like
.north_pole
or.ecliptic_pole
. Maybe instead the documentation could show examples of unpacking the rotation matrix into its three vectors? Just making things up, not yet checking the order they would really arrive in, it might look like:And so forth.
Good, I’m glad you were able to produce them! Have you checked out the rotation matrix itself, by the way? My intuition is that the three unit vectors are there inside the rotation matrix. Try either:
—or else:
—and then examine the vectors:
Do any of those vectors look like the unit vectors you’re looking for?