1- from numpy import radians , sin , cos , tan , allclose , hypot , degrees , arctan2 , sqrt , pi
1+ from numpy import radians , sin , cos , tan , arctan , hypot , degrees , arctan2 , sqrt , pi
22import numpy as np
3- from copy import deepcopy
43from typing import Tuple
54from datetime import datetime
65
@@ -14,7 +13,7 @@ class Ellipsoid:
1413 https://en.wikibooks.org/wiki/PROJ.4#Spheroid
1514 """
1615
17- def __init__ (self , model : str = 'wgs84' ) -> None :
16+ def __init__ (self , model : str = 'wgs84' ) -> None :
1817 if model == 'wgs84' :
1918 """https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84"""
2019 self .a = 6378137. # semi-major axis [m]
@@ -45,7 +44,7 @@ def __init__(self, model: str='wgs84') -> None:
4544 raise NotImplementedError ('{} model not implemented, let us know and we will add it (or make a pull request)' .format (model ))
4645
4746
48- def get_radius_normal (lat_radians : float , ell : Ellipsoid = None ) -> float :
47+ def get_radius_normal (lat_radians : float , ell : Ellipsoid = None ) -> float :
4948 """ Compute normal radius of planetary body"""
5049 if ell is None :
5150 ell = Ellipsoid ()
@@ -57,7 +56,7 @@ def get_radius_normal(lat_radians: float, ell: Ellipsoid=None) -> float:
5756
5857
5958def geodetic2ecef (lat : float , lon : float , alt : float ,
60- ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
59+ ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
6160 """
6261 Point
6362
@@ -78,14 +77,15 @@ def geodetic2ecef(lat: float, lon: float, alt: float,
7877 lon = radians (lon )
7978
8079 with np .errstate (invalid = 'ignore' ):
81- if ((lat < - pi / 2 ) | (lat > pi / 2 )).any ():
80+ # need np.any() to handle scalar and array cases
81+ if np .any ((lat < - pi / 2 ) | (lat > pi / 2 )):
8282 raise ValueError ('-90 <= lat <= 90' )
8383
84- if ((lon < - pi ) | (lon > 2 * pi )). any ( ):
84+ if np . any ((lon < - pi ) | (lon > 2 * pi )):
8585 raise ValueError ('-180 <= lat <= 360' )
8686
87- if (np .asarray (alt ) < 0 ). any ( ):
88- raise ValueError ('altitude \in [0, Infinity)' )
87+ if np . any (np .asarray (alt ) < 0 ):
88+ raise ValueError ('altitude [0, Infinity)' )
8989 # radius of curvature of the prime vertical section
9090 N = get_radius_normal (lat , ell )
9191 # Compute cartesian (geocentric) coordinates given (curvilinear) geodetic
@@ -98,7 +98,7 @@ def geodetic2ecef(lat: float, lon: float, alt: float,
9898
9999
100100def ecef2geodetic (x : float , y : float , z : float ,
101- ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
101+ ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
102102 """
103103 convert ECEF (meters) to geodetic coordinates
104104
@@ -113,49 +113,38 @@ def ecef2geodetic(x: float, y: float, z: float,
113113 lat,lon (degrees/radians)
114114 alt (meters)
115115
116- Algorithm is based on
117- http://www.astro.uni.torun.pl/~kb/Papers/geod/Geod-BG.htm
118- This algorithm provides a converging solution to the latitude equation
119- in terms of the parametric or reduced latitude form (v)
120- This algorithm provides a uniform solution over all latitudes as it does
121- not involve division by cos(phi) or sin(phi)
116+ based on:
117+ You, Rey-Jer. (2000). Transformation of Cartesian to Geodetic Coordinates without Iterations.
118+ Journal of Surveying Engineering. doi: 10.1061/(ASCE)0733-9453
122119 """
123120 if ell is None :
124121 ell = Ellipsoid ()
125122
126- ea = ell .a
127- eb = ell .b
128- rad = hypot (x , y )
129- # Constant required for Latitude equation
130- rho = arctan2 (eb * z , ea * rad )
131- # Constant required for latitude equation
132- c = (ea ** 2 - eb ** 2 ) / hypot (ea * rad , eb * z )
133- # Starter for the Newtons Iteration Method
134- vnew = arctan2 (ea * z , eb * rad )
135- # Initializing the parametric latitude
136- v = 0
137- for _ in range (5 ):
138- v = deepcopy (vnew )
139- # %% Newtons Method for computing iterations
140- vnew = v - ((2 * sin (v - rho ) - c * sin (2 * v )) /
141- (2 * (cos (v - rho ) - c * cos (2 * v ))))
142-
143- if allclose (v , vnew ):
144- break
145- # %% Computing latitude from the root of the latitude equation
146- lat = arctan2 (ea * tan (vnew ), eb )
147- # by inspection
148- lon = arctan2 (y , x )
123+ r = sqrt (x ** 2 + y ** 2 + z ** 2 )
149124
150- alt = (((rad - ea * cos (vnew )) * cos (lat )) +
151- ((z - eb * sin (vnew )) * sin (lat )))
125+ E = sqrt (ell .a ** 2 - ell .b ** 2 )
152126
153- with np .errstate (invalid = 'ignore' ):
154- if ((lat < - pi / 2 ) | (lat > pi / 2 )).any ():
155- raise ValueError ('-90 <= lat <= 90' )
127+ # eqn. 4a
128+ u = sqrt (0.5 * (r ** 2 - E ** 2 ) + 0.5 * sqrt ((r ** 2 - E ** 2 )** 2 + 4 * E ** 2 * z ** 2 ))
156129
157- if ((lon < - pi ) | (lon > 2 * pi )).any ():
158- raise ValueError ('-180 <= lat <= 360' )
130+ Q = hypot (x , y )
131+
132+ huE = hypot (u , E )
133+
134+ # eqn. 4b
135+ Beta = arctan (huE / u * z / hypot (x , y ))
136+
137+ # eqn. 13
138+ eps = ((ell .b * u - ell .a * huE + E ** 2 ) * sin (Beta )) / (ell .a * huE * 1 / cos (Beta ) - E ** 2 * cos (Beta ))
139+
140+ Beta += eps
141+ # %% final output
142+ lat = arctan (ell .a / ell .b * tan (Beta ))
143+
144+ lon = arctan2 (y , x )
145+
146+ # eqn. 7
147+ alt = sqrt ((z - ell .b * sin (Beta ))** 2 + (Q - ell .a * cos (Beta ))** 2 )
159148
160149 if deg :
161150 return degrees (lat ), degrees (lon ), alt
@@ -164,7 +153,7 @@ def ecef2geodetic(x: float, y: float, z: float,
164153
165154
166155def ecef2enuv (u : float , v : float , w : float ,
167- lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
156+ lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
168157 """
169158 for VECTOR i.e. between two points
170159
@@ -187,7 +176,7 @@ def ecef2enuv(u: float, v: float, w: float,
187176
188177def ecef2enu (x : float , y : float , z : float ,
189178 lat0 : float , lon0 : float , h0 : float ,
190- ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
179+ ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
191180 """
192181
193182 input
@@ -208,7 +197,7 @@ def ecef2enu(x: float, y: float, z: float,
208197
209198
210199def enu2uvw (east : float , north : float , up : float ,
211- lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
200+ lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
212201 if deg :
213202 lat0 = radians (lat0 )
214203 lon0 = radians (lon0 )
@@ -223,7 +212,7 @@ def enu2uvw(east: float, north: float, up: float,
223212
224213
225214def uvw2enu (u : float , v : float , w : float ,
226- lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
215+ lat0 : float , lon0 : float , deg : bool = True ) -> Tuple [float , float , float ]:
227216 if deg :
228217 lat0 = radians (lat0 )
229218 lon0 = radians (lon0 )
@@ -237,7 +226,7 @@ def uvw2enu(u: float, v: float, w: float,
237226
238227
239228def eci2geodetic (eci : np .ndarray , t : datetime ,
240- useastropy : bool = True ) -> Tuple [float , float , float ]:
229+ useastropy : bool = True ) -> Tuple [float , float , float ]:
241230 """
242231 convert ECI to geodetic coordinates
243232
@@ -265,7 +254,7 @@ def eci2geodetic(eci: np.ndarray, t: datetime,
265254
266255def enu2ecef (e1 : float , n1 : float , u1 : float ,
267256 lat0 : float , lon0 : float , h0 : float ,
268- ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
257+ ell : Ellipsoid = None , deg : bool = True ) -> Tuple [float , float , float ]:
269258 """
270259 ENU to ECEF
271260
0 commit comments