@@ -105,6 +105,82 @@ def recast_lon180(lon: np.ndarray) -> np.ndarray:
105105 return recast_lon (lon , - 180 )
106106
107107
108+ def plane_to_sphere (
109+ x : np .ndarray , y : np .ndarray , lon_origin : float = 0 , lat_origin : float = 0
110+ ) -> Tuple [np .ndarray , np .ndarray ]:
111+ """Convert Cartesian coordinates on a plane to spherical coordinates.
112+
113+ The arrays of input zonal and meridional displacements ``x`` and ``y`` are
114+ assumed to follow a contiguous trajectory. The spherical coordinate of each
115+ successive point is determined by following a great circle path from the
116+ previous point. The spherical coordinate of the first point is determined by
117+ following a great circle path from the origin, by default (0, 0).
118+
119+ The output arrays have the same floating-point output type as the input.
120+
121+ If projecting multiple trajectories onto the same plane, use
122+ :func:`apply_ragged` for highest accuracy.
123+
124+ Parameters
125+ ----------
126+ x : np.ndarray
127+ An N-d array of zonal displacements in meters
128+ y : np.ndarray
129+ An N-d array of meridional displacements in meters
130+ lon_origin : float, optional
131+ Origin longitude of the tangent plane in degrees, default 0
132+ lat_origin : float, optional
133+ Origin latitude of the tangent plane in degrees, default 0
134+
135+ Returns
136+ -------
137+ lon : np.ndarray
138+ Longitude in degrees
139+ lat : np.ndarray
140+ Latitude in degrees
141+
142+ Examples
143+ --------
144+ >>> plane_to_sphere(np.array([0., 0.]), np.array([0., 1000.]))
145+ (array([0.00000000e+00, 5.50062664e-19]), array([0. , 0.0089832]))
146+
147+ You can also specify an origin longitude and latitude:
148+
149+ >>> plane_to_sphere(np.array([0., 0.]), np.array([0., 1000.]), lon_origin=1, lat_origin=0)
150+ (array([1., 1.]), array([0. , 0.0089832]))
151+
152+ Raises
153+ ------
154+ AttributeError
155+ If ``x`` and ``y`` are not NumPy arrays
156+
157+ See Also
158+ --------
159+ :func:`sphere_to_plane`
160+ """
161+ lon = np .empty_like (x )
162+ lat = np .empty_like (y )
163+
164+ # Cartesian distances between each point
165+ dx = np .diff (x , prepend = 0 )
166+ dy = np .diff (y , prepend = 0 )
167+
168+ distances = np .sqrt (dx ** 2 + dy ** 2 )
169+ bearings = np .arctan2 (dy , dx )
170+
171+ # Compute spherical coordinates following great circles between each
172+ # successive point.
173+ lat [..., 0 ], lon [..., 0 ] = haversine .position_from_distance_and_bearing (
174+ lat_origin , lon_origin , distances [..., 0 ], bearings [..., 0 ]
175+ )
176+ for n in range (1 , lon .shape [- 1 ]):
177+ lat [..., n ], lon [..., n ] = haversine .position_from_distance_and_bearing (
178+ lat [..., n - 1 ], lon [..., n - 1 ], distances [..., n ], bearings [..., n ]
179+ )
180+
181+ return lon , lat
182+
183+
108184def sphere_to_plane (
109185 lon : np .ndarray , lat : np .ndarray , lon_origin : float = 0 , lat_origin : float = 0
110186) -> Tuple [np .ndarray , np .ndarray ]:
@@ -116,8 +192,7 @@ def sphere_to_plane(
116192 The Cartesian coordinate of the first point is determined by following a
117193 great circle path from the origin, by default (0, 0).
118194
119- This function uses 64-bit floats for all intermediate calculations,
120- regardless of the type of input arrays, to avoid loss of precision.
195+ The output arrays have the same floating-point output type as the input.
121196
122197 If projecting multiple trajectories onto the same plane, use
123198 :func:`apply_ragged` for highest accuracy.
@@ -135,29 +210,36 @@ def sphere_to_plane(
135210
136211 Returns
137212 -------
138- Tuple[np.ndarray, np.ndarray]
139- x- and y-coordinates of the tangent plane
213+ x : np.ndarray
214+ x-coordinates on the tangent plane
215+ y : np.ndarray
216+ y-coordinates on the tangent plane
140217
141218 Examples
142219 --------
143220 >>> sphere_to_plane(np.array([0., 1.]), np.array([0., 0.]))
144221 (array([ 0. , 111318.84502145]), array([0., 0.]))
145222
146- You can also specify an x and y origin :
223+ You can also specify an origin longitude and latitude :
147224
148225 >>> sphere_to_plane(np.array([0., 1.]), np.array([0., 0.]), lon_origin=1, lat_origin=0)
149226 (array([-111318.84502145, 0. ]),
150227 array([1.36326267e-11, 1.36326267e-11]))
151228
152229 Raises
153230 ------
154- TypeError
231+ AttributeError
155232 If ``lon`` and ``lat`` are not NumPy arrays
233+
234+ See Also
235+ --------
236+ :func:`plane_to_sphere`
156237 """
157- x = np .empty (lon .shape , dtype = np .float64 )
158- y = np .empty (lat .shape , dtype = np .float64 )
159- distances = np .empty (lon .shape , dtype = np .float64 )
160- bearings = np .empty (lon .shape , dtype = np .float64 )
238+ x = np .empty_like (lon )
239+ y = np .empty_like (lat )
240+
241+ distances = np .empty_like (x )
242+ bearings = np .empty_like (x )
161243
162244 # Distance and bearing of the starting point relative to the origin
163245 distances [0 ] = haversine .distance (lat_origin , lon_origin , lat [..., 0 ], lon [..., 0 ])
0 commit comments