[Git][debian-gis-team/pymap3d][upstream] New upstream version 2.4.0
Antonio Valentino
gitlab at salsa.debian.org
Sat Apr 25 08:34:12 BST 2020
Antonio Valentino pushed to branch upstream at Debian GIS Project / pymap3d
Commits:
6bff0c08 by Antonio Valentino at 2020-04-25T06:19:08+00:00
New upstream version 2.4.0
- - - - -
12 changed files:
- contributors.md → .github/contributors.md
- .github/workflows/ci.yml
- .github/workflows/ci_stdlib_only.yml
- − CONTRIBUTING.md
- README.md
- setup.cfg
- src/pymap3d/__init__.py
- src/pymap3d/aer.py
- src/pymap3d/ecef.py
- src/pymap3d/eci.py
- tests/test_astropy.py
- tests/test_eci.py
Changes:
=====================================
contributors.md → .github/contributors.md
=====================================
=====================================
.github/workflows/ci.yml
=====================================
@@ -4,8 +4,7 @@ on:
push:
paths:
- "**.py"
- pull_request:
- release:
+
jobs:
@@ -15,7 +14,7 @@ jobs:
- uses: actions/checkout at v2
- uses: actions/setup-python at v1
with:
- python-version: '3.x'
+ python-version: 3.8
- run: pip install .[full,tests,lint]
- run: flake8
=====================================
.github/workflows/ci_stdlib_only.yml
=====================================
@@ -4,8 +4,7 @@ on:
push:
paths:
- "**.py"
- pull_request:
- release:
+
jobs:
=====================================
CONTRIBUTING.md deleted
=====================================
@@ -1,14 +0,0 @@
-## Issues
-
-GitHub Issues are a great way to report problems or request features.
-This project adheres to [Semantic Versioning](https://semver.org).
-The goal of PyMap3D is to provide a Matlab Mapping Toolbox - like API for 3D coordinate conversion,
-allowing for arbitrarily shaped input/output arrays where feasible.
-
-## Pull Requests
-
-It's always nice to get pull requests.
-PyMap3D is intended for non-interactive use from massively parallel (HPC) down to embedded systems.
-
-Most PyMap3D functions require Numpy for arrays.
-Some of the most essential functions fall back to Python stdlib `math` for scalar cases when Numpy isn't available.
=====================================
README.md
=====================================
@@ -88,7 +88,7 @@ converted to the desired coordinate system:
aer2ecef aer2enu aer2geodetic aer2ned
ecef2aer ecef2enu ecef2enuv ecef2geodetic ecef2ned ecef2nedv
- ecef2eci eci2ecef eci2aer aer2eci
+ ecef2eci eci2ecef eci2aer aer2eci geodetic2eci eci2geodetic
enu2aer enu2ecef enu2geodetic
geodetic2aer geodetic2ecef geodetic2enu geodetic2ned
ned2aer ned2ecef ned2geodetic
@@ -135,7 +135,8 @@ As noted above, use list comprehension if you need vector data without Numpy.
As compared to [PyProj](https://github.com/jswhit/pyproj):
-* PyMap3D does not require anything beyond pure Python -- not even Numpy is required except for ECI (let us know if this is an issue).
+* PyMap3D does not require anything beyond pure Python for most transforms
+* Astronomical conversions are done using (optional) AstroPy for established accuracy
* PyMap3D API is similar to Matlab Mapping Toolbox, while PyProj's interface is quite distinct
* PyMap3D intrinsically handles local coordinate systems such as ENU,
while PyProj ENU requires some [additional effort](https://github.com/jswhit/pyproj/issues/105).
=====================================
setup.cfg
=====================================
@@ -1,6 +1,6 @@
[metadata]
name = pymap3d
-version = 2.3.0
+version = 2.4.0
author = Michael Hirsch, Ph.D.
author_email = scivision at users.noreply.github.com
description = pure Python (no prereqs) coordinate conversions, following convention of several popular Matlab routines.
=====================================
src/pymap3d/__init__.py
=====================================
@@ -35,7 +35,7 @@ from .aer import ecef2aer, aer2ecef, geodetic2aer, aer2geodetic, eci2aer, aer2ec
from .ellipsoid import Ellipsoid
from .enu import enu2geodetic, geodetic2enu, aer2enu, enu2aer
from .ned import ned2ecef, ned2geodetic, geodetic2ned, ecef2nedv, ned2aer, aer2ned, ecef2ned
-from .ecef import geodetic2ecef, ecef2geodetic, eci2geodetic, ecef2enuv, enu2ecef, ecef2enu, enu2uvw, uvw2enu
+from .ecef import geodetic2ecef, ecef2geodetic, eci2geodetic, geodetic2eci, ecef2enuv, enu2ecef, ecef2enu, enu2uvw, uvw2enu
from .sidereal import datetime2sidereal
from .latitude import (
geod2geoc,
=====================================
src/pymap3d/aer.py
=====================================
@@ -165,7 +165,7 @@ def aer2geodetic(
def eci2aer(
- x: "ndarray", y: "ndarray", z: "ndarray", lat0: "ndarray", lon0: "ndarray", h0: "ndarray", t: datetime, useastropy: bool = True
+ x: "ndarray", y: "ndarray", z: "ndarray", lat0: "ndarray", lon0: "ndarray", h0: "ndarray", t: datetime
) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
"""
takes Earth Centered Inertial x,y,z ECI coordinates of point and gives az, el, slant range from Observer
@@ -187,8 +187,6 @@ def eci2aer(
observer altitude above geodetic ellipsoid (meters)
t : datetime.datetime
Observation time
- useastropy: bool, optional
- Force use / non-use of AstroPy (default use AstroPy if available)
Returns
-------
@@ -200,9 +198,9 @@ def eci2aer(
slant range [meters]
"""
if eci2ecef is None:
- raise ImportError("pip install numpy")
+ raise ImportError("pip install astropy")
- xecef, yecef, zecef = eci2ecef(x, y, z, t, useastropy=useastropy)
+ xecef, yecef, zecef = eci2ecef(x, y, z, t)
return ecef2aer(xecef, yecef, zecef, lat0, lon0, h0)
@@ -217,7 +215,6 @@ def aer2eci(
t: datetime,
ell=None,
deg: bool = True,
- useastropy: bool = True,
) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
"""
gives ECI of a point from an observer at az, el, slant range
@@ -242,8 +239,6 @@ def aer2eci(
reference ellipsoid
deg : bool, optional
degrees input/output (False: radians in/out)
- useastropy: bool, optional
- Force use / non-use of AstroPy (default use AstroPy if available)
Returns
-------
@@ -262,7 +257,7 @@ def aer2eci(
x, y, z = aer2ecef(az, el, srange, lat0, lon0, h0, ell, deg)
- return ecef2eci(x, y, z, t, useastropy=useastropy)
+ return ecef2eci(x, y, z, t)
def aer2ecef(
=====================================
src/pymap3d/ecef.py
=====================================
@@ -1,7 +1,7 @@
""" Transforms involving ECEF: earth-centered, earth-fixed frame """
try:
from numpy import radians, sin, cos, tan, arctan as atan, hypot, degrees, arctan2 as atan2, sqrt, pi, vectorize
- from .eci import eci2ecef
+ from .eci import eci2ecef, ecef2eci
except ImportError:
from math import radians, sin, cos, tan, atan, hypot, degrees, atan2, sqrt, pi
@@ -15,7 +15,17 @@ from .utils import sanitize
# py < 3.6 compatible
tau = 2 * pi
-__all__ = ["geodetic2ecef", "ecef2geodetic", "ecef2enuv", "ecef2enu", "enu2uvw", "uvw2enu", "eci2geodetic", "enu2ecef"]
+__all__ = [
+ "geodetic2ecef",
+ "ecef2geodetic",
+ "ecef2enuv",
+ "ecef2enu",
+ "enu2uvw",
+ "uvw2enu",
+ "eci2geodetic",
+ "geodetic2eci",
+ "enu2ecef",
+]
if typing.TYPE_CHECKING:
from numpy import ndarray
@@ -321,11 +331,13 @@ def uvw2enu(
def eci2geodetic(
- x: "ndarray", y: "ndarray", z: "ndarray", t: datetime, useastropy: bool = True
+ x: "ndarray", y: "ndarray", z: "ndarray", t: datetime, ell: Ellipsoid = None, deg: bool = True
) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
"""
convert Earth Centered Internal ECI to geodetic coordinates
+ J2000 time
+
Parameters
----------
x : "ndarray"
@@ -335,7 +347,11 @@ def eci2geodetic(
z : "ndarray"
ECI z-location [meters]
t : datetime.datetime, "ndarray"
- length N vector of datetime OR greenwich sidereal time angle [radians].
+ UTC time
+ ell : Ellipsoid (optional)
+ planet ellipsoid model
+ deg : bool (optional)
+ if True, degrees. if False, radians
Results
-------
@@ -346,21 +362,56 @@ def eci2geodetic(
alt : "ndarray"
altitude above ellipsoid (meters)
- Notes
- -----
-
- Conversion is idealized: doesn't consider nutations, perterbations,
- etc. like the IAU-76/FK5 or IAU-2000/2006 model-based conversions
- from ECI to ECEF
-
eci2geodetic() a.k.a. eci2lla()
"""
if eci2ecef is None:
- raise ImportError("pip install numpy")
+ raise ImportError("pip install astropy")
+
+ xecef, yecef, zecef = eci2ecef(x, y, z, t)
+
+ return ecef2geodetic(xecef, yecef, zecef, ell, deg)
+
+
+def geodetic2eci(
+ lat: "ndarray", lon: "ndarray", alt: "ndarray", t: datetime, ell: Ellipsoid = None, deg: bool = True
+) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
+ """
+ convert geodetic coordinates to Earth Centered Internal ECI
+
+ J2000 frame
+
+ Parameters
+ ----------
+ lat : "ndarray"
+ geodetic latitude
+ lon : "ndarray"
+ geodetic longitude
+ alt : "ndarray"
+ altitude above ellipsoid (meters)
+ t : datetime.datetime, "ndarray"
+ UTC time
+ ell : Ellipsoid (optional)
+ planet ellipsoid model
+ deg : bool (optional)
+ if True, degrees. if False, radians
- xecef, yecef, zecef = eci2ecef(x, y, z, t, useastropy=useastropy)
+ Results
+ -------
+ x : "ndarray"
+ ECI x-location [meters]
+ y : "ndarray"
+ ECI y-location [meters]
+ z : "ndarray"
+ ECI z-location [meters]
+
+ geodetic2eci() a.k.a lla2eci()
+ """
+ if ecef2eci is None:
+ raise ImportError("pip install astropy")
- return ecef2geodetic(xecef, yecef, zecef)
+ x, y, z = geodetic2ecef(lat, lon, alt, ell, deg)
+
+ return ecef2eci(x, y, z, t)
def enu2ecef(
@@ -386,9 +437,9 @@ def enu2ecef(
u1 : "ndarray"
target up ENU coordinate (meters)
lat0 : "ndarray"
- Observer geodetic latitude
+ Observer geodetic latitude
lon0 : "ndarray"
- Observer geodetic longitude
+ Observer geodetic longitude
h0 : "ndarray"
observer altitude above geodetic ellipsoid (meters)
ell : Ellipsoid, optional
=====================================
src/pymap3d/eci.py
=====================================
@@ -1,13 +1,12 @@
""" transforms involving ECI earth-centered inertial """
from datetime import datetime
-import numpy as np
import typing
-from .sidereal import datetime2sidereal
-
try:
+ from astropy.coordinates import GCRS, ITRS, EarthLocation, CartesianRepresentation
from astropy.time import Time
+ import astropy.units as u
except ImportError:
Time = None
@@ -17,13 +16,11 @@ if typing.TYPE_CHECKING:
from numpy import ndarray
-def eci2ecef(
- x: "ndarray", y: "ndarray", z: "ndarray" = None, time: datetime = None, *, useastropy: bool = True
-) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
+def eci2ecef(x: "ndarray", y: "ndarray", z: "ndarray", time: datetime) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
"""
Observer => Point ECI => ECEF
- defaults to J2000 frame
+ J2000 frame
Parameters
----------
@@ -35,58 +32,32 @@ def eci2ecef(
ECI z-location [meters]
time : datetime.datetime
time of obsevation (UTC)
- useastropy : bool, optional
- use AstroPy for conversion
Results
-------
- x : "ndarray"
- target x ECEF coordinate
- y : "ndarray"
- target y ECEF coordinate
- z : "ndarray"
- target z ECEF coordinate
+ x_ecef : "ndarray"
+ x ECEF coordinate
+ y_ecef : "ndarray"
+ y ECEF coordinate
+ z_ecef : "ndarray"
+ z ECEF coordinate
"""
- # %%
- x = np.atleast_1d(x)
- y = np.atleast_1d(y)
- z = np.atleast_1d(z)
- if not x.shape == y.shape == z.shape:
- raise ValueError("shapes of ECI x,y,z must be identical")
-
- useastropy = useastropy and Time is not None
-
- if useastropy:
- gst = Time(time).sidereal_time("apparent", "greenwich").radian
- else:
- gst = datetime2sidereal(time, 0.0)
-
- gst = np.atleast_1d(gst)
- if gst.ndim != 1:
- raise ValueError("GST must be scalar or vector in radians")
- if gst.size == 1:
- gst *= np.ones(x.size)
- if gst.size != x.size:
- raise ValueError("GST must be scalar or same length as positions")
-
- eci = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
- ecef = np.empty((x.size, 3))
- for i in range(eci.shape[0]):
- ecef[i, :] = _rottrip(gst[i]) @ eci[i, :]
-
- xecef = ecef[:, 0].reshape(x.shape)
- yecef = ecef[:, 1].reshape(x.shape)
- zecef = ecef[:, 2].reshape(x.shape)
-
- return xecef, yecef, zecef
-
-
-def ecef2eci(
- x: "ndarray", y: "ndarray", z: "ndarray" = None, time: datetime = None, *, useastropy: bool = True
-) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
+
+ if Time is None:
+ raise ImportError("pip install astropy")
+
+ gcrs = GCRS(CartesianRepresentation(x * u.m, y * u.m, z * u.m), obstime=time)
+ itrs = gcrs.transform_to(ITRS(obstime=time))
+
+ return itrs.x.value, itrs.y.value, itrs.z.value
+
+
+def ecef2eci(x: "ndarray", y: "ndarray", z: "ndarray", time: datetime) -> typing.Tuple["ndarray", "ndarray", "ndarray"]:
"""
Point => Point ECEF => ECI
+ J2000 frame
+
Parameters
----------
@@ -98,66 +69,22 @@ def ecef2eci(
target z ECEF coordinate
time : datetime.datetime
time of observation
- useastropy : bool, optional
- use AstroPy for conversion
-
Results
-------
- x : "ndarray"
- target x ECI coordinate
- y : "ndarray"
- target y ECI coordinate
- z : "ndarray"
- target z ECI coordinate
+ x_eci : "ndarray"
+ x ECI coordinate
+ y_eci : "ndarray"
+ y ECI coordinate
+ z_eci : "ndarray"
+ z ECI coordinate
"""
- # %%
- x = np.atleast_1d(x)
- y = np.atleast_1d(y)
- z = np.atleast_1d(z)
- if not x.shape == y.shape == z.shape:
- raise ValueError("shapes of ECI x,y,z must be identical")
-
- useastropy = useastropy and Time is not None
-
- if useastropy:
- gst = Time(time).sidereal_time("apparent", "greenwich").radian
- else:
- gst = datetime2sidereal(time, 0.0)
- gst = np.atleast_1d(gst)
- if gst.ndim != 1:
- raise ValueError("GST must be scalar or vector in radians")
- if gst.size == 1:
- gst *= np.ones(x.size)
- if gst.size != x.size:
- raise ValueError("GST must be scalar or same length as positions")
+ if Time is None:
+ raise ImportError("pip install astropy")
- ecef = np.column_stack((x.ravel(), y.ravel(), z.ravel()))
- eci = np.empty((x.size, 3))
- for i in range(x.size):
- eci[i, :] = _rottrip(gst[i]).T @ ecef[i, :] # this one is transposed
+ itrs = ITRS(CartesianRepresentation(x * u.m, y * u.m, z * u.m), obstime=time)
+ gcrs = itrs.transform_to(GCRS(obstime=time))
+ ecef = EarthLocation(*gcrs.cartesian.xyz)
- xeci = eci[:, 0].reshape(x.shape)
- yeci = eci[:, 1].reshape(x.shape)
- zeci = eci[:, 2].reshape(x.shape)
-
- return xeci, yeci, zeci
-
-
-def _rottrip(ang: float) -> np.ndarray:
- """
- transformation matrix
-
- Parameters
- ----------
-
- ang : float
- angle to transform (radians)
-
- Returns
- -------
- T : numpy.ndarray of float
- 3 x 3 transformation matrix
- """
- return np.array([[np.cos(ang), np.sin(ang), 0], [-np.sin(ang), np.cos(ang), 0], [0, 0, 1]])
+ return ecef.x.value, ecef.y.value, ecef.z.value
=====================================
tests/test_astropy.py
=====================================
@@ -3,16 +3,14 @@ import pytest
from pytest import approx
from math import radians
from datetime import datetime
-import pymap3d as pm
+
import pymap3d.sidereal as pmd
import pymap3d.haversine as pmh
lon = -148
t0 = datetime(2014, 4, 6, 8)
-lla0 = (42, -82, 200)
sra = 2.90658
ha = 45.482789587392013
-eci0 = (-3.977913815668146e6, -2.582332196263046e6, 4.250818828152067e6)
@pytest.mark.parametrize("time", [t0, [t0]], ids=("scalar", "list"))
@@ -44,27 +42,5 @@ def test_anglesep_meeus():
assert pmh.anglesep_meeus(35, 23, 84, 20) == approx(ha)
- at pytest.mark.parametrize("useastropy", [True, False])
-def test_eci_geodetic(useastropy):
- pytest.importorskip("numpy")
- t = "2013-01-15T12:00:05"
- lla = pm.eci2geodetic(*eci0, t, useastropy=useastropy)
- assert lla == approx(lla0, rel=0.2)
-
-
- at pytest.mark.parametrize("useastropy", [True, False])
-def test_eci_aer(useastropy):
- pytest.importorskip("numpy")
- t = "2013-01-15T12:00:05"
-
- aer1 = pm.eci2aer(*eci0, 42, -100, 0, t, useastropy=useastropy)
- assert aer1 == approx([83.73050, -6.614478, 1.473510e6], rel=0.001)
-
- assert pm.aer2eci(*aer1, 42, -100, 0, t, useastropy=useastropy) == approx(eci0, rel=0.001)
-
- with pytest.raises(ValueError):
- pm.aer2eci(aer1[0], aer1[1], -1, 42, -100, 0, t, useastropy=useastropy)
-
-
if __name__ == "__main__":
pytest.main([__file__])
=====================================
tests/test_eci.py
=====================================
@@ -1,35 +1,72 @@
import pytest
from pytest import approx
+from datetime import datetime
import pymap3d as pm
-t0 = "2014-04-06T08:00:00"
-t1 = "2013-01-15T12:00:05"
-eci0 = (-3.977913815668146e6, -2.582332196263046e6, 4.250818828152067e6)
+def test_eci2ecef():
- at pytest.mark.parametrize("useastropy", [True, False])
-def test_eciecef(useastropy):
- pytest.importorskip("numpy")
- ecef = pm.eci2ecef(*eci0, t1, useastropy=useastropy)
- assert ecef == approx([649012.04640917, -4697980.55129606, 4250818.82815207], rel=0.001)
+ pytest.importorskip("astropy")
+ # this example from Matlab eci2ecef docs
+ eci = [-2981784, 5207055, 3161595]
+ utc = datetime(2019, 1, 4, 12)
+ ecef = pm.eci2ecef(*eci, utc)
+ assert ecef == approx([-5.7627e6, -1.6827e6, 3.1560e6], rel=0.01)
- assert pm.ecef2eci(*ecef, t1, useastropy=useastropy) == approx(eci0, rel=0.001)
+def test_ecef2eci():
- at pytest.mark.parametrize("useastropy", [True, False])
-def test_eci_times(useastropy):
- pytest.importorskip("numpy")
- with pytest.raises(ValueError):
- pm.eci2ecef(*eci0, [t0, t0], useastropy=useastropy)
+ pytest.importorskip("astropy")
+ # this example from Matlab ecef2eci docs
+ ecef = [-5762640, -1682738, 3156028]
+ utc = datetime(2019, 1, 4, 12)
+ eci = pm.ecef2eci(*ecef, utc)
+ print(eci)
+ assert eci == approx([-2.9818e6, 5.2070e6, 3.1616e6], rel=0.01)
+
+
+def test_eci2geodetic():
+ pytest.importorskip("astropy")
+
+ eci = [-2981784, 5207055, 3161595]
+ utc = datetime(2019, 1, 4, 12)
+ lla = pm.eci2geodetic(*eci, utc)
+ assert lla == approx([27.881, -163.722, 408850.65], rel=0.001)
+
+
+def test_geodetic2eci():
+ pytest.importorskip("astropy")
+
+ lla = [27.881, -163.722, 408850.65]
+ utc = datetime(2019, 1, 4, 12)
+ eci = pm.geodetic2eci(*lla, utc)
+ assert eci == approx([-2981784, 5207055, 3161595], rel=0.001)
- with pytest.raises(ValueError):
- pm.ecef2eci(*eci0, [t0, t0], useastropy=useastropy)
- x = [eci0[0]] * 2
- y = [eci0[1]] * 2
- z = [eci0[2]] * 2
- t = [t0] * 2
- assert pm.ecef2eci(*pm.eci2ecef(x, y, z, t, useastropy=useastropy), t, useastropy=useastropy) == approx(eci0, rel=0.001)
+def test_eci2aer():
+ # test coords from Matlab eci2aer
+ pytest.importorskip("astropy")
+ t = datetime(1969, 7, 20, 21, 17, 40)
+
+ eci = [-3.8454e8, -0.5099e8, -0.3255e8]
+ lla = [28.4, -80.5, 2.7]
+
+ aer = pm.eci2aer(*eci, *lla, t)
+ assert aer == approx([162.55, 55.12, 384013940.9], rel=0.001)
+
+
+def test_aer2eci():
+ # test coords from Matlab aer2eci
+ pytest.importorskip("astropy")
+
+ aer = [162.55, 55.12, 384013940.9]
+ lla = [28.4, -80.5, 2.7]
+ t = datetime(1969, 7, 20, 21, 17, 40)
+
+ assert pm.aer2eci(*aer, *lla, t) == approx([-3.8454e8, -0.5099e8, -0.3255e8], rel=0.001)
+
+ with pytest.raises(ValueError):
+ pm.aer2eci(aer[0], aer[1], -1, *lla, t)
if __name__ == "__main__":
View it on GitLab: https://salsa.debian.org/debian-gis-team/pymap3d/-/commit/6bff0c0860f766be2468ee9747f77e913f18e2b3
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pymap3d/-/commit/6bff0c0860f766be2468ee9747f77e913f18e2b3
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20200425/c06785df/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list