Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add calculation of zenith angle #363

Merged
merged 1 commit into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/api_reference/spatial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,6 @@ General Methods

.. autofunction:: pyTMD.spatial.to_horizontal

.. autofunction:: pyTMD.spatial.to_zenith

.. autofunction:: pyTMD.spatial.scale_factors
27 changes: 18 additions & 9 deletions pyTMD/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
]

# PURPOSE: calculate the sum of a polynomial function of time
def polynomial_sum(coefficients: list | np.ndarray, t: np.ndarray):
def polynomial_sum(
coefficients: list | np.ndarray,
t: np.ndarray
):
"""
Calculates the sum of a polynomial function using Horner's method [1]_

Expand All @@ -48,7 +51,10 @@ def polynomial_sum(coefficients: list | np.ndarray, t: np.ndarray):
t = np.atleast_1d(t)
return np.sum([c * (t ** i) for i, c in enumerate(coefficients)], axis=0)

def normalize_angle(theta: float | np.ndarray, circle: float = 360.0):
def normalize_angle(
theta: float | np.ndarray,
circle: float = 360.0
):
"""
Normalize an angle to a single rotation

Expand All @@ -61,15 +67,18 @@ def normalize_angle(theta: float | np.ndarray, circle: float = 360.0):
"""
return np.mod(theta, circle)

def rotate(theta: float | np.ndarray, axis: str = 'x'):
def rotate(
theta: float | np.ndarray,
axis: str = 'x'
):
"""
Rotate a 3-dimensional matrix about a given axis

Parameters
----------
theta: float or np.ndarray
Angle of rotation in radians
axis: str
axis: str, default 'x'
Axis of rotation (``'x'``, ``'y'``, or ``'z'``)
"""
# allocate for output rotation matrix
Expand Down Expand Up @@ -112,13 +121,13 @@ def legendre(
Parameters
----------
l: int
degree of Legrendre polynomials (0 to 3)
degree of the Legrendre polynomials (0 to 3)
x: np.ndarray
elements ranging from -1 to 1

Typically ``cos(theta)``, where ``theta`` is the colatitude in radians
m: int, default = 0
order of the Legendre polynomial
m: int, default 0
order of the Legendre polynomials (0 to ``l``)

Returns
-------
Expand Down Expand Up @@ -183,13 +192,13 @@ def sph_harm(
Parameters
----------
l: int
degree of spherical harmonics (0 to 3)
degree of the spherical harmonics (0 to 3)
theta: np.ndarray
colatitude in radians
phi: np.ndarray
longitude in radians
m: int, default 0
order of the spherical harmonics (0 to l)
order of the spherical harmonics (0 to ``l``)

Returns
-------
Expand Down
49 changes: 49 additions & 0 deletions pyTMD/spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"to_ENU",
"from_ENU",
"to_horizontal",
"to_zenith",
"scale_areas",
"scale_factors",
]
Expand Down Expand Up @@ -2193,6 +2194,54 @@ def to_horizontal(
phi = np.mod(np.arctan2(E/D, N/D)*180.0/np.pi, 360.0)
return (alpha, phi, D)

def to_zenith(
x: np.ndarray,
y: np.ndarray,
z: np.ndarray,
lon0: float | np.ndarray = 0.0,
lat0: float | np.ndarray = 0.0,
h0: float | np.ndarray = 0.0,
a_axis: float = _wgs84.a_axis,
flat: float = _wgs84.flat,
):
"""
Calculate zenith angle of an object from Earth-Centered
Earth-Fixed (ECEF) cartesian coordinates

Parameters
----------
x, np.ndarray
cartesian x-coordinates
y, np.ndarray
cartesian y-coordinates
z, np.ndarray
cartesian z-coordinates
lon0: float or np.ndarray, default 0.0
reference longitude (degrees east)
lat0: float or np.ndarray, default 0.0
reference latitude (degrees north)
h0: float or np.ndarray, default 0.0
reference height (meters)
a_axis: float, default 6378137.0
semimajor axis of the ellipsoid
flat: float, default 1.0/298.257223563
ellipsoidal flattening

Returns
-------
zenith: np.ndarray
zenith angle of object in degrees
"""
# convert from ECEF to ENU
E, N, U = to_ENU(x, y, z, lon0=lon0, lat0=lat0, h0=h0,
a_axis=a_axis, flat=flat)
# convert from ENU to horizontal coordinates
alpha, phi, D = to_horizontal(E, N, U)
# calculate zenith angle in degrees
zenith = 90.0 - alpha
# return zenith angle
return zenith

def scale_areas(*args, **kwargs):
warnings.warn("Deprecated. Please use pyTMD.spatial.scale_factors instead",
DeprecationWarning)
Expand Down
11 changes: 11 additions & 0 deletions test/test_spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,20 @@ def test_ECEF_to_horizontal():
# convert from ENU to horizontal coordinates
salt, saz, sdist = pyTMD.spatial.to_horizontal(SE, SN, SU)
lalt, laz, ldist = pyTMD.spatial.to_horizontal(LE, LN, LU)
# calculate zenith angle from ECEF coordinates
solar_zenith = pyTMD.spatial.to_zenith(SX, SY, SZ,
lon0=lon0, lat0=lat0, h0=h0)
lunar_zenith = pyTMD.spatial.to_zenith(LX, LY, LZ,
lon0=lon0, lat0=lat0, h0=h0)
# check solar azimuth and elevation
assert np.isclose(salt, -5.486, atol=0.001)
assert np.isclose(saz, 115.320, atol=0.001)
# check lunar azimuth and elevation
assert np.isclose(lalt, 36.381, atol=0.001)
assert np.isclose(laz, 156.297, atol=0.001)
# check solar and lunar zenith angles
assert np.isclose(solar_zenith, 95.486, atol=0.001)
assert np.isclose(lunar_zenith, 53.619, atol=0.001)
# verify relation between altitudes and zenith angles
assert solar_zenith == (90.0 - salt)
assert lunar_zenith == (90.0 - lalt)
Loading