Skip to content

Commit eb18637

Browse files
committed
add geodetic2eci
1 parent 1218417 commit eb18637

File tree

6 files changed

+128
-52
lines changed

6 files changed

+128
-52
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ converted to the desired coordinate system:
8888

8989
aer2ecef aer2enu aer2geodetic aer2ned
9090
ecef2aer ecef2enu ecef2enuv ecef2geodetic ecef2ned ecef2nedv
91-
ecef2eci eci2ecef eci2aer aer2eci
91+
ecef2eci eci2ecef eci2aer aer2eci geodetic2eci eci2geodetic
9292
enu2aer enu2ecef enu2geodetic
9393
geodetic2aer geodetic2ecef geodetic2enu geodetic2ned
9494
ned2aer ned2ecef ned2geodetic
@@ -135,7 +135,8 @@ As noted above, use list comprehension if you need vector data without Numpy.
135135

136136
As compared to [PyProj](https://github.com/jswhit/pyproj):
137137

138-
* PyMap3D does not require anything beyond pure Python -- not even Numpy is required except for ECI (let us know if this is an issue).
138+
* PyMap3D does not require anything beyond pure Python for most transforms
139+
* Astronomical conversions are done using (optional) AstroPy for established accuracy
139140
* PyMap3D API is similar to Matlab Mapping Toolbox, while PyProj's interface is quite distinct
140141
* PyMap3D intrinsically handles local coordinate systems such as ENU,
141142
while PyProj ENU requires some [additional effort](https://github.com/jswhit/pyproj/issues/105).

src/pymap3d/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from .ellipsoid import Ellipsoid
3636
from .enu import enu2geodetic, geodetic2enu, aer2enu, enu2aer
3737
from .ned import ned2ecef, ned2geodetic, geodetic2ned, ecef2nedv, ned2aer, aer2ned, ecef2ned
38-
from .ecef import geodetic2ecef, ecef2geodetic, eci2geodetic, ecef2enuv, enu2ecef, ecef2enu, enu2uvw, uvw2enu
38+
from .ecef import geodetic2ecef, ecef2geodetic, eci2geodetic, geodetic2eci, ecef2enuv, enu2ecef, ecef2enu, enu2uvw, uvw2enu
3939
from .sidereal import datetime2sidereal
4040
from .latitude import (
4141
geod2geoc,

src/pymap3d/ecef.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" Transforms involving ECEF: earth-centered, earth-fixed frame """
22
try:
33
from numpy import radians, sin, cos, tan, arctan as atan, hypot, degrees, arctan2 as atan2, sqrt, pi, vectorize
4-
from .eci import eci2ecef
4+
from .eci import eci2ecef, ecef2eci
55
except ImportError:
66
from math import radians, sin, cos, tan, atan, hypot, degrees, atan2, sqrt, pi
77

@@ -15,7 +15,17 @@
1515
# py < 3.6 compatible
1616
tau = 2 * pi
1717

18-
__all__ = ["geodetic2ecef", "ecef2geodetic", "ecef2enuv", "ecef2enu", "enu2uvw", "uvw2enu", "eci2geodetic", "enu2ecef"]
18+
__all__ = [
19+
"geodetic2ecef",
20+
"ecef2geodetic",
21+
"ecef2enuv",
22+
"ecef2enu",
23+
"enu2uvw",
24+
"uvw2enu",
25+
"eci2geodetic",
26+
"geodetic2eci",
27+
"enu2ecef",
28+
]
1929

2030
if typing.TYPE_CHECKING:
2131
from numpy import ndarray
@@ -320,10 +330,14 @@ def uvw2enu(
320330
return East, North, Up
321331

322332

323-
def eci2geodetic(x: "ndarray", y: "ndarray", z: "ndarray", t: datetime) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
333+
def eci2geodetic(
334+
x: "ndarray", y: "ndarray", z: "ndarray", t: datetime, ell: Ellipsoid = None, deg: bool = True
335+
) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
324336
"""
325337
convert Earth Centered Internal ECI to geodetic coordinates
326338
339+
J2000 time
340+
327341
Parameters
328342
----------
329343
x : "ndarray"
@@ -333,7 +347,11 @@ def eci2geodetic(x: "ndarray", y: "ndarray", z: "ndarray", t: datetime) -> typin
333347
z : "ndarray"
334348
ECI z-location [meters]
335349
t : datetime.datetime, "ndarray"
336-
length N vector of datetime OR greenwich sidereal time angle [radians].
350+
UTC time
351+
ell : Ellipsoid (optional)
352+
planet ellipsoid model
353+
deg : bool (optional)
354+
if True, degrees. if False, radians
337355
338356
Results
339357
-------
@@ -344,21 +362,56 @@ def eci2geodetic(x: "ndarray", y: "ndarray", z: "ndarray", t: datetime) -> typin
344362
alt : "ndarray"
345363
altitude above ellipsoid (meters)
346364
347-
Notes
348-
-----
349-
350-
Conversion is idealized: doesn't consider nutations, perterbations,
351-
etc. like the IAU-76/FK5 or IAU-2000/2006 model-based conversions
352-
from ECI to ECEF
353-
354365
eci2geodetic() a.k.a. eci2lla()
355366
"""
356367
if eci2ecef is None:
357368
raise ImportError("pip install astropy")
358369

359370
xecef, yecef, zecef = eci2ecef(x, y, z, t)
360371

361-
return ecef2geodetic(xecef, yecef, zecef)
372+
return ecef2geodetic(xecef, yecef, zecef, ell, deg)
373+
374+
375+
def geodetic2eci(
376+
lat: "ndarray", lon: "ndarray", alt: "ndarray", t: datetime, ell: Ellipsoid = None, deg: bool = True
377+
) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
378+
"""
379+
convert geodetic coordinates to Earth Centered Internal ECI
380+
381+
J2000 frame
382+
383+
Parameters
384+
----------
385+
lat : "ndarray"
386+
geodetic latitude
387+
lon : "ndarray"
388+
geodetic longitude
389+
alt : "ndarray"
390+
altitude above ellipsoid (meters)
391+
t : datetime.datetime, "ndarray"
392+
UTC time
393+
ell : Ellipsoid (optional)
394+
planet ellipsoid model
395+
deg : bool (optional)
396+
if True, degrees. if False, radians
397+
398+
Results
399+
-------
400+
x : "ndarray"
401+
ECI x-location [meters]
402+
y : "ndarray"
403+
ECI y-location [meters]
404+
z : "ndarray"
405+
ECI z-location [meters]
406+
407+
geodetic2eci() a.k.a lla2eci()
408+
"""
409+
if ecef2eci is None:
410+
raise ImportError("pip install astropy")
411+
412+
x, y, z = geodetic2ecef(lat, lon, alt, ell, deg)
413+
414+
return ecef2eci(x, y, z, t)
362415

363416

364417
def enu2ecef(
@@ -384,9 +437,9 @@ def enu2ecef(
384437
u1 : "ndarray"
385438
target up ENU coordinate (meters)
386439
lat0 : "ndarray"
387-
Observer geodetic latitude
440+
Observer geodetic latitude
388441
lon0 : "ndarray"
389-
Observer geodetic longitude
442+
Observer geodetic longitude
390443
h0 : "ndarray"
391444
observer altitude above geodetic ellipsoid (meters)
392445
ell : Ellipsoid, optional

src/pymap3d/eci.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ def eci2ecef(x: "ndarray", y: "ndarray", z: "ndarray", time: datetime) -> typing
3535
3636
Results
3737
-------
38-
x : "ndarray"
39-
target x ECEF coordinate
40-
y : "ndarray"
41-
target y ECEF coordinate
42-
z : "ndarray"
43-
target z ECEF coordinate
38+
x_ecef : "ndarray"
39+
x ECEF coordinate
40+
y_ecef : "ndarray"
41+
y ECEF coordinate
42+
z_ecef : "ndarray"
43+
z ECEF coordinate
4444
"""
4545

4646
if Time is None:
@@ -72,12 +72,12 @@ def ecef2eci(x: "ndarray", y: "ndarray", z: "ndarray", time: datetime) -> typing
7272
7373
Results
7474
-------
75-
x : "ndarray"
76-
target x ECI coordinate
77-
y : "ndarray"
78-
target y ECI coordinate
79-
z : "ndarray"
80-
target z ECI coordinate
75+
x_eci : "ndarray"
76+
x ECI coordinate
77+
y_eci : "ndarray"
78+
y ECI coordinate
79+
z_eci : "ndarray"
80+
z ECI coordinate
8181
"""
8282

8383
if Time is None:

tests/test_astropy.py

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
from pytest import approx
44
from math import radians
55
from datetime import datetime
6-
import pymap3d as pm
6+
77
import pymap3d.sidereal as pmd
88
import pymap3d.haversine as pmh
99

1010
lon = -148
1111
t0 = datetime(2014, 4, 6, 8)
12-
lla0 = (42, -82, 200)
1312
sra = 2.90658
1413
ha = 45.482789587392013
15-
eci0 = (-3.977913815668146e6, -2.582332196263046e6, 4.250818828152067e6)
1614

1715

1816
@pytest.mark.parametrize("time", [t0, [t0]], ids=("scalar", "list"))
@@ -44,25 +42,5 @@ def test_anglesep_meeus():
4442
assert pmh.anglesep_meeus(35, 23, 84, 20) == approx(ha)
4543

4644

47-
def test_eci_geodetic():
48-
pytest.importorskip("astropy")
49-
t = "2013-01-15T12:00:05"
50-
lla = pm.eci2geodetic(*eci0, t)
51-
assert lla == approx(lla0, rel=0.001)
52-
53-
54-
def test_eci_aer():
55-
pytest.importorskip("astropy")
56-
t = "2013-01-15T12:00:05"
57-
58-
aer1 = pm.eci2aer(*eci0, 42, -100, 0, t)
59-
assert aer1 == approx([83.9511, -6.66787, 1485123.89], rel=0.001)
60-
61-
assert pm.aer2eci(*aer1, 42, -100, 0, t) == approx(eci0, rel=0.001)
62-
63-
with pytest.raises(ValueError):
64-
pm.aer2eci(aer1[0], aer1[1], -1, 42, -100, 0, t)
65-
66-
6745
if __name__ == "__main__":
6846
pytest.main([__file__])

tests/test_eci.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,49 @@ def test_ecef2eci():
2525
assert eci == approx([-2.9818e6, 5.2070e6, 3.1616e6], rel=0.01)
2626

2727

28+
def test_eci2geodetic():
29+
pytest.importorskip("astropy")
30+
31+
eci = [-2981784, 5207055, 3161595]
32+
utc = datetime(2019, 1, 4, 12)
33+
lla = pm.eci2geodetic(*eci, utc)
34+
assert lla == approx([27.881, -163.722, 408850.65], rel=0.001)
35+
36+
37+
def test_geodetic2eci():
38+
pytest.importorskip("astropy")
39+
40+
lla = [27.881, -163.722, 408850.65]
41+
utc = datetime(2019, 1, 4, 12)
42+
eci = pm.geodetic2eci(*lla, utc)
43+
assert eci == approx([-2981784, 5207055, 3161595], rel=0.001)
44+
45+
46+
def test_eci2aer():
47+
# test coords from Matlab eci2aer
48+
pytest.importorskip("astropy")
49+
t = datetime(1969, 7, 20, 21, 17, 40)
50+
51+
eci = [-3.8454e8, -0.5099e8, -0.3255e8]
52+
lla = [28.4, -80.5, 2.7]
53+
54+
aer = pm.eci2aer(*eci, *lla, t)
55+
assert aer == approx([162.55, 55.12, 384013940.9], rel=0.001)
56+
57+
58+
def test_aer2eci():
59+
# test coords from Matlab aer2eci
60+
pytest.importorskip("astropy")
61+
62+
aer = [162.55, 55.12, 384013940.9]
63+
lla = [28.4, -80.5, 2.7]
64+
t = datetime(1969, 7, 20, 21, 17, 40)
65+
66+
assert pm.aer2eci(*aer, *lla, t) == approx([-3.8454e8, -0.5099e8, -0.3255e8], rel=0.001)
67+
68+
with pytest.raises(ValueError):
69+
pm.aer2eci(aer[0], aer[1], -1, *lla, t)
70+
71+
2872
if __name__ == "__main__":
2973
pytest.main([__file__])

0 commit comments

Comments
 (0)