From fba5f358097309ff168745e87bfff47d7c6c879a Mon Sep 17 00:00:00 2001 From: tsutterley Date: Fri, 15 Nov 2024 17:47:30 -0800 Subject: [PATCH] feat: add calculation of zenith angle test: add zenith angle test --- doc/source/api_reference/spatial.rst | 2 ++ pyTMD/math.py | 27 ++++++++++----- pyTMD/spatial.py | 49 ++++++++++++++++++++++++++++ test/test_spatial.py | 11 +++++++ 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/doc/source/api_reference/spatial.rst b/doc/source/api_reference/spatial.rst index b11c178b..8dc8bd56 100644 --- a/doc/source/api_reference/spatial.rst +++ b/doc/source/api_reference/spatial.rst @@ -99,4 +99,6 @@ General Methods .. autofunction:: pyTMD.spatial.to_horizontal +.. autofunction:: pyTMD.spatial.to_zenith + .. autofunction:: pyTMD.spatial.scale_factors diff --git a/pyTMD/math.py b/pyTMD/math.py index 24cee294..11a87556 100644 --- a/pyTMD/math.py +++ b/pyTMD/math.py @@ -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]_ @@ -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 @@ -61,7 +67,10 @@ 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 @@ -69,7 +78,7 @@ def rotate(theta: float | np.ndarray, axis: str = 'x'): ---------- 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 @@ -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 ------- @@ -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 ------- diff --git a/pyTMD/spatial.py b/pyTMD/spatial.py index 56499deb..e0076ea1 100644 --- a/pyTMD/spatial.py +++ b/pyTMD/spatial.py @@ -157,6 +157,7 @@ "to_ENU", "from_ENU", "to_horizontal", + "to_zenith", "scale_areas", "scale_factors", ] @@ -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) diff --git a/test/test_spatial.py b/test/test_spatial.py index 40f36f16..093a6488 100644 --- a/test/test_spatial.py +++ b/test/test_spatial.py @@ -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)