From 59b6e622e6c6f8a0c4fb9c6a3af70abb354d07d3 Mon Sep 17 00:00:00 2001 From: Shane Elipot Date: Sun, 17 Sep 2023 07:55:30 -0700 Subject: [PATCH 01/11] add wavelet module to doc? --- docs/api.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 9fb13dfd..72c2e031 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -83,3 +83,10 @@ Signal .. automodule:: clouddrift.signal :members: :undoc-members: + +Wavelet +--------- + +.. automodule:: clouddrift.wavelet + :members: + :undoc-members: \ No newline at end of file From 4b39d153139a382803f6ceb167217c2d7228c284 Mon Sep 17 00:00:00 2001 From: Philippe Miron Date: Sun, 17 Sep 2023 12:23:02 -0400 Subject: [PATCH 02/11] fix the few warnings after generating the doc --- clouddrift/wavelet.py | 3 ++- docs/api.rst | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/clouddrift/wavelet.py b/clouddrift/wavelet.py index 3cf22c16..7dd08e20 100644 --- a/clouddrift/wavelet.py +++ b/clouddrift/wavelet.py @@ -654,7 +654,7 @@ def morse_logspace_freq( >>> radian_frequency = morse_logspace_freq(3,5,1024,highset=(0.2,np.pi),lowset=(5,0),density=10) - See Also + See Also -------- :func:`morse_wavelet`, `morse_freq`, `morse_properties`. """ @@ -725,6 +725,7 @@ def morse_properties( kurt: np.ndarray or float Normalized fourth moment of the time-domain demodulate, or 'demodulate kurtosis'. + See Also -------- :func:`morse_wavelet`, `morse_freq`, `morse_amplitude`, `morse_logspace_freq`. diff --git a/docs/api.rst b/docs/api.rst index 72c2e031..fede5f04 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -71,21 +71,21 @@ RaggedArray :undoc-members: Sphere ---------- +------ .. automodule:: clouddrift.sphere :members: :undoc-members: Signal ---------- +------ .. automodule:: clouddrift.signal :members: :undoc-members: Wavelet ---------- +------- .. automodule:: clouddrift.wavelet :members: From 29da4ee3cae2f87501ca38b45f47d377090efcff Mon Sep 17 00:00:00 2001 From: Philippe Miron Date: Sun, 17 Sep 2023 12:24:06 -0400 Subject: [PATCH 03/11] lint --- clouddrift/wavelet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clouddrift/wavelet.py b/clouddrift/wavelet.py index 7dd08e20..fd3d999e 100644 --- a/clouddrift/wavelet.py +++ b/clouddrift/wavelet.py @@ -725,7 +725,7 @@ def morse_properties( kurt: np.ndarray or float Normalized fourth moment of the time-domain demodulate, or 'demodulate kurtosis'. - + See Also -------- :func:`morse_wavelet`, `morse_freq`, `morse_amplitude`, `morse_logspace_freq`. From 8ab5bb091ecada65354e1e10c5c57f846cb52fee Mon Sep 17 00:00:00 2001 From: Shane Elipot Date: Mon, 18 Sep 2023 15:58:37 -0700 Subject: [PATCH 04/11] first commit --- clouddrift/sphere.py | 169 +++++++++++++++++++++++++++++++++++++++++- tests/sphere_tests.py | 73 ++++++++++++++++++ 2 files changed, 241 insertions(+), 1 deletion(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 7d95b2ae..53622e25 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -5,10 +5,12 @@ """ import numpy as np -from typing import Optional, Tuple +from typing import Optional, Tuple, Union import xarray as xr +import warnings EARTH_RADIUS_METERS = 6.3781e6 +EARTH_ROTATION_RATE = 7.2921159e-5 def distance( @@ -566,3 +568,168 @@ def cartesian_to_spherical( lat = np.rad2deg(np.arcsin(z)) return lon, lat + + +def cartesian_to_tangentplane( + u: Union[float, np.ndarray], + v: Union[float, np.ndarray], + w: Union[float, np.ndarray], + longitude: Union[float, np.ndarray], + latitude: Union[float, np.ndarray], +) -> Union[Tuple[float], Tuple[np.ndarray]]: + """ + Project a three-dimensional Cartesian vector on a plane tangent to + a spherical Earth. + + The Cartesian coordinate system is a right-handed system whose + origin lies at the center of a sphere. It is oriented with the + Z-axis passing through the north pole at lat = 90, the X-axis passing through + the point lon = 0, lat = 0, and the Y-axis passing through the point lon = 90, + lat = 0. + + Parameters + ---------- + u : float or np.ndarray + First component of Cartesian vector. + v : float or np.ndarray + Second component of Cartesian vector. + w : float or np.ndarray + Third component of Cartesian vector. + longitude : float or np.ndarray + Longitude in degrees of tangent point of plane. + latitude : float or np.ndarray + Latitude in degrees of tangent point of plane. + + Returns + ------- + + up: float or np.ndarray + First component of projected vector on tangent plane (positive eastward). + vp: float or np.ndarray + Second component of projected vector on tangent plane (positive northward). + + Raises + ------ + Warning + Raised if the input latitude is not in the expected range [-90, 90]. + + Examples + -------- + >>> u, v = cartesian_to_tangentplane(1, 1, 1, 45, 90) + + See Also + -------- + :func:`tangentplane_to_cartesian` + """ + if np.any(latitude < -90) or np.any(latitude > 90): + warnings.warn("Input latitude outside of range [-90,90].") + + phi = np.radians(latitude) + theta = np.radians(longitude) + u_projected = v * np.cos(theta) - u * np.sin(theta) + v_projected = ( + w * np.cos(phi) + - u * np.cos(theta) * np.sin(phi) + - v * np.sin(theta) * np.sin(phi) + ) + # JML says vh = w.*cos(phi)-u.*cos(theta).*sin(phi)-v.*sin(theta).*sin(phi) but vh=w./cos(phi) is the same + return u_projected, v_projected + + +def tangentplane_to_cartesian( + up: Union[float, np.ndarray], + vp: Union[float, np.ndarray], + longitude: Union[float, np.ndarray], + latitude: Union[float, np.ndarray], +) -> Union[Tuple[float], Tuple[np.ndarray]]: + """ + Return the three-dimensional Cartesian components of a vector conatined in + a plane tangent to a spherical Earth. + + The Cartesian coordinate system is a right-handed system whose + origin lies at the center of a sphere. It is oriented with the + Z-axis passing through the north pole at lat = 90, the X-axis passing through + the point lon = 0, lat = 0, and the Y-axis passing through the point lon = 90, + lat = 0. + + Parameters + ---------- + up: float or np.ndarray + First component of vector on tangent plane (positive eastward). + vp: float or np.ndarray + Second component of vector on tangent plane (positive northward). + longitude : float or np.ndarray + Longitude in degrees of tangent point of plane. + latitude : float or np.ndarray + Latitude in degrees of tangent point of plane. + + Returns + ------- + u : float or np.ndarray + First component of Cartesian vector. + v : float or np.ndarray + Second component of Cartesian vector. + w : float or np.ndarray + Third component of Cartesian vector. + + Raises + ------ + Warning + Raised if the input latitude is not in the expected range [-90, 90]. + + Examples + -------- + >>> u, v, w = tangentplane_to_cartesian(1, 1, 45, 90) + + Notes + ----- + This function is inverted by `cartesian_to_tangetplane`. + + See Also + -------- + :func:`cartesian_to_tangentplane` + """ + if np.any(latitude < -90) or np.any(latitude > 90): + warnings.warn("Input latitude outside of range [-90,90].") + + phi = np.radians(latitude) + theta = np.radians(longitude) + u = -up * np.sin(theta) - vp * np.sin(phi) * np.cos(theta) + v = up * np.cos(theta) - vp * np.sin(phi) * np.sin(theta) + w = vp * np.cos(phi) + + return u, v, w + + +def coriolis_frequency( + latitude: Union[float, np.ndarray], +) -> Union[float, np.ndarray]: + """ + Return the Coriolis frequency or commonly known `f` parameter in geophysical fluid dynamics. + + Parameters + ---------- + latitude : float or np.ndarray + Latitude in degrees. + + Returns + ------ + f : float or np.ndarray + Signed Coriolis frequency in radian per seconds. + + Raises + ------ + Warning + Raised if the input latitudes are not in the expected range [-90, 90]. + + Examples + -------- + >>> f = coriolis_frequency(np.array([0, 45, 90])) + + """ + f = 2 * EARTH_ROTATION_RATE * np.sin(np.radians(latitude)) + + if np.any(latitude < -90) or np.any(latitude > 90): + warnings.warn("Input latitudes outside of range [-90,90].") + + return f diff --git a/tests/sphere_tests.py b/tests/sphere_tests.py index 5960d0fd..773510a4 100644 --- a/tests/sphere_tests.py +++ b/tests/sphere_tests.py @@ -9,6 +9,9 @@ position_from_distance_and_bearing, spherical_to_cartesian, cartesian_to_spherical, + tangentplane_to_cartesian, + cartesian_to_tangentplane, + coriolis_frequency, EARTH_RADIUS_METERS, ) import unittest @@ -354,3 +357,73 @@ def test_cartesian_to_spherical_invert(self): self.assertTrue(np.allclose(x, x2)) self.assertTrue(np.allclose(y, y2)) self.assertTrue(np.allclose(z, z2)) + + +class cartesian_to_tangentplane_tests(unittest.TestCase): + def test_cartesian_to_tangentplane_values(self): + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 0.0, 0.0) + self.assertTrue(np.allclose((up, vp), (1.0, 1.0))) + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 90.0, 0.0) + self.assertTrue(np.allclose((up, vp), (-1.0, 1.0))) + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 180.0, 0.0) + self.assertTrue(np.allclose((up, vp), (-1.0, 1.0))) + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 270.0, 0.0) + self.assertTrue(np.allclose((up, vp), (1.0, 1.0))) + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 0.0, 90.0) + self.assertTrue(np.allclose((up, vp), (1.0, -1.0))) + up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 0.0, -90.0) + self.assertTrue(np.allclose((up, vp), (1.0, 1.0))) + + def test_cartesian_to_tangentplane_warning(self): + with self.assertWarns(Warning): + cartesian_to_tangentplane(1, 1, 1, 0, 91) + with self.assertWarns(Warning): + cartesian_to_tangentplane(1, 1, 1, 0, -91) + + +class tangentplane_to_cartesian_tests(unittest.TestCase): + def test_tangentplane_to_cartesian_values(self): + uvw = tangentplane_to_cartesian(1, 1, 0, 0) + self.assertTrue(np.allclose(uvw, (0, 1, 1))) + uvw = tangentplane_to_cartesian(1, 1, 90, 0) + self.assertTrue(np.allclose(uvw, (-1, 0, 1))) + uvw = tangentplane_to_cartesian(1, 1, 180, 0) + self.assertTrue(np.allclose(uvw, (0, -1, 1))) + uvw = tangentplane_to_cartesian(1, 1, 270, 0) + self.assertTrue(np.allclose(uvw, (1, 0, 1))) + uvw = tangentplane_to_cartesian(1, 1, 0, 90) + self.assertTrue(np.allclose(uvw, (-1, 1, 0))) + uvw = tangentplane_to_cartesian(1, 1, 0, -90) + self.assertTrue(np.allclose(uvw, (1, 1, 0))) + + def test_tangentplane_to_cartesian_inverse(self): + u, v, w = tangentplane_to_cartesian(1, 1, 45, 45) + self.assertTrue(np.allclose((1, 1), cartesian_to_tangentplane(u, v, w, 45, 45))) + + def test_tangentplane_to_cartesian_warning(self): + with self.assertWarns(Warning): + tangentplane_to_cartesian(1, 1, 0, 91) + with self.assertWarns(Warning): + tangentplane_to_cartesian(1, 1, 0, -91) + + +class coriolis_frequency_tests(unittest.TestCase): + def test_coriolis_frequency_values(self): + f = coriolis_frequency(np.array([-90, -60, -30, 0, 45, 90])) + f_expected = np.array( + [ + -0.000145842318, + -0.000126303152, + -7.2921159e-05, + 0, + 0.000103126092, + 0.000145842318, + ] + ) + self.assertTrue(np.allclose(f, f_expected)) + + def test_coriolis_frequency_warning(self): + with self.assertWarns(Warning): + coriolis_frequency(91) + with self.assertWarns(Warning): + coriolis_frequency(-91) From 10ef39d0b46597e427a71e5086c7f24b9e3fd07c Mon Sep 17 00:00:00 2001 From: Milan Curcic Date: Tue, 19 Sep 2023 10:20:34 -0400 Subject: [PATCH 05/11] Fix indentation --- clouddrift/sphere.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 53622e25..434cf1f1 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -709,13 +709,13 @@ def coriolis_frequency( Parameters ---------- - latitude : float or np.ndarray - Latitude in degrees. + latitude : float or np.ndarray + Latitude in degrees. Returns ------ - f : float or np.ndarray - Signed Coriolis frequency in radian per seconds. + f : float or np.ndarray + Signed Coriolis frequency in radian per seconds. Raises ------ From a32e6cc17b3c7ebd66b8d31480a448d49edffd17 Mon Sep 17 00:00:00 2001 From: Shane Elipot Date: Tue, 19 Sep 2023 07:38:56 -0700 Subject: [PATCH 06/11] remove warnings --- clouddrift/sphere.py | 6 ------ tests/sphere_tests.py | 12 ------------ 2 files changed, 18 deletions(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 53622e25..143be334 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -689,9 +689,6 @@ def tangentplane_to_cartesian( -------- :func:`cartesian_to_tangentplane` """ - if np.any(latitude < -90) or np.any(latitude > 90): - warnings.warn("Input latitude outside of range [-90,90].") - phi = np.radians(latitude) theta = np.radians(longitude) u = -up * np.sin(theta) - vp * np.sin(phi) * np.cos(theta) @@ -729,7 +726,4 @@ def coriolis_frequency( """ f = 2 * EARTH_ROTATION_RATE * np.sin(np.radians(latitude)) - if np.any(latitude < -90) or np.any(latitude > 90): - warnings.warn("Input latitudes outside of range [-90,90].") - return f diff --git a/tests/sphere_tests.py b/tests/sphere_tests.py index 773510a4..2a26b9b7 100644 --- a/tests/sphere_tests.py +++ b/tests/sphere_tests.py @@ -374,12 +374,6 @@ def test_cartesian_to_tangentplane_values(self): up, vp = cartesian_to_tangentplane(1.0, 1.0, 1.0, 0.0, -90.0) self.assertTrue(np.allclose((up, vp), (1.0, 1.0))) - def test_cartesian_to_tangentplane_warning(self): - with self.assertWarns(Warning): - cartesian_to_tangentplane(1, 1, 1, 0, 91) - with self.assertWarns(Warning): - cartesian_to_tangentplane(1, 1, 1, 0, -91) - class tangentplane_to_cartesian_tests(unittest.TestCase): def test_tangentplane_to_cartesian_values(self): @@ -400,12 +394,6 @@ def test_tangentplane_to_cartesian_inverse(self): u, v, w = tangentplane_to_cartesian(1, 1, 45, 45) self.assertTrue(np.allclose((1, 1), cartesian_to_tangentplane(u, v, w, 45, 45))) - def test_tangentplane_to_cartesian_warning(self): - with self.assertWarns(Warning): - tangentplane_to_cartesian(1, 1, 0, 91) - with self.assertWarns(Warning): - tangentplane_to_cartesian(1, 1, 0, -91) - class coriolis_frequency_tests(unittest.TestCase): def test_coriolis_frequency_values(self): From da2a6981f840c575ab213d0de644817427c7822c Mon Sep 17 00:00:00 2001 From: Milan Curcic Date: Thu, 21 Sep 2023 11:28:24 -0400 Subject: [PATCH 07/11] Remove warning from docstring Co-authored-by: Philippe Miron --- clouddrift/sphere.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index ac226519..9c09dc63 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -672,11 +672,6 @@ def tangentplane_to_cartesian( w : float or np.ndarray Third component of Cartesian vector. - Raises - ------ - Warning - Raised if the input latitude is not in the expected range [-90, 90]. - Examples -------- >>> u, v, w = tangentplane_to_cartesian(1, 1, 45, 90) From 777c600b1a6a2cc5baeae17fae6865682b87943d Mon Sep 17 00:00:00 2001 From: Milan Curcic Date: Thu, 21 Sep 2023 11:28:37 -0400 Subject: [PATCH 08/11] Fix typo Co-authored-by: Philippe Miron --- clouddrift/sphere.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 9c09dc63..4ae04217 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -643,7 +643,7 @@ def tangentplane_to_cartesian( latitude: Union[float, np.ndarray], ) -> Union[Tuple[float], Tuple[np.ndarray]]: """ - Return the three-dimensional Cartesian components of a vector conatined in + Return the three-dimensional Cartesian components of a vector contained in a plane tangent to a spherical Earth. The Cartesian coordinate system is a right-handed system whose From 1ddcd17e7caaed2eb1c82dd63129cb951ed53760 Mon Sep 17 00:00:00 2001 From: Milan Curcic Date: Thu, 21 Sep 2023 11:28:48 -0400 Subject: [PATCH 09/11] Fix typo Co-authored-by: Philippe Miron --- clouddrift/sphere.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 4ae04217..f578a938 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -644,7 +644,7 @@ def tangentplane_to_cartesian( ) -> Union[Tuple[float], Tuple[np.ndarray]]: """ Return the three-dimensional Cartesian components of a vector contained in - a plane tangent to a spherical Earth. + a plane tangent to a spherical Earth. The Cartesian coordinate system is a right-handed system whose origin lies at the center of a sphere. It is oriented with the From 63d069553a915cb5386a232325cf649c5b169e2a Mon Sep 17 00:00:00 2001 From: Milan Curcic Date: Thu, 21 Sep 2023 11:28:59 -0400 Subject: [PATCH 10/11] Update clouddrift/sphere.py Co-authored-by: Philippe Miron --- clouddrift/sphere.py | 1 - 1 file changed, 1 deletion(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index f578a938..2796d5a1 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -602,7 +602,6 @@ def cartesian_to_tangentplane( Returns ------- - up: float or np.ndarray First component of projected vector on tangent plane (positive eastward). vp: float or np.ndarray From 28f7a5d965a37018f651ca675d480d38f22aa0b5 Mon Sep 17 00:00:00 2001 From: Philippe Miron Date: Thu, 21 Sep 2023 12:20:59 -0400 Subject: [PATCH 11/11] Update clouddrift/sphere.py earth rotation constant Co-authored-by: Milan Curcic --- clouddrift/sphere.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clouddrift/sphere.py b/clouddrift/sphere.py index 2796d5a1..fd4a9cad 100644 --- a/clouddrift/sphere.py +++ b/clouddrift/sphere.py @@ -10,7 +10,8 @@ import warnings EARTH_RADIUS_METERS = 6.3781e6 -EARTH_ROTATION_RATE = 7.2921159e-5 +EARTH_DAY_SECONDS = 86164.091 +EARTH_ROTATION_RATE = 2 * np.pi / EARTH_DAY_SECONDS def distance(