Skip to content

Commit e8628f1

Browse files
committed
fallback for non-astropy and python 2.7
1 parent 2083a74 commit e8628f1

File tree

6 files changed

+121
-85
lines changed

6 files changed

+121
-85
lines changed

README.rst

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,19 @@ Python 3-D coordinate conversions
1919
==================================
2020

2121
Python coordinate conversions, following convention of several popular Matlab routines.
22+
Tested from Python 2.7 through Python 3.7+
23+
24+
For those not having AstroPy, lower accuracy fallback functions are included for some functions.
2225

2326
.. contents::
2427

28+
29+
:prereqs: AstroPy, Numpy, Python 3 or Python 2.7
30+
2531
Install
2632
=======
2733
Development::
28-
34+
2935
python -m pip install -e .
3036

3137

@@ -36,12 +42,16 @@ simple::
3642

3743
Usage
3844
=====
39-
a few quick examples::
45+
a few quick examples
46+
47+
.. code:: python
4048
41-
from pymap3d import *
49+
import pymap3d as pm
4250
43-
lat,lon,alt = eci2geodetic(eci,t)
44-
az,el,range = geodetic2aer(lat,lon,alt,42,-82,0)
51+
lat,lon,alt = pm.eci2geodetic(eci, t)
52+
53+
az,el,range = pm.geodetic2aer(lat, lon, alt, 42, -82, 0)
54+
4555
4656
Functions
4757
==========
@@ -60,5 +70,6 @@ Popular mapping toolbox functions ported to Python include::
6070

6171
Caveats
6272
=======
63-
Atmospheric effects neglected in all functions not invoking AstroPy.
64-
Planetary perturbations and nutation etc. not fully considered.
73+
74+
* Atmospheric effects neglected in all functions not invoking AstroPy. Need to update code to add these input parameters (just start a GitHub Issue to request).
75+
* Planetary perturbations and nutation etc. not fully considered.

pymap3d/__init__.py

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
1414
see test.py for example uses.
1515
"""
16-
1716
from __future__ import division
1817
from six import string_types,PY2
19-
from dateutil.parser import parse
2018
from datetime import datetime
2119
import numpy as np
2220
from numpy import sin, cos, tan, sqrt, radians, arctan2, hypot, degrees
@@ -26,6 +24,10 @@
2624
from astropy.coordinates import Angle,SkyCoord, EarthLocation, AltAz, ICRS
2725
except ImportError:
2826
Time = None
27+
#
28+
from .vallado import vazel2radec, vradec2azel
29+
from .timeconv import str2dt
30+
2931

3032
class EarthEllipsoid:
3133

@@ -418,20 +420,6 @@ def get_radius_normal(lat_radians, ell):
418420
return a**2 / sqrt(
419421
a**2 * (cos(lat_radians))**2 + b**2 *
420422
(sin(lat_radians))**2)
421-
422-
423-
def str2dt(t):
424-
"""
425-
output: datetime
426-
"""
427-
428-
t = np.atleast_1d(t)
429-
if isinstance(t[0], string_types):
430-
t = [parse(T) for T in t]
431-
432-
assert isinstance(t[0], datetime), 'did not convert {} to datetime'.format(type(t[0]))
433-
434-
return t
435423
#%% internal use
436424
def _depack(x0):
437425
m, n = x0.shape
@@ -490,12 +478,9 @@ def _uvw2enu(u, v, w, lat0, lon0, deg):
490478

491479
#%% azel radec
492480
def azel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
493-
if PY2:
494-
raise RuntimeError('Python 2 is not supported for this AstroPy operation. It is time to upgrade to Python 3 (released 2008)')
495-
496-
if Time is None:
497-
raise ImportError('You need to install AstroPy')
498481

482+
if PY2 or Time is None: # non-AstroPy method, less accurate
483+
return vazel2radec(az_deg, el_deg, lat_deg, lon_deg, t)
499484

500485
t = str2dt(t)
501486

@@ -511,7 +496,7 @@ def azel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
511496

512497
def radec2azel(ra_deg, dec_deg, lat_deg, lon_deg, t):
513498
if Time is None:
514-
raise ImportError('You need to install AstroPy')
499+
return vradec2azel(ra_deg, dec_deg, lat_deg, lon_deg, t)
515500
#%% input trapping
516501
t = str2dt(t)
517502
lat_deg = np.atleast_1d(lat_deg)

pymap3d/datetime2hourangle.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,54 @@
11
#!/usr/bin/env python
2+
from __future__ import division
23
from numpy import atleast_1d, empty_like, pi, nan
34
from datetime import datetime
4-
from astropy.time import Time
5-
import astropy.units as u
6-
from astropy.coordinates import Longitude
5+
try:
6+
from astropy.time import Time
7+
import astropy.units as u
8+
from astropy.coordinates import Longitude
9+
except ImportError:
10+
Time = None
11+
12+
#
13+
from .timeconv import str2dt
714
"""
8-
input longitude and output are in RADIANS!
15+
The "usevallado" datetime to julian runs 4 times faster than astropy.
16+
However, AstroPy is more accurate.
917
10-
Note: The "usevallado" datetime to julian runs 4 times faster than astropy.
11-
AstroPy is more accurate, but version 1.0.0 doesn't auto-download new IERS data
12-
for current/future times, giving an
13-
IndexError: (some) times are outside of range covered by IERS table.
1418
"""
1519

1620

1721
def datetime2sidereal(t, lon_radians, usevallado=True):
1822
"""
23+
algorithm by D. Vallado Fundamentals of Astrodynamics and Applications
1924
lon: longitude in RADIANS
2025
"""
2126
if usevallado:
2227
jd = datetime2julian(t)
23-
gst = julian2sidereal(jd) # Greenwich Sidereal time RADIANS
24-
return gst + lon_radians # radians # Algorithm 15 p. 188 rotate to LOCAL SIDEREAL TIME
28+
# %% Greenwich Sidereal time RADIANS
29+
gst = julian2sidereal(jd)
30+
# %% Algorithm 15 p. 188 rotate GST to LOCAL SIDEREAL TIME
31+
tsr = gst + lon_radians # radians
2532
else: # astropy
26-
return Time(t).sidereal_time(kind='apparent',
27-
longitude=Longitude(lon_radians, unit=u.radian)).radian
33+
if Time is not None:
34+
tsr = Time(t).sidereal_time(kind='apparent',
35+
longitude=Longitude(lon_radians, unit=u.radian)).radian
36+
else:
37+
raise ImportError('AstroPy required, or use "usevallado=True"')
38+
39+
return tsr
2840

2941

3042
def datetime2julian(t):
3143
"""
3244
from D.Vallado Fundamentals of Astrodynamics and Applications p.187
3345
and J. Meeus Astronomical Algorithms 1991 Eqn. 7.1 pg. 61
3446
"""
35-
t= atleast_1d(t)
3647

37-
assert isinstance(t[0],datetime)
48+
t = str2dt(t)
49+
t = atleast_1d(t)
50+
51+
assert isinstance(t[0], datetime)
3852

3953
jDate = empty_like(t, dtype=float) # yes we need the dtype!
4054

@@ -53,21 +67,25 @@ def datetime2julian(t):
5367
A = int(year / 100.0)
5468
B = 2 - A + int(A / 4.)
5569
C = ((d.second / 60. + d.minute) / 60. + d.hour) / 24.
56-
jDate[i] = (int(365.25 * (year + 4716)) +
70+
jDate[i] = (int(365.25 * (year + 4716)) +
5771
int(30.6001 * (month + 1)) + d.day + B - 1524.5 + C)
5872

5973
return jDate
6074

6175

6276
def julian2sidereal(juliandate):
63-
# D. Vallado Ed. 4
64-
# TODO needs unit testing
65-
# Julian centuries from J2000.0
66-
67-
# Vallado Eq. 3-42 p. 184, Seidelmann 3.311-1
68-
tUT1 = (juliandate - 2451545.0) / 36525.
69-
70-
gmst_sec = (67310.54841 + (876600 * 3600 + 8640184.812866) *
77+
"""
78+
D. Vallado Ed. 4
79+
TODO needs unit testing
80+
81+
input:
82+
juliandate: Julian centuries from J2000.0
83+
"""
84+
85+
# %% Vallado Eq. 3-42 p. 184, Seidelmann 3.311-1
86+
tUT1 = (juliandate - 2451545.0) / 36525.
87+
88+
gmst_sec = (67310.54841 + (876600 * 3600 + 8640184.812866) *
7189
tUT1 + 0.093104 * tUT1**2 - 6.2e-6 * tUT1**3) # Eqn. 3-47 p. 188
7290

7391
# 1/86400 and %(2*pi) implied by units of radians

pymap3d/vallado.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#!/usr/bin/env python
22
"""
3-
converts right ascension, declination to azimuth, elevation and vice versa
4-
5-
3+
converts right ascension, declination to azimuth, elevation and vice versa.
4+
Normally do this via AstroPy.
5+
These functions are fallbacks for those who don't wish to use AstroPy (perhaps Python 2.7 users).
66
77
inputs:
88
ra_deg: numpy ndarray of right ascension values [degrees]
@@ -17,13 +17,13 @@
1717
1818
Michael Hirsch implementation of algorithms from D. Vallado
1919
"""
20+
from __future__ import division
2021
from numpy import sin, cos, degrees, radians, arcsin, arctan2, atleast_1d
21-
2222
#
2323
from .datetime2hourangle import datetime2sidereal
2424

2525

26-
def azel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
26+
def vazel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
2727
"""
2828
from D.Vallado Fundamentals of Astrodynamics and Applications
2929
p.258-259
@@ -40,7 +40,7 @@ def azel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
4040
el = radians(el_deg)
4141
lat = radians(lat_deg)
4242
lon = radians(lon_deg)
43-
# Vallado "algorithm 28" p 268
43+
# %% Vallado "algorithm 28" p 268
4444
dec = arcsin(sin(el) * sin(lat) + cos(el) * cos(lat) * cos(az))
4545

4646
lha = arctan2(-(sin(az) * cos(el)) / cos(dec),
@@ -52,7 +52,7 @@ def azel2radec(az_deg, el_deg, lat_deg, lon_deg, t):
5252
return degrees(lst - lha) % 360, degrees(dec)
5353

5454

55-
def radec2azel(dtime,ra_deg,dec_deg,lat_deg,lon_deg):
55+
def vradec2azel(ra_deg,dec_deg,lat_deg,lon_deg,t):
5656
"""
5757
from D. Vallado "Fundamentals of Astrodynamics and Applications "
5858
4th Edition Ch. 4.4 pg. 266-268
@@ -62,11 +62,12 @@ def radec2azel(dtime,ra_deg,dec_deg,lat_deg,lon_deg):
6262
lat = radians(lat_deg)
6363
lon = radians(lon_deg)
6464

65-
lst = datetime2sidereal(dtime,lon) #RADIANS
66-
lha = lst - ra #Eq. 4-11 p. 267 LOCAL HOUR ANGLE
67-
68-
el = arcsin(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(lha) ) #Eq. 4-12 p. 267
69-
#combine Eq. 4-13 and 4-14 p. 268
65+
lst = datetime2sidereal(t, lon) #RADIANS
66+
# %% Eq. 4-11 p. 267 LOCAL HOUR ANGLE
67+
lha = lst - ra
68+
# %% #Eq. 4-12 p. 267
69+
el = arcsin(sin(lat) * sin(dec) + cos(lat) * cos(dec) * cos(lha) )
70+
# %% combine Eq. 4-13 and 4-14 p. 268
7071
az = arctan2( -sin(lha) * cos(dec) / cos(el),
7172
( sin(dec) - sin(el) * sin(lat) ) / (cos(el)*cos(lat)) )
7273

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/env python
2-
install_requires = ['python-dateutil','pytz','numpy','astropy']
2+
install_requires = ['six','python-dateutil','pytz','numpy','astropy']
33
tests_require = ['nose','coveralls']
44
# %%
55
from setuptools import setup,find_packages
66

77
setup(name='pymap3d',
88
packages=find_packages(),
9-
version = '1.2.6',
9+
version = '1.3.0',
1010
description='Python coordinate conversions, following convention of several popular Matlab routines.',
1111
long_description=open('README.rst').read(),
1212
author = 'Michael Hirsch, Ph.D.',

tests/test.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,64 @@
33
runs tests
44
"""
55
from datetime import datetime
6-
import six
7-
import logging
86
from numpy import asarray,radians
97
from numpy.testing import assert_allclose, assert_almost_equal,run_module_suite
108
from pytz import UTC
11-
from dateutil.parser import parse
129
#
1310
import pymap3d as pm
1411
from pymap3d.haversine import angledist,angledist_meeus
1512
from pymap3d.vincenty import vreckon,vdist
1613
from pymap3d.datetime2hourangle import datetime2sidereal
14+
from pymap3d.vallado import vazel2radec, vradec2azel
15+
from pymap3d.timeconv import str2dt
16+
17+
def test_str2dt():
18+
19+
assert str2dt('2014-04-06T08:00:00Z') == datetime(2014,4,6,8, tzinfo=UTC)
20+
ti = [str2dt('2014-04-06T08:00:00Z'), str2dt('2014-04-06T08:01:02Z')]
21+
to = [datetime(2014,4,6,8, tzinfo=UTC), datetime(2014,4,6,8,1,2, tzinfo=UTC)]
22+
assert ti == to # even though ti is numpy array of datetime and to is list of datetime
23+
24+
# %%
25+
t = '2014-04-06T08:00:00Z'
26+
rdlat, rdlon = (65, -148)
27+
ra, dec = (166.5032081149338,55.000011165405752)
28+
ha = 45.482789587392013
29+
azi, eli = (180.1,80)
1730

1831
def test_datetime2sidereal():
19-
sdrtest = datetime2sidereal(datetime(2014,4,6,8), radians(-148), False)
20-
assert_allclose(sdrtest,2.9065780550600806,rtol=1e-5)
32+
sdrapy = datetime2sidereal(t, radians(rdlon), False)
33+
assert_allclose(sdrapy, 2.9065780550600806, rtol=1e-5)
34+
35+
sdrvallado = datetime2sidereal(t, radians(rdlon), True)
36+
assert_allclose(sdrvallado, 2.9065780550600806, rtol=1e-5)
37+
2138

2239
def test_azel2radec():
23-
if six.PY2:
24-
logging.error('AstroPy does not support Python 2 for this function. Python 3 was released in 2008.')
25-
return
40+
R,D = pm.azel2radec(azi, eli, rdlat, rdlon, t)
41+
assert_allclose(R, ra, rtol=1e-2)
42+
assert_allclose(D, dec, rtol=1e-2)
43+
44+
Rv, Dv = vazel2radec(azi, eli, rdlat, rdlon, t)
45+
assert_allclose(Rv, ra)
46+
assert_allclose(Dv, dec)
2647

27-
ra,dec = pm.azel2radec(180.1, 80,
28-
65, -148,
29-
'2014-04-06T08:00:00Z')
30-
assert_allclose(ra,166.5032081149338,rtol=1e-2)
31-
assert_allclose(dec,55.000011165405752,rtol=1e-2)
3248

3349
def test_radec2azel():
34-
aztest, eltest = pm.radec2azel(166.5032081149338,55.000011165405752, 65, -148,
35-
parse('2014-04-06T08:00:00'))
36-
assert_allclose(aztest, 179.40287898,rtol=1e-2) #180.1 from vallado, but his is less precise
37-
assert_allclose(eltest, 79.92186504,rtol=1e-2) #80.0 from vallado, but his is less precise
50+
azapy, elapy = pm.radec2azel(ra,dec, rdlat, rdlon, t)
51+
assert_allclose(azapy, azi, rtol=1e-2)
52+
assert_allclose(elapy, eli, rtol=1e-2)
53+
54+
azvallado, elvallado = vradec2azel(ra, dec, rdlat, rdlon, t)
55+
assert_allclose(azvallado, azi, rtol=1e-2)
56+
assert_allclose(elvallado, eli, rtol=1e-2)
57+
3858

3959
def test_haversine():
40-
assert_almost_equal(angledist(35,23,84,20),45.482789587392013)
60+
assert_almost_equal(angledist(35,23, 84,20), ha)
4161
#%% compare with astropy
42-
assert_almost_equal(45.482789587392013, angledist_meeus(35,23, 84,20))
62+
assert_almost_equal(ha, angledist_meeus(35,23, 84,20))
63+
4364

4465
def test_vreckon():
4566
lat2,lon2,a21 = vreckon(10,20,3000,38)

0 commit comments

Comments
 (0)