pygeodesy 24.3.24__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- pygeodesy/wgrs.py +439 -0
pygeodesy/formy.py
ADDED
|
@@ -0,0 +1,1818 @@
|
|
|
1
|
+
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
u'''Formulary of basic geodesy functions and approximations.
|
|
5
|
+
'''
|
|
6
|
+
# make sure int/int division yields float quotient, see .basics
|
|
7
|
+
from __future__ import division as _; del _ # PYCHOK semicolon
|
|
8
|
+
|
|
9
|
+
# from pygeodesy.cartesianBase import CartesianBase # _MODS
|
|
10
|
+
from pygeodesy.constants import EPS, EPS0, EPS1, PI, PI2, PI3, PI_2, R_M, \
|
|
11
|
+
_umod_PI2, float0_, isnon0, remainder, \
|
|
12
|
+
_0_0, _0_125, _0_25, _0_5, _1_0, _2_0, _4_0, \
|
|
13
|
+
_32_0, _90_0, _180_0, _360_0
|
|
14
|
+
from pygeodesy.datums import Datum, Ellipsoid, _ellipsoidal_datum, \
|
|
15
|
+
_mean_radius, _spherical_datum, _WGS84, _EWGS84
|
|
16
|
+
# from pygeodesy.ellipsoids import Ellipsoid, _EWGS84 # from .datums
|
|
17
|
+
from pygeodesy.errors import IntersectionError, LimitError, limiterrors, \
|
|
18
|
+
_TypeError, _ValueError, _xattr, _xError, \
|
|
19
|
+
_xkwds, _xkwds_pop2
|
|
20
|
+
from pygeodesy.fmath import euclid, hypot, hypot2, sqrt0
|
|
21
|
+
from pygeodesy.fsums import fsumf_
|
|
22
|
+
from pygeodesy.interns import NN, _delta_, _distant_, _inside_, _SPACE_, _too_
|
|
23
|
+
from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
|
|
24
|
+
from pygeodesy.named import _NamedTuple, _xnamed, Fmt, unstr
|
|
25
|
+
from pygeodesy.namedTuples import Bearing2Tuple, Distance4Tuple, Intersection3Tuple, \
|
|
26
|
+
LatLon2Tuple, PhiLam2Tuple, Vector3Tuple
|
|
27
|
+
# from pygeodesy.streprs import Fmt, unstr # from .named
|
|
28
|
+
# from pygeodesy.triaxials import _hartzell2 # _MODS
|
|
29
|
+
from pygeodesy.units import _isHeight, _isRadius, Bearing, Degrees_, Distance, \
|
|
30
|
+
Distance_, Height, Lam_, Lat, Lon, Meter_, Phi_, \
|
|
31
|
+
Radians, Radians_, Radius, Radius_, Scalar, _100km
|
|
32
|
+
from pygeodesy.utily import acos1, atan2b, atan2d, degrees2m, _loneg, m2degrees, \
|
|
33
|
+
tan_2, sincos2, sincos2_, sincos2d_, _Wrap
|
|
34
|
+
# from pygeodesy.vector3d import _otherV3d # _MODS
|
|
35
|
+
# from pygeodesy.vector3dBase import _xyz_y_z3 # _MODS
|
|
36
|
+
# from pygeodesy import ellipsoidalExact, ellipsoidalKarney, vector3d, \
|
|
37
|
+
# sphericalNvector, sphericalTrigonometry # _MODS
|
|
38
|
+
|
|
39
|
+
from contextlib import contextmanager
|
|
40
|
+
from math import asin, atan, atan2, cos, degrees, fabs, radians, sin, sqrt # pow
|
|
41
|
+
|
|
42
|
+
__all__ = _ALL_LAZY.formy
|
|
43
|
+
__version__ = '24.02.18'
|
|
44
|
+
|
|
45
|
+
_RADIANS2 = (PI / _180_0)**2 # degrees- to radians-squared
|
|
46
|
+
_ratio_ = 'ratio'
|
|
47
|
+
_xline_ = 'xline'
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _anti2(a, b, n_2, n, n2):
|
|
51
|
+
'''(INTERNAL) Helper for C{antipode} and C{antipode_}.
|
|
52
|
+
'''
|
|
53
|
+
r = remainder(a, n) if fabs(a) > n_2 else a
|
|
54
|
+
if r == a:
|
|
55
|
+
r = -r
|
|
56
|
+
b += n
|
|
57
|
+
if fabs(b) > n:
|
|
58
|
+
b = remainder(b, n2)
|
|
59
|
+
return float0_(r, b)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def antipode(lat, lon, name=NN):
|
|
63
|
+
'''Return the antipode, the point diametrically opposite
|
|
64
|
+
to a given point in C{degrees}.
|
|
65
|
+
|
|
66
|
+
@arg lat: Latitude (C{degrees}).
|
|
67
|
+
@arg lon: Longitude (C{degrees}).
|
|
68
|
+
@kwarg name: Optional name (C{str}).
|
|
69
|
+
|
|
70
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)}.
|
|
71
|
+
|
|
72
|
+
@see: Functions L{antipode_} and L{normal} and U{Geosphere
|
|
73
|
+
<https://CRAN.R-Project.org/web/packages/geosphere/geosphere.pdf>}.
|
|
74
|
+
'''
|
|
75
|
+
return LatLon2Tuple(*_anti2(lat, lon, _90_0, _180_0, _360_0), name=name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def antipode_(phi, lam, name=NN):
|
|
79
|
+
'''Return the antipode, the point diametrically opposite
|
|
80
|
+
to a given point in C{radians}.
|
|
81
|
+
|
|
82
|
+
@arg phi: Latitude (C{radians}).
|
|
83
|
+
@arg lam: Longitude (C{radians}).
|
|
84
|
+
@kwarg name: Optional name (C{str}).
|
|
85
|
+
|
|
86
|
+
@return: A L{PhiLam2Tuple}C{(phi, lam)}.
|
|
87
|
+
|
|
88
|
+
@see: Functions L{antipode} and L{normal_} and U{Geosphere
|
|
89
|
+
<https://CRAN.R-Project.org/web/packages/geosphere/geosphere.pdf>}.
|
|
90
|
+
'''
|
|
91
|
+
return PhiLam2Tuple(*_anti2(phi, lam, PI_2, PI, PI2), name=name)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bearing(lat1, lon1, lat2, lon2, **final_wrap):
|
|
95
|
+
'''Compute the initial or final bearing (forward or reverse
|
|
96
|
+
azimuth) between a (spherical) start and end point.
|
|
97
|
+
|
|
98
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
99
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
100
|
+
@arg lat2: End latitude (C{degrees}).
|
|
101
|
+
@arg lon2: End longitude (C{degrees}).
|
|
102
|
+
@kwarg final_wrap: Optional keyword arguments for function
|
|
103
|
+
L{pygeodesy.bearing_}.
|
|
104
|
+
|
|
105
|
+
@return: Initial or final bearing (compass C{degrees360}) or
|
|
106
|
+
zero if start and end point coincide.
|
|
107
|
+
'''
|
|
108
|
+
r = bearing_(Phi_(lat1=lat1), Lam_(lon1=lon1),
|
|
109
|
+
Phi_(lat2=lat2), Lam_(lon2=lon2), **final_wrap)
|
|
110
|
+
return degrees(r)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def bearing_(phi1, lam1, phi2, lam2, final=False, wrap=False):
|
|
114
|
+
'''Compute the initial or final bearing (forward or reverse azimuth)
|
|
115
|
+
between a (spherical) start and end point.
|
|
116
|
+
|
|
117
|
+
@arg phi1: Start latitude (C{radians}).
|
|
118
|
+
@arg lam1: Start longitude (C{radians}).
|
|
119
|
+
@arg phi2: End latitude (C{radians}).
|
|
120
|
+
@arg lam2: End longitude (C{radians}).
|
|
121
|
+
@kwarg final: Return final bearing if C{True}, initial otherwise (C{bool}).
|
|
122
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{phi2}} and
|
|
123
|
+
B{C{lam2}} (C{bool}).
|
|
124
|
+
|
|
125
|
+
@return: Initial or final bearing (compass C{radiansPI2}) or zero if start
|
|
126
|
+
and end point coincide.
|
|
127
|
+
|
|
128
|
+
@see: U{Bearing<https://www.Movable-Type.co.UK/scripts/latlong.html>}, U{Course
|
|
129
|
+
between two points<https://www.EdWilliams.org/avform147.htm#Crs>} and
|
|
130
|
+
U{Bearing Between Two Points<https://web.Archive.org/web/20020630205931/
|
|
131
|
+
https://MathForum.org/library/drmath/view/55417.html>}.
|
|
132
|
+
'''
|
|
133
|
+
db, phi2, lam2 = _Wrap.philam3(lam1, phi2, lam2, wrap)
|
|
134
|
+
if final: # swap plus PI
|
|
135
|
+
phi1, lam1, phi2, lam2, db = phi2, lam2, phi1, lam1, -db
|
|
136
|
+
r = PI3
|
|
137
|
+
else:
|
|
138
|
+
r = PI2
|
|
139
|
+
sa1, ca1, sa2, ca2, sdb, cdb = sincos2_(phi1, phi2, db)
|
|
140
|
+
|
|
141
|
+
x = ca1 * sa2 - sa1 * ca2 * cdb
|
|
142
|
+
y = sdb * ca2
|
|
143
|
+
return _umod_PI2(atan2(y, x) + r) # .utily.wrapPI2
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _bearingTo2(p1, p2, wrap=False): # for points.ispolar, sphericalTrigonometry.areaOf
|
|
147
|
+
'''(INTERNAL) Compute initial and final bearing.
|
|
148
|
+
'''
|
|
149
|
+
try: # for LatLon_ and ellipsoidal LatLon
|
|
150
|
+
return p1.bearingTo2(p2, wrap=wrap)
|
|
151
|
+
except AttributeError:
|
|
152
|
+
pass
|
|
153
|
+
# XXX spherical version, OK for ellipsoidal ispolar?
|
|
154
|
+
t = p1.philam + p2.philam
|
|
155
|
+
return Bearing2Tuple(degrees(bearing_(*t, final=False, wrap=wrap)),
|
|
156
|
+
degrees(bearing_(*t, final=True, wrap=wrap)),
|
|
157
|
+
name=_bearingTo2.__name__)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def compassAngle(lat1, lon1, lat2, lon2, adjust=True, wrap=False):
|
|
161
|
+
'''Return the angle from North for the direction vector M{(lon2 - lon1,
|
|
162
|
+
lat2 - lat1)} between two points.
|
|
163
|
+
|
|
164
|
+
Suitable only for short, not near-polar vectors up to a few hundred
|
|
165
|
+
Km or Miles. Use function L{pygeodesy.bearing} for longer vectors.
|
|
166
|
+
|
|
167
|
+
@arg lat1: From latitude (C{degrees}).
|
|
168
|
+
@arg lon1: From longitude (C{degrees}).
|
|
169
|
+
@arg lat2: To latitude (C{degrees}).
|
|
170
|
+
@arg lon2: To longitude (C{degrees}).
|
|
171
|
+
@kwarg adjust: Adjust the longitudinal delta by the cosine of the
|
|
172
|
+
mean latitude (C{bool}).
|
|
173
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{lat2}}
|
|
174
|
+
and B{C{lon2}} (C{bool}).
|
|
175
|
+
|
|
176
|
+
@return: Compass angle from North (C{degrees360}).
|
|
177
|
+
|
|
178
|
+
@note: Courtesy of Martin Schultz.
|
|
179
|
+
|
|
180
|
+
@see: U{Local, flat earth approximation
|
|
181
|
+
<https://www.EdWilliams.org/avform.htm#flat>}.
|
|
182
|
+
'''
|
|
183
|
+
d_lon, lat2, lon2 = _Wrap.latlon3(lon1, lat2, lon2, wrap)
|
|
184
|
+
if adjust: # scale delta lon
|
|
185
|
+
d_lon *= _scale_deg(lat1, lat2)
|
|
186
|
+
return atan2b(d_lon, lat2 - lat1)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def cosineAndoyerLambert(lat1, lon1, lat2, lon2, datum=_WGS84, wrap=False):
|
|
190
|
+
'''Compute the distance between two (ellipsoidal) points using the U{Andoyer-Lambert
|
|
191
|
+
<https://books.google.com/books?id=x2UiAQAAIAAJ>} correction of the U{Law of
|
|
192
|
+
Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
193
|
+
|
|
194
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
195
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
196
|
+
@arg lat2: End latitude (C{degrees}).
|
|
197
|
+
@arg lon2: End longitude (C{degrees}).
|
|
198
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
199
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
200
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
201
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
202
|
+
|
|
203
|
+
@return: Distance (C{meter}, same units as the B{C{datum}}'s
|
|
204
|
+
ellipsoid axes or C{radians} if B{C{datum}} is C{None}).
|
|
205
|
+
|
|
206
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
207
|
+
|
|
208
|
+
@see: Functions L{cosineAndoyerLambert_}, L{cosineForsytheAndoyerLambert},
|
|
209
|
+
L{cosineLaw}, L{equirectangular}, L{euclidean}, L{flatLocal}/L{hubeny},
|
|
210
|
+
L{flatPolar}, L{haversine}, L{thomas} and L{vincentys} and method
|
|
211
|
+
L{Ellipsoid.distance2}.
|
|
212
|
+
'''
|
|
213
|
+
return _dE(cosineAndoyerLambert_, datum, wrap, lat1, lon1, lat2, lon2)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def cosineAndoyerLambert_(phi2, phi1, lam21, datum=_WGS84):
|
|
217
|
+
'''Compute the I{angular} distance between two (ellipsoidal) points using the U{Andoyer-Lambert
|
|
218
|
+
<https://books.google.com/books?id=x2UiAQAAIAAJ>} correction of the U{Law of
|
|
219
|
+
Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
220
|
+
|
|
221
|
+
@arg phi2: End latitude (C{radians}).
|
|
222
|
+
@arg phi1: Start latitude (C{radians}).
|
|
223
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
224
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
225
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
226
|
+
|
|
227
|
+
@return: Angular distance (C{radians}).
|
|
228
|
+
|
|
229
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
230
|
+
|
|
231
|
+
@see: Functions L{cosineAndoyerLambert}, L{cosineForsytheAndoyerLambert_},
|
|
232
|
+
L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
233
|
+
L{flatPolar_}, L{haversine_}, L{thomas_} and L{vincentys_} and U{Geodesy-PHP
|
|
234
|
+
<https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/Distance/
|
|
235
|
+
AndoyerLambert.php>}.
|
|
236
|
+
'''
|
|
237
|
+
s2, c2, s1, c1, r, c21 = _sincosa6(phi2, phi1, lam21)
|
|
238
|
+
if isnon0(c1) and isnon0(c2):
|
|
239
|
+
E = _ellipsoidal(datum, cosineAndoyerLambert_)
|
|
240
|
+
if E.f: # ellipsoidal
|
|
241
|
+
r2 = atan2(E.b_a * s2, c2)
|
|
242
|
+
r1 = atan2(E.b_a * s1, c1)
|
|
243
|
+
s2, c2, s1, c1 = sincos2_(r2, r1)
|
|
244
|
+
r = acos1(s1 * s2 + c1 * c2 * c21)
|
|
245
|
+
if r:
|
|
246
|
+
sr, _, sr_2, cr_2 = sincos2_(r, r * _0_5)
|
|
247
|
+
if isnon0(sr_2) and isnon0(cr_2):
|
|
248
|
+
s = (sr + r) * ((s1 - s2) / sr_2)**2
|
|
249
|
+
c = (sr - r) * ((s1 + s2) / cr_2)**2
|
|
250
|
+
r += (c - s) * E.f * _0_125
|
|
251
|
+
return r
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def cosineForsytheAndoyerLambert(lat1, lon1, lat2, lon2, datum=_WGS84, wrap=False):
|
|
255
|
+
'''Compute the distance between two (ellipsoidal) points using the U{Forsythe-Andoyer-Lambert
|
|
256
|
+
<https://www2.UNB.Ca/gge/Pubs/TR77.pdf>} correction of the U{Law of Cosines
|
|
257
|
+
<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
258
|
+
|
|
259
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
260
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
261
|
+
@arg lat2: End latitude (C{degrees}).
|
|
262
|
+
@arg lon2: End longitude (C{degrees}).
|
|
263
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
264
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
265
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
266
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
267
|
+
|
|
268
|
+
@return: Distance (C{meter}, same units as the B{C{datum}}'s
|
|
269
|
+
ellipsoid axes or C{radians} if B{C{datum}} is C{None}).
|
|
270
|
+
|
|
271
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
272
|
+
|
|
273
|
+
@see: Functions L{cosineForsytheAndoyerLambert_}, L{cosineAndoyerLambert},
|
|
274
|
+
L{cosineLaw}, L{equirectangular}, L{euclidean}, L{flatLocal}/L{hubeny},
|
|
275
|
+
L{flatPolar}, L{haversine}, L{thomas} and L{vincentys} and method
|
|
276
|
+
L{Ellipsoid.distance2}.
|
|
277
|
+
'''
|
|
278
|
+
return _dE(cosineForsytheAndoyerLambert_, datum, wrap, lat1, lon1, lat2, lon2)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def cosineForsytheAndoyerLambert_(phi2, phi1, lam21, datum=_WGS84):
|
|
282
|
+
'''Compute the I{angular} distance between two (ellipsoidal) points using the
|
|
283
|
+
U{Forsythe-Andoyer-Lambert<https://www2.UNB.Ca/gge/Pubs/TR77.pdf>} correction of
|
|
284
|
+
the U{Law of Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>}
|
|
285
|
+
formula.
|
|
286
|
+
|
|
287
|
+
@arg phi2: End latitude (C{radians}).
|
|
288
|
+
@arg phi1: Start latitude (C{radians}).
|
|
289
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
290
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid to use (L{Ellipsoid},
|
|
291
|
+
L{Ellipsoid2} or L{a_f2Tuple}).
|
|
292
|
+
|
|
293
|
+
@return: Angular distance (C{radians}).
|
|
294
|
+
|
|
295
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
296
|
+
|
|
297
|
+
@see: Functions L{cosineForsytheAndoyerLambert}, L{cosineAndoyerLambert_},
|
|
298
|
+
L{cosineLaw_}, L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
299
|
+
L{flatPolar_}, L{haversine_}, L{thomas_} and L{vincentys_} and U{Geodesy-PHP
|
|
300
|
+
<https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/
|
|
301
|
+
Distance/ForsytheCorrection.php>}.
|
|
302
|
+
'''
|
|
303
|
+
s2, c2, s1, c1, r, _ = _sincosa6(phi2, phi1, lam21)
|
|
304
|
+
if r and isnon0(c1) and isnon0(c2):
|
|
305
|
+
E = _ellipsoidal(datum, cosineForsytheAndoyerLambert_)
|
|
306
|
+
if E.f: # ellipsoidal
|
|
307
|
+
sr, cr, s2r, _ = sincos2_(r, r * 2)
|
|
308
|
+
if isnon0(sr) and fabs(cr) < EPS1:
|
|
309
|
+
s = (s1 + s2)**2 / (1 + cr)
|
|
310
|
+
t = (s1 - s2)**2 / (1 - cr)
|
|
311
|
+
x = s + t
|
|
312
|
+
y = s - t
|
|
313
|
+
|
|
314
|
+
s = 8 * r**2 / sr
|
|
315
|
+
a = 64 * r + s * cr * 2 # 16 * r**2 / tan(r)
|
|
316
|
+
d = 48 * sr + s # 8 * r**2 / tan(r)
|
|
317
|
+
b = -2 * d
|
|
318
|
+
e = 30 * s2r
|
|
319
|
+
c = fsumf_(30 * r, e * _0_5, s * cr) # 8 * r**2 / tan(r)
|
|
320
|
+
t = fsumf_( a * x, e * y**2, b * y, -c * x**2, d * x * y)
|
|
321
|
+
|
|
322
|
+
r += fsumf_(-r * x, 3 * y * sr, t * E.f / _32_0) * E.f * _0_25
|
|
323
|
+
return r
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def cosineLaw(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
327
|
+
'''Compute the distance between two points using the U{spherical Law of Cosines
|
|
328
|
+
<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
329
|
+
|
|
330
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
331
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
332
|
+
@arg lat2: End latitude (C{degrees}).
|
|
333
|
+
@arg lon2: End longitude (C{degrees}).
|
|
334
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
335
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
336
|
+
L{a_f2Tuple}) to use.
|
|
337
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and B{C{lat2}}
|
|
338
|
+
and B{C{lon2}} (C{bool}).
|
|
339
|
+
|
|
340
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
341
|
+
ellipsoid or datum axes).
|
|
342
|
+
|
|
343
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
344
|
+
|
|
345
|
+
@see: Functions L{cosineLaw_}, L{cosineAndoyerLambert},
|
|
346
|
+
L{cosineForsytheAndoyerLambert}, L{equirectangular}, L{euclidean},
|
|
347
|
+
L{flatLocal}/L{hubeny}, L{flatPolar}, L{haversine}, L{thomas} and
|
|
348
|
+
L{vincentys} and method L{Ellipsoid.distance2}.
|
|
349
|
+
|
|
350
|
+
@note: See note at function L{vincentys_}.
|
|
351
|
+
'''
|
|
352
|
+
return _dS(cosineLaw_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def cosineLaw_(phi2, phi1, lam21):
|
|
356
|
+
'''Compute the I{angular} distance between two points using the U{spherical Law of
|
|
357
|
+
Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
|
|
358
|
+
|
|
359
|
+
@arg phi2: End latitude (C{radians}).
|
|
360
|
+
@arg phi1: Start latitude (C{radians}).
|
|
361
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
362
|
+
|
|
363
|
+
@return: Angular distance (C{radians}).
|
|
364
|
+
|
|
365
|
+
@see: Functions L{cosineLaw}, L{cosineAndoyerLambert_},
|
|
366
|
+
L{cosineForsytheAndoyerLambert_}, L{equirectangular_},
|
|
367
|
+
L{euclidean_}, L{flatLocal_}/L{hubeny_}, L{flatPolar_},
|
|
368
|
+
L{haversine_}, L{thomas_} and L{vincentys_}.
|
|
369
|
+
|
|
370
|
+
@note: See note at function L{vincentys_}.
|
|
371
|
+
'''
|
|
372
|
+
return _sincosa6(phi2, phi1, lam21)[4]
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _d3(wrap, lat1, lon1, lat2, lon2):
|
|
376
|
+
'''(INTERNAL) Helper for _dE, _dS and _eA.
|
|
377
|
+
'''
|
|
378
|
+
if wrap:
|
|
379
|
+
d_lon, lat2, _ = _Wrap.latlon3(lon1, lat2, lon2, wrap)
|
|
380
|
+
return radians(lat2), Phi_(lat1=lat1), radians(d_lon)
|
|
381
|
+
else: # for backward compaibility
|
|
382
|
+
return Phi_(lat2=lat2), Phi_(lat1=lat1), Phi_(d_lon=lon2 - lon1)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _dE(func_, earth, *wrap_lls):
|
|
386
|
+
'''(INTERNAL) Helper for ellipsoidal distances.
|
|
387
|
+
'''
|
|
388
|
+
E = _ellipsoidal(earth, func_)
|
|
389
|
+
r = func_(*_d3(*wrap_lls), datum=E)
|
|
390
|
+
return r * E.a
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _dS(func_, radius, *wrap_lls, **adjust):
|
|
394
|
+
'''(INTERNAL) Helper for spherical distances.
|
|
395
|
+
'''
|
|
396
|
+
r = func_(*_d3(*wrap_lls), **adjust)
|
|
397
|
+
if radius is not R_M:
|
|
398
|
+
_, lat1, _, lat2, _ = wrap_lls
|
|
399
|
+
radius = _mean_radius(radius, lat1, lat2)
|
|
400
|
+
return r * radius
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _eA(excess_, radius, *wrap_lls):
|
|
404
|
+
'''(INTERNAL) Helper for spherical excess or area.
|
|
405
|
+
'''
|
|
406
|
+
r = excess_(*_d3(*wrap_lls))
|
|
407
|
+
if radius:
|
|
408
|
+
_, lat1, _, lat2, _ = wrap_lls
|
|
409
|
+
r *= _mean_radius(radius, lat1, lat2)**2
|
|
410
|
+
return r
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _ellipsoidal(earth, where):
|
|
414
|
+
'''(INTERNAL) Helper for distances.
|
|
415
|
+
'''
|
|
416
|
+
return _EWGS84 if earth in (_WGS84, _EWGS84) else (
|
|
417
|
+
earth if isinstance(earth, Ellipsoid) else
|
|
418
|
+
(earth if isinstance(earth, Datum) else # PYCHOK indent
|
|
419
|
+
_ellipsoidal_datum(earth, name=where.__name__)).ellipsoid)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def equirectangular(lat1, lon1, lat2, lon2, radius=R_M, **adjust_limit_wrap):
|
|
423
|
+
'''Compute the distance between two points using the U{Equirectangular Approximation
|
|
424
|
+
/ Projection<https://www.Movable-Type.co.UK/scripts/latlong.html#equirectangular>}.
|
|
425
|
+
|
|
426
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
427
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
428
|
+
@arg lat2: End latitude (C{degrees}).
|
|
429
|
+
@arg lon2: End longitude (C{degrees}).
|
|
430
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
431
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
432
|
+
L{a_f2Tuple}).
|
|
433
|
+
@kwarg adjust_limit_wrap: Optional keyword arguments for
|
|
434
|
+
function L{equirectangular_}.
|
|
435
|
+
|
|
436
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or
|
|
437
|
+
the ellipsoid or datum axes).
|
|
438
|
+
|
|
439
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
440
|
+
|
|
441
|
+
@see: Function L{equirectangular_} for more details, the
|
|
442
|
+
available B{C{options}}, errors, restrictions and other,
|
|
443
|
+
approximate or accurate distance functions.
|
|
444
|
+
'''
|
|
445
|
+
d = sqrt(equirectangular_(Lat(lat1=lat1), Lon(lon1=lon1),
|
|
446
|
+
Lat(lat2=lat2), Lon(lon2=lon2),
|
|
447
|
+
**adjust_limit_wrap).distance2) # PYCHOK 4 vs 2-3
|
|
448
|
+
return degrees2m(d, radius=_mean_radius(radius, lat1, lat2))
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _equirectangular(lat1, lon1, lat2, lon2, **adjust_limit_wrap):
|
|
452
|
+
'''(INTERNAL) Helper for the L{frechet._FrecherMeterRadians}
|
|
453
|
+
and L{hausdorff._HausdorffMeterRedians} classes.
|
|
454
|
+
'''
|
|
455
|
+
return equirectangular_(lat1, lon1, lat2, lon2, **adjust_limit_wrap).distance2 * _RADIANS2
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def equirectangular_(lat1, lon1, lat2, lon2, adjust=True, limit=45, wrap=False):
|
|
459
|
+
'''Compute the distance between two points using the U{Equirectangular Approximation
|
|
460
|
+
/ Projection<https://www.Movable-Type.co.UK/scripts/latlong.html#equirectangular>}.
|
|
461
|
+
|
|
462
|
+
This approximation is valid for short distance of several hundred Km
|
|
463
|
+
or Miles, see the B{C{limit}} keyword argument and L{LimitError}.
|
|
464
|
+
|
|
465
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
466
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
467
|
+
@arg lat2: End latitude (C{degrees}).
|
|
468
|
+
@arg lon2: End longitude (C{degrees}).
|
|
469
|
+
@kwarg adjust: Adjust the wrapped, unrolled longitudinal delta
|
|
470
|
+
by the cosine of the mean latitude (C{bool}).
|
|
471
|
+
@kwarg limit: Optional limit for lat- and longitudinal deltas
|
|
472
|
+
(C{degrees}) or C{None} or C{0} for unlimited.
|
|
473
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{lat2}}
|
|
474
|
+
and B{C{lon2}} (C{bool}).
|
|
475
|
+
|
|
476
|
+
@return: A L{Distance4Tuple}C{(distance2, delta_lat, delta_lon,
|
|
477
|
+
unroll_lon2)} in C{degrees squared}.
|
|
478
|
+
|
|
479
|
+
@raise LimitError: If the lat- and/or longitudinal delta exceeds the
|
|
480
|
+
B{C{-limit..limit}} range and L{pygeodesy.limiterrors}
|
|
481
|
+
set to C{True}.
|
|
482
|
+
|
|
483
|
+
@see: U{Local, flat earth approximation
|
|
484
|
+
<https://www.EdWilliams.org/avform.htm#flat>}, functions
|
|
485
|
+
L{equirectangular}, L{cosineAndoyerLambert},
|
|
486
|
+
L{cosineForsytheAndoyerLambert}, L{cosineLaw}, L{euclidean},
|
|
487
|
+
L{flatLocal}/L{hubeny}, L{flatPolar}, L{haversine}, L{thomas}
|
|
488
|
+
and L{vincentys} and methods L{Ellipsoid.distance2},
|
|
489
|
+
C{LatLon.distanceTo*} and C{LatLon.equirectangularTo}.
|
|
490
|
+
'''
|
|
491
|
+
d_lon, lat2, ulon2 = _Wrap.latlon3(lon1, lat2, lon2, wrap)
|
|
492
|
+
d_lat = lat2 - lat1
|
|
493
|
+
|
|
494
|
+
if limit and limit > 0 and limiterrors():
|
|
495
|
+
d = max(fabs(d_lat), fabs(d_lon))
|
|
496
|
+
if d > limit:
|
|
497
|
+
t = _SPACE_(_delta_, Fmt.PAREN_g(d), Fmt.exceeds_limit(limit))
|
|
498
|
+
s = unstr(equirectangular_, lat1, lon1, lat2, lon2,
|
|
499
|
+
limit=limit, wrap=wrap)
|
|
500
|
+
raise LimitError(s, txt=t)
|
|
501
|
+
|
|
502
|
+
if adjust: # scale delta lon
|
|
503
|
+
d_lon *= _scale_deg(lat1, lat2)
|
|
504
|
+
|
|
505
|
+
d2 = hypot2(d_lat, d_lon) # degrees squared!
|
|
506
|
+
return Distance4Tuple(d2, d_lat, d_lon, ulon2 - lon2)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def euclidean(lat1, lon1, lat2, lon2, radius=R_M, adjust=True, wrap=False):
|
|
510
|
+
'''Approximate the C{Euclidean} distance between two (spherical) points.
|
|
511
|
+
|
|
512
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
513
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
514
|
+
@arg lat2: End latitude (C{degrees}).
|
|
515
|
+
@arg lon2: End longitude (C{degrees}).
|
|
516
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
517
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
518
|
+
L{a_f2Tuple}) to use.
|
|
519
|
+
@kwarg adjust: Adjust the longitudinal delta by the cosine of
|
|
520
|
+
the mean latitude (C{bool}).
|
|
521
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{lat2}}
|
|
522
|
+
and B{C{lon2}} (C{bool}).
|
|
523
|
+
|
|
524
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
525
|
+
ellipsoid or datum axes).
|
|
526
|
+
|
|
527
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
528
|
+
|
|
529
|
+
@see: U{Distance between two (spherical) points
|
|
530
|
+
<https://www.EdWilliams.org/avform.htm#Dist>}, functions L{euclid},
|
|
531
|
+
L{euclidean_}, L{cosineAndoyerLambert}, L{cosineForsytheAndoyerLambert},
|
|
532
|
+
L{cosineLaw}, L{equirectangular}, L{flatLocal}/L{hubeny}, L{flatPolar},
|
|
533
|
+
L{haversine}, L{thomas} and L{vincentys} and methods L{Ellipsoid.distance2},
|
|
534
|
+
C{LatLon.distanceTo*} and C{LatLon.equirectangularTo}.
|
|
535
|
+
'''
|
|
536
|
+
return _dS(euclidean_, radius, wrap, lat1, lon1, lat2, lon2, adjust=adjust)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def euclidean_(phi2, phi1, lam21, adjust=True):
|
|
540
|
+
'''Approximate the I{angular} C{Euclidean} distance between two (spherical) points.
|
|
541
|
+
|
|
542
|
+
@arg phi2: End latitude (C{radians}).
|
|
543
|
+
@arg phi1: Start latitude (C{radians}).
|
|
544
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
545
|
+
@kwarg adjust: Adjust the longitudinal delta by the cosine
|
|
546
|
+
of the mean latitude (C{bool}).
|
|
547
|
+
|
|
548
|
+
@return: Angular distance (C{radians}).
|
|
549
|
+
|
|
550
|
+
@see: Functions L{euclid}, L{euclidean}, L{cosineAndoyerLambert_},
|
|
551
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{equirectangular_},
|
|
552
|
+
L{flatLocal_}/L{hubeny_}, L{flatPolar_}, L{haversine_}, L{thomas_}
|
|
553
|
+
and L{vincentys_}.
|
|
554
|
+
'''
|
|
555
|
+
if adjust:
|
|
556
|
+
lam21 *= _scale_rad(phi2, phi1)
|
|
557
|
+
return euclid(phi2 - phi1, lam21)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def excessAbc_(A, b, c):
|
|
561
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) triangle from two sides
|
|
562
|
+
and the included (small) angle.
|
|
563
|
+
|
|
564
|
+
@arg A: An interior triangle angle (C{radians}).
|
|
565
|
+
@arg b: Frist adjacent triangle side (C{radians}).
|
|
566
|
+
@arg c: Second adjacent triangle side (C{radians}).
|
|
567
|
+
|
|
568
|
+
@return: Spherical excess (C{radians}).
|
|
569
|
+
|
|
570
|
+
@raise UnitError: Invalid B{C{A}}, B{C{b}} or B{C{c}}.
|
|
571
|
+
|
|
572
|
+
@see: Functions L{excessGirard_}, L{excessLHuilier_} and U{Spherical
|
|
573
|
+
trigonometry<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
574
|
+
'''
|
|
575
|
+
A = Radians_(A=A)
|
|
576
|
+
b = Radians_(b=b) * _0_5
|
|
577
|
+
c = Radians_(c=c) * _0_5
|
|
578
|
+
|
|
579
|
+
sA, cA, sb, cb, sc, cc = sincos2_(A, b, c)
|
|
580
|
+
return atan2(sA * sb * sc, cb * cc + cA * sb * sc) * _2_0
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def excessCagnoli_(a, b, c):
|
|
584
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) triangle using U{Cagnoli's
|
|
585
|
+
<https://Zenodo.org/record/35392>} (D.34) formula.
|
|
586
|
+
|
|
587
|
+
@arg a: First triangle side (C{radians}).
|
|
588
|
+
@arg b: Second triangle side (C{radians}).
|
|
589
|
+
@arg c: Third triangle side (C{radians}).
|
|
590
|
+
|
|
591
|
+
@return: Spherical excess (C{radians}).
|
|
592
|
+
|
|
593
|
+
@raise UnitError: Invalid B{C{a}}, B{C{b}} or B{C{c}}.
|
|
594
|
+
|
|
595
|
+
@see: Function L{excessLHuilier_} and U{Spherical trigonometry
|
|
596
|
+
<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
597
|
+
'''
|
|
598
|
+
a = Radians_(a=a)
|
|
599
|
+
b = Radians_(b=b)
|
|
600
|
+
c = Radians_(c=c)
|
|
601
|
+
|
|
602
|
+
s = fsumf_(a, b, c) * _0_5
|
|
603
|
+
_s = sin
|
|
604
|
+
r = _s(s) * _s(s - a) * _s(s - b) * _s(s - c)
|
|
605
|
+
c = cos(a * _0_5) * cos(b * _0_5) * cos(c * _0_5)
|
|
606
|
+
r = asin(sqrt(r) * _0_5 / c) if c and r > 0 else _0_0
|
|
607
|
+
return Radians(Cagnoli=r * _2_0)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def excessGirard_(A, B, C):
|
|
611
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) triangle using U{Girard's
|
|
612
|
+
<https://MathWorld.Wolfram.com/GirardsSphericalExcessFormula.html>} formula.
|
|
613
|
+
|
|
614
|
+
@arg A: First interior triangle angle (C{radians}).
|
|
615
|
+
@arg B: Second interior triangle angle (C{radians}).
|
|
616
|
+
@arg C: Third interior triangle angle (C{radians}).
|
|
617
|
+
|
|
618
|
+
@return: Spherical excess (C{radians}).
|
|
619
|
+
|
|
620
|
+
@raise UnitError: Invalid B{C{A}}, B{C{B}} or B{C{C}}.
|
|
621
|
+
|
|
622
|
+
@see: Function L{excessLHuilier_} and U{Spherical trigonometry
|
|
623
|
+
<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
624
|
+
'''
|
|
625
|
+
return Radians(Girard=fsumf_(Radians_(A=A),
|
|
626
|
+
Radians_(B=B),
|
|
627
|
+
Radians_(C=C), -PI))
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def excessLHuilier_(a, b, c):
|
|
631
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) triangle using U{L'Huilier's
|
|
632
|
+
<https://MathWorld.Wolfram.com/LHuiliersTheorem.html>}'s Theorem.
|
|
633
|
+
|
|
634
|
+
@arg a: First triangle side (C{radians}).
|
|
635
|
+
@arg b: Second triangle side (C{radians}).
|
|
636
|
+
@arg c: Third triangle side (C{radians}).
|
|
637
|
+
|
|
638
|
+
@return: Spherical excess (C{radians}).
|
|
639
|
+
|
|
640
|
+
@raise UnitError: Invalid B{C{a}}, B{C{b}} or B{C{c}}.
|
|
641
|
+
|
|
642
|
+
@see: Function L{excessCagnoli_}, L{excessGirard_} and U{Spherical
|
|
643
|
+
trigonometry<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
644
|
+
'''
|
|
645
|
+
a = Radians_(a=a)
|
|
646
|
+
b = Radians_(b=b)
|
|
647
|
+
c = Radians_(c=c)
|
|
648
|
+
|
|
649
|
+
s = fsumf_(a, b, c) * _0_5
|
|
650
|
+
_t = tan_2
|
|
651
|
+
r = _t(s) * _t(s - a) * _t(s - b) * _t(s - c)
|
|
652
|
+
r = atan(sqrt(r)) if r > 0 else _0_0
|
|
653
|
+
return Radians(LHuilier=r * _4_0)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def excessKarney(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
657
|
+
'''Compute the surface area of a (spherical) quadrilateral bounded by a
|
|
658
|
+
segment of a great circle, two meridians and the equator using U{Karney's
|
|
659
|
+
<https://MathOverflow.net/questions/97711/the-area-of-spherical-polygons>}
|
|
660
|
+
method.
|
|
661
|
+
|
|
662
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
663
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
664
|
+
@arg lat2: End latitude (C{degrees}).
|
|
665
|
+
@arg lon2: End longitude (C{degrees}).
|
|
666
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
667
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
668
|
+
L{a_f2Tuple}) or C{None}.
|
|
669
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
670
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
671
|
+
|
|
672
|
+
@return: Surface area, I{signed} (I{square} C{meter} or the same units as
|
|
673
|
+
B{C{radius}} I{squared}) or the I{spherical excess} (C{radians})
|
|
674
|
+
if C{B{radius}=0} or C{None}.
|
|
675
|
+
|
|
676
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
677
|
+
|
|
678
|
+
@raise UnitError: Invalid B{C{lat2}} or B{C{lat1}}.
|
|
679
|
+
|
|
680
|
+
@raise ValueError: Semi-circular longitudinal delta.
|
|
681
|
+
|
|
682
|
+
@see: Functions L{excessKarney_} and L{excessQuad}.
|
|
683
|
+
'''
|
|
684
|
+
return _eA(excessKarney_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def excessKarney_(phi2, phi1, lam21):
|
|
688
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) quadrilateral bounded by
|
|
689
|
+
a segment of a great circle, two meridians and the equator using U{Karney's
|
|
690
|
+
<https://MathOverflow.net/questions/97711/the-area-of-spherical-polygons>}
|
|
691
|
+
method.
|
|
692
|
+
|
|
693
|
+
@arg phi2: End latitude (C{radians}).
|
|
694
|
+
@arg phi1: Start latitude (C{radians}).
|
|
695
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
696
|
+
|
|
697
|
+
@return: Spherical excess, I{signed} (C{radians}).
|
|
698
|
+
|
|
699
|
+
@raise ValueError: Semi-circular longitudinal delta B{C{lam21}}.
|
|
700
|
+
|
|
701
|
+
@see: Function L{excessKarney} and U{Area of a spherical polygon
|
|
702
|
+
<https://MathOverflow.net/questions/97711/the-area-of-spherical-polygons>}.
|
|
703
|
+
'''
|
|
704
|
+
# from: Veness <https://www.Movable-Type.co.UK/scripts/latlong.html> Area
|
|
705
|
+
# method due to Karney: for each edge of the polygon,
|
|
706
|
+
#
|
|
707
|
+
# tan(Δλ / 2) · (tan(φ1 / 2) + tan(φ2 / 2))
|
|
708
|
+
# tan(E / 2) = -----------------------------------------
|
|
709
|
+
# 1 + tan(φ1 / 2) · tan(φ2 / 2)
|
|
710
|
+
#
|
|
711
|
+
# where E is the spherical excess of the trapezium obtained by extending
|
|
712
|
+
# the edge to the equator-circle vector for each edge (see also ***).
|
|
713
|
+
_t = tan_2
|
|
714
|
+
t2 = _t(phi2)
|
|
715
|
+
t1 = _t(phi1)
|
|
716
|
+
t = _t(lam21, lam21=None)
|
|
717
|
+
return Radians(Karney=atan2(t * (t1 + t2),
|
|
718
|
+
_1_0 + (t1 * t2)) * _2_0)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
# ***) Original post no longer available, following is a copy of the main part
|
|
722
|
+
# <http://OSGeo-org.1560.x6.Nabble.com/Area-of-a-spherical-polygon-td3841625.html>
|
|
723
|
+
#
|
|
724
|
+
# The area of a polygon on a (unit) sphere is given by the spherical excess
|
|
725
|
+
#
|
|
726
|
+
# A = 2 * pi - sum(exterior angles)
|
|
727
|
+
#
|
|
728
|
+
# However this is badly conditioned if the polygon is small. In this case, use
|
|
729
|
+
#
|
|
730
|
+
# A = sum(S12{i, i+1}) over the edges of the polygon
|
|
731
|
+
#
|
|
732
|
+
# where S12 is the area of the quadrilateral bounded by an edge of the polygon,
|
|
733
|
+
# two meridians and the equator, i.e. with vertices (phi1, lambda1), (phi2,
|
|
734
|
+
# lambda2), (0, lambda1) and (0, lambda2). S12 is given by
|
|
735
|
+
#
|
|
736
|
+
# tan(S12 / 2) = tan(lambda21 / 2) * (tan(phi1 / 2) + tan(phi2 / 2)) /
|
|
737
|
+
# (tan(phi1 / 2) * tan(phi2 / 2) + 1)
|
|
738
|
+
#
|
|
739
|
+
# = tan(lambda21 / 2) * tanh((Lamb(phi1) + Lamb(phi2)) / 2)
|
|
740
|
+
#
|
|
741
|
+
# where lambda21 = lambda2 - lambda1 and Lamb(x) is the Lambertian (or the
|
|
742
|
+
# inverse Gudermannian) function
|
|
743
|
+
#
|
|
744
|
+
# Lambertian(x) = asinh(tan(x)) = atanh(sin(x)) = 2 * atanh(tan(x / 2))
|
|
745
|
+
#
|
|
746
|
+
# Notes: The formula for S12 is exact, except that...
|
|
747
|
+
# - it is indeterminate if an edge is a semi-circle
|
|
748
|
+
# - the formula for A applies only if the polygon does not include a pole
|
|
749
|
+
# (if it does, then add +/- 2 * pi to the result)
|
|
750
|
+
# - in the limit of small phi and lambda, S12 reduces to the trapezoidal
|
|
751
|
+
# formula, S12 = (lambda2 - lambda1) * (phi1 + phi2) / 2
|
|
752
|
+
# - I derived this result from the equation for the area of a spherical
|
|
753
|
+
# triangle in terms of two edges and the included angle given by, e.g.
|
|
754
|
+
# U{Todhunter, I. - Spherical Trigonometry (1871), Sec. 103, Eq. (2)
|
|
755
|
+
# <http://Books.Google.com/books?id=3uBHAAAAIAAJ&pg=PA71>}
|
|
756
|
+
# - I would be interested to know if this formula for S12 is already known
|
|
757
|
+
# - Charles Karney
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def excessQuad(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
761
|
+
'''Compute the surface area of a (spherical) quadrilateral bounded by a segment
|
|
762
|
+
of a great circle, two meridians and the equator.
|
|
763
|
+
|
|
764
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
765
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
766
|
+
@arg lat2: End latitude (C{degrees}).
|
|
767
|
+
@arg lon2: End longitude (C{degrees}).
|
|
768
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
769
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
770
|
+
L{a_f2Tuple}) or C{None}.
|
|
771
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
772
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
773
|
+
|
|
774
|
+
@return: Surface area, I{signed} (I{square} C{meter} or the same units as
|
|
775
|
+
B{C{radius}} I{squared}) or the I{spherical excess} (C{radians})
|
|
776
|
+
if C{B{radius}=0} or C{None}.
|
|
777
|
+
|
|
778
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
779
|
+
|
|
780
|
+
@raise UnitError: Invalid B{C{lat2}} or B{C{lat1}}.
|
|
781
|
+
|
|
782
|
+
@see: Function L{excessQuad_} and L{excessKarney}.
|
|
783
|
+
'''
|
|
784
|
+
return _eA(excessQuad_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def excessQuad_(phi2, phi1, lam21):
|
|
788
|
+
'''Compute the I{spherical excess} C{E} of a (spherical) quadrilateral bounded
|
|
789
|
+
by a segment of a great circle, two meridians and the equator.
|
|
790
|
+
|
|
791
|
+
@arg phi2: End latitude (C{radians}).
|
|
792
|
+
@arg phi1: Start latitude (C{radians}).
|
|
793
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
794
|
+
|
|
795
|
+
@return: Spherical excess, I{signed} (C{radians}).
|
|
796
|
+
|
|
797
|
+
@see: Function L{excessQuad} and U{Spherical trigonometry
|
|
798
|
+
<https://WikiPedia.org/wiki/Spherical_trigonometry>}.
|
|
799
|
+
'''
|
|
800
|
+
s = sin((phi2 + phi1) * _0_5)
|
|
801
|
+
c = cos((phi2 - phi1) * _0_5)
|
|
802
|
+
return Radians(Quad=atan2(tan_2(lam21) * s, c) * _2_0)
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def flatLocal(lat1, lon1, lat2, lon2, datum=_WGS84, scaled=True, wrap=False):
|
|
806
|
+
'''Compute the distance between two (ellipsoidal) points using
|
|
807
|
+
the U{ellipsoidal Earth to plane projection<https://WikiPedia.org/
|
|
808
|
+
wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>}
|
|
809
|
+
aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>} formula.
|
|
810
|
+
|
|
811
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
812
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
813
|
+
@arg lat2: End latitude (C{degrees}).
|
|
814
|
+
@arg lon2: End longitude (C{degrees}).
|
|
815
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
816
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
817
|
+
@kwarg scaled: Scale prime_vertical by C{cos(B{phi})} (C{bool}),
|
|
818
|
+
see method L{pygeodesy.Ellipsoid.roc2_}.
|
|
819
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
820
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
821
|
+
|
|
822
|
+
@return: Distance (C{meter}, same units as the B{C{datum}}'s
|
|
823
|
+
ellipsoid axes).
|
|
824
|
+
|
|
825
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
826
|
+
|
|
827
|
+
@note: The meridional and prime_vertical radii of curvature
|
|
828
|
+
are taken and scaled at the mean of both latitude.
|
|
829
|
+
|
|
830
|
+
@see: Functions L{flatLocal_} or L{hubeny_}, L{cosineLaw}, L{flatPolar},
|
|
831
|
+
L{cosineAndoyerLambert}, L{cosineForsytheAndoyerLambert},
|
|
832
|
+
L{equirectangular}, L{euclidean}, L{haversine}, L{thomas},
|
|
833
|
+
L{vincentys}, method L{Ellipsoid.distance2} and U{local, flat
|
|
834
|
+
earth approximation<https://www.EdWilliams.org/avform.htm#flat>}.
|
|
835
|
+
'''
|
|
836
|
+
E = _ellipsoidal(datum, flatLocal)
|
|
837
|
+
return E._hubeny_2(*_d3(wrap, lat1, lon1, lat2, lon2),
|
|
838
|
+
scaled=scaled, squared=False) * E.a
|
|
839
|
+
|
|
840
|
+
hubeny = flatLocal # PYCHOK for Karl Hubeny
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def flatLocal_(phi2, phi1, lam21, datum=_WGS84, scaled=True):
|
|
844
|
+
'''Compute the I{angular} distance between two (ellipsoidal) points using
|
|
845
|
+
the U{ellipsoidal Earth to plane projection<https://WikiPedia.org/
|
|
846
|
+
wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>}
|
|
847
|
+
aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>} formula.
|
|
848
|
+
|
|
849
|
+
@arg phi2: End latitude (C{radians}).
|
|
850
|
+
@arg phi1: Start latitude (C{radians}).
|
|
851
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
852
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
853
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
854
|
+
@kwarg scaled: Scale prime_vertical by C{cos(B{phi})} (C{bool}),
|
|
855
|
+
see method L{pygeodesy.Ellipsoid.roc2_}.
|
|
856
|
+
|
|
857
|
+
@return: Angular distance (C{radians}).
|
|
858
|
+
|
|
859
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
860
|
+
|
|
861
|
+
@note: The meridional and prime_vertical radii of curvature
|
|
862
|
+
are taken and scaled I{at the mean of both latitude}.
|
|
863
|
+
|
|
864
|
+
@see: Functions L{flatLocal} or L{hubeny}, L{cosineAndoyerLambert_},
|
|
865
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_}, L{flatPolar_},
|
|
866
|
+
L{equirectangular_}, L{euclidean_}, L{haversine_}, L{thomas_}
|
|
867
|
+
and L{vincentys_} and U{local, flat earth approximation
|
|
868
|
+
<https://www.EdWilliams.org/avform.htm#flat>}.
|
|
869
|
+
'''
|
|
870
|
+
E = _ellipsoidal(datum, flatLocal_)
|
|
871
|
+
return E._hubeny_2(phi2, phi1, lam21, scaled=scaled, squared=False)
|
|
872
|
+
|
|
873
|
+
hubeny_ = flatLocal_ # PYCHOK for Karl Hubeny
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
def flatPolar(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
877
|
+
'''Compute the distance between two (spherical) points using
|
|
878
|
+
the U{polar coordinate flat-Earth <https://WikiPedia.org/wiki/
|
|
879
|
+
Geographical_distance#Polar_coordinate_flat-Earth_formula>}
|
|
880
|
+
formula.
|
|
881
|
+
|
|
882
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
883
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
884
|
+
@arg lat2: End latitude (C{degrees}).
|
|
885
|
+
@arg lon2: End longitude (C{degrees}).
|
|
886
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
887
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
888
|
+
L{a_f2Tuple}) to use.
|
|
889
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and B{C{lat2}}
|
|
890
|
+
and B{C{lon2}} (C{bool}).
|
|
891
|
+
|
|
892
|
+
@return: Distance (C{meter}, same units as B{C{radius}} or the
|
|
893
|
+
ellipsoid or datum axes).
|
|
894
|
+
|
|
895
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
896
|
+
|
|
897
|
+
@see: Functions L{flatPolar_}, L{cosineAndoyerLambert},
|
|
898
|
+
L{cosineForsytheAndoyerLambert},L{cosineLaw},
|
|
899
|
+
L{flatLocal}/L{hubeny}, L{equirectangular},
|
|
900
|
+
L{euclidean}, L{haversine}, L{thomas} and
|
|
901
|
+
L{vincentys}.
|
|
902
|
+
'''
|
|
903
|
+
return _dS(flatPolar_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
def flatPolar_(phi2, phi1, lam21):
|
|
907
|
+
'''Compute the I{angular} distance between two (spherical) points
|
|
908
|
+
using the U{polar coordinate flat-Earth<https://WikiPedia.org/wiki/
|
|
909
|
+
Geographical_distance#Polar_coordinate_flat-Earth_formula>}
|
|
910
|
+
formula.
|
|
911
|
+
|
|
912
|
+
@arg phi2: End latitude (C{radians}).
|
|
913
|
+
@arg phi1: Start latitude (C{radians}).
|
|
914
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
915
|
+
|
|
916
|
+
@return: Angular distance (C{radians}).
|
|
917
|
+
|
|
918
|
+
@see: Functions L{flatPolar}, L{cosineAndoyerLambert_},
|
|
919
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_},
|
|
920
|
+
L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
921
|
+
L{haversine_}, L{thomas_} and L{vincentys_}.
|
|
922
|
+
'''
|
|
923
|
+
a = fabs(PI_2 - phi1) # co-latitude
|
|
924
|
+
b = fabs(PI_2 - phi2) # co-latitude
|
|
925
|
+
if a < b:
|
|
926
|
+
a, b = b, a
|
|
927
|
+
if a < EPS0:
|
|
928
|
+
a = _0_0
|
|
929
|
+
elif b > 0:
|
|
930
|
+
b = b / a # /= chokes PyChecker
|
|
931
|
+
c = b * cos(lam21) * _2_0
|
|
932
|
+
c = fsumf_(_1_0, b**2, -fabs(c))
|
|
933
|
+
a *= sqrt0(c)
|
|
934
|
+
return a
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def _hartzell(pov, los, earth, **kwds):
|
|
938
|
+
'''(INTERNAL) Helper for C{CartesianBase.hartzell} and C{LatLonBase.hartzell}.
|
|
939
|
+
'''
|
|
940
|
+
if earth is None:
|
|
941
|
+
earth = pov.datum
|
|
942
|
+
else:
|
|
943
|
+
earth = _spherical_datum(earth, name=hartzell.__name__)
|
|
944
|
+
pov = pov.toDatum(earth)
|
|
945
|
+
h = pov.height
|
|
946
|
+
if h < 0: # EPS0
|
|
947
|
+
t = _SPACE_(Fmt.PARENSPACED(height=h), _inside_)
|
|
948
|
+
raise IntersectionError(pov=pov, earth=earth, txt=t)
|
|
949
|
+
return hartzell(pov, los=los, earth=earth, **kwds) if h > 0 else pov # EPS0
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def hartzell(pov, los=False, earth=_WGS84, name=NN, **LatLon_and_kwds):
|
|
953
|
+
'''Compute the intersection of the earth's surface and a Line-Of-Sight from
|
|
954
|
+
a Point-Of-View in space.
|
|
955
|
+
|
|
956
|
+
@arg pov: Point-Of-View outside the earth (C{LatLon}, C{Cartesian},
|
|
957
|
+
L{Ecef9Tuple} or L{Vector3d}).
|
|
958
|
+
@kwarg los: Line-Of-Sight, I{direction} to earth (L{Los}, L{Vector3d}),
|
|
959
|
+
C{True} for the I{normal, plumb} onto the surface or
|
|
960
|
+
C{False} or C{None} to point to the center of the earth.
|
|
961
|
+
@kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2},
|
|
962
|
+
L{a_f2Tuple} or a C{scalar} earth radius in C{meter}).
|
|
963
|
+
@kwarg name: Optional name (C{str}).
|
|
964
|
+
@kwarg LatLon_and_kwds: Optional C{B{LatLon}=None} class to return the
|
|
965
|
+
intersection plus additional C{LatLon} keyword
|
|
966
|
+
arguments, include B{C{datum}} if different
|
|
967
|
+
from B{C{earth}}.
|
|
968
|
+
|
|
969
|
+
@return: The intersection (L{Vector3d}, B{C{pov}}'s C{cartesian type} or
|
|
970
|
+
the given B{C{LatLon}} instance) with attribute C{heigth} set
|
|
971
|
+
to the distance to the B{C{pov}}.
|
|
972
|
+
|
|
973
|
+
@raise IntersectionError: Invalid B{C{pov}} or B{C{pov}} inside the earth or
|
|
974
|
+
invalid B{C{los}} or B{C{los}} points outside or
|
|
975
|
+
away from the earth.
|
|
976
|
+
|
|
977
|
+
@raise TypeError: Invalid B{C{earth}}, C{ellipsoid} or C{datum}.
|
|
978
|
+
|
|
979
|
+
@see: Class L{Los}, functions L{tyr3d} and L{hartzell4} and methods
|
|
980
|
+
L{Ellipsoid.hartzell4} and any C{Cartesian.hartzell} and C{LatLon.hartzell}.
|
|
981
|
+
'''
|
|
982
|
+
n = hartzell.__name__
|
|
983
|
+
D = earth if isinstance(earth, Datum) else _spherical_datum(earth, name=n)
|
|
984
|
+
try:
|
|
985
|
+
r, h, i = _MODS.triaxials._hartzell3(pov, los, D.ellipsoid._triaxial)
|
|
986
|
+
|
|
987
|
+
r = _xnamed(r, name or n)
|
|
988
|
+
C = _MODS.cartesianBase.CartesianBase
|
|
989
|
+
if LatLon_and_kwds:
|
|
990
|
+
c = C(r, datum=D, name=r.name)
|
|
991
|
+
r = c.toLatLon(**_xkwds(LatLon_and_kwds, height=h))
|
|
992
|
+
elif isinstance(r, C):
|
|
993
|
+
r.height = h
|
|
994
|
+
if i:
|
|
995
|
+
r._iteration = i
|
|
996
|
+
except Exception as x:
|
|
997
|
+
raise IntersectionError(pov=pov, los=los, earth=earth, cause=x, **LatLon_and_kwds)
|
|
998
|
+
return r
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def haversine(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
1002
|
+
'''Compute the distance between two (spherical) points using the
|
|
1003
|
+
U{Haversine<https://www.Movable-Type.co.UK/scripts/latlong.html>}
|
|
1004
|
+
formula.
|
|
1005
|
+
|
|
1006
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
1007
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
1008
|
+
@arg lat2: End latitude (C{degrees}).
|
|
1009
|
+
@arg lon2: End longitude (C{degrees}).
|
|
1010
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
1011
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
1012
|
+
L{a_f2Tuple}) to use.
|
|
1013
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
1014
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
1015
|
+
|
|
1016
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
1017
|
+
|
|
1018
|
+
@raise TypeError: Invalid B{C{radius}}.
|
|
1019
|
+
|
|
1020
|
+
@see: U{Distance between two (spherical) points
|
|
1021
|
+
<https://www.EdWilliams.org/avform.htm#Dist>}, functions
|
|
1022
|
+
L{cosineLaw}, L{cosineAndoyerLambert}, L{cosineForsytheAndoyerLambert},
|
|
1023
|
+
L{equirectangular}, L{euclidean}, L{flatLocal}/L{hubeny}, L{flatPolar},
|
|
1024
|
+
L{thomas} and L{vincentys} and methods L{Ellipsoid.distance2},
|
|
1025
|
+
C{LatLon.distanceTo*} and C{LatLon.equirectangularTo}.
|
|
1026
|
+
|
|
1027
|
+
@note: See note at function L{vincentys_}.
|
|
1028
|
+
'''
|
|
1029
|
+
return _dS(haversine_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def haversine_(phi2, phi1, lam21):
|
|
1033
|
+
'''Compute the I{angular} distance between two (spherical) points
|
|
1034
|
+
using the U{Haversine<https://www.Movable-Type.co.UK/scripts/latlong.html>}
|
|
1035
|
+
formula.
|
|
1036
|
+
|
|
1037
|
+
@arg phi2: End latitude (C{radians}).
|
|
1038
|
+
@arg phi1: Start latitude (C{radians}).
|
|
1039
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
1040
|
+
|
|
1041
|
+
@return: Angular distance (C{radians}).
|
|
1042
|
+
|
|
1043
|
+
@see: Functions L{haversine}, L{cosineAndoyerLambert_},
|
|
1044
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_},
|
|
1045
|
+
L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
1046
|
+
L{flatPolar_}, L{thomas_} and L{vincentys_}.
|
|
1047
|
+
|
|
1048
|
+
@note: See note at function L{vincentys_}.
|
|
1049
|
+
'''
|
|
1050
|
+
def _hsin(rad):
|
|
1051
|
+
return sin(rad * _0_5)**2
|
|
1052
|
+
|
|
1053
|
+
h = _hsin(phi2 - phi1) + cos(phi1) * cos(phi2) * _hsin(lam21) # haversine
|
|
1054
|
+
return atan2(sqrt0(h), sqrt0(_1_0 - h)) * _2_0 # == asin(sqrt(h)) * 2
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def heightOf(angle, distance, radius=R_M):
|
|
1058
|
+
'''Determine the height above the (spherical) earth' surface after
|
|
1059
|
+
traveling along a straight line at a given tilt.
|
|
1060
|
+
|
|
1061
|
+
@arg angle: Tilt angle above horizontal (C{degrees}).
|
|
1062
|
+
@arg distance: Distance along the line (C{meter} or same units as
|
|
1063
|
+
B{C{radius}}).
|
|
1064
|
+
@kwarg radius: Optional mean earth radius (C{meter}).
|
|
1065
|
+
|
|
1066
|
+
@return: Height (C{meter}, same units as B{C{distance}} and B{C{radius}}).
|
|
1067
|
+
|
|
1068
|
+
@raise ValueError: Invalid B{C{angle}}, B{C{distance}} or B{C{radius}}.
|
|
1069
|
+
|
|
1070
|
+
@see: U{MultiDop geog_lib.GeogBeamHt<https://GitHub.com/NASA/MultiDop>}
|
|
1071
|
+
(U{Shapiro et al. 2009, JTECH
|
|
1072
|
+
<https://Journals.AMetSoc.org/doi/abs/10.1175/2009JTECHA1256.1>}
|
|
1073
|
+
and U{Potvin et al. 2012, JTECH
|
|
1074
|
+
<https://Journals.AMetSoc.org/doi/abs/10.1175/JTECH-D-11-00019.1>}).
|
|
1075
|
+
'''
|
|
1076
|
+
r = h = Radius(radius)
|
|
1077
|
+
d = fabs(Distance(distance))
|
|
1078
|
+
if d > h:
|
|
1079
|
+
d, h = h, d
|
|
1080
|
+
|
|
1081
|
+
if d > EPS0: # and h > EPS0
|
|
1082
|
+
d = d / h # /= h chokes PyChecker
|
|
1083
|
+
s = sin(Phi_(angle=angle, clip=_180_0))
|
|
1084
|
+
s = fsumf_(_1_0, s * d * _2_0, d**2)
|
|
1085
|
+
if s > 0:
|
|
1086
|
+
return h * sqrt(s) - r
|
|
1087
|
+
|
|
1088
|
+
raise _ValueError(angle=angle, distance=distance, radius=radius)
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
def heightOrthometric(h_ll, N):
|
|
1092
|
+
'''Get the I{orthometric} height B{H}, the height above the geoid, earth surface.
|
|
1093
|
+
|
|
1094
|
+
@arg h_ll: The height above the ellipsoid (C{meter}) or an I{ellipsoidal}
|
|
1095
|
+
location (C{LatLon} with a C{height} or C{h} attribute).
|
|
1096
|
+
@arg N: The I{geoid} height (C{meter}), the height of the geoid above the
|
|
1097
|
+
ellipsoid at the same B{C{h_ll}} location.
|
|
1098
|
+
|
|
1099
|
+
@return: I{Orthometric} height C{B{H} = B{h} - B{N}} (C{meter}, same units
|
|
1100
|
+
as B{C{h}} and B{C{N}}).
|
|
1101
|
+
|
|
1102
|
+
@see: U{Ellipsoid, Geoid, and Othometric Heights<https://www.NGS.NOAA.gov/
|
|
1103
|
+
GEOID/PRESENTATIONS/2007_02_24_CCPS/Roman_A_PLSC2007notes.pdf>}, page
|
|
1104
|
+
6 and module L{pygeodesy.geoids}.
|
|
1105
|
+
'''
|
|
1106
|
+
h = h_ll if _isHeight(h_ll) else _xattr(h_ll, height=_xattr(h_ll, h=0))
|
|
1107
|
+
return Height(H=Height(h=h) - Height(N=N))
|
|
1108
|
+
|
|
1109
|
+
|
|
1110
|
+
def horizon(height, radius=R_M, refraction=False):
|
|
1111
|
+
'''Determine the distance to the horizon from a given altitude
|
|
1112
|
+
above the (spherical) earth.
|
|
1113
|
+
|
|
1114
|
+
@arg height: Altitude (C{meter} or same units as B{C{radius}}).
|
|
1115
|
+
@kwarg radius: Optional mean earth radius (C{meter}).
|
|
1116
|
+
@kwarg refraction: Consider atmospheric refraction (C{bool}).
|
|
1117
|
+
|
|
1118
|
+
@return: Distance (C{meter}, same units as B{C{height}} and B{C{radius}}).
|
|
1119
|
+
|
|
1120
|
+
@raise ValueError: Invalid B{C{height}} or B{C{radius}}.
|
|
1121
|
+
|
|
1122
|
+
@see: U{Distance to horizon<https://www.EdWilliams.org/avform.htm#Horizon>}.
|
|
1123
|
+
'''
|
|
1124
|
+
h, r = Height(height), Radius(radius)
|
|
1125
|
+
if min(h, r) < 0:
|
|
1126
|
+
raise _ValueError(height=height, radius=radius)
|
|
1127
|
+
|
|
1128
|
+
d2 = ((r * 2.415750694528) if refraction else # 2.0 / 0.8279
|
|
1129
|
+
fsumf_(r, r, h)) * h
|
|
1130
|
+
return sqrt0(d2)
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
class _idllmn6(object): # see also .geodesicw._wargs, .latlonBase._toCartesian3, .vector2d._numpy
|
|
1134
|
+
'''(INTERNAL) Helper for C{intersection2} and C{intersections2}.
|
|
1135
|
+
'''
|
|
1136
|
+
@contextmanager # <https://www.Python.org/dev/peps/pep-0343/> Examples
|
|
1137
|
+
def __call__(self, datum, lat1, lon1, lat2, lon2, small, wrap, s, **kwds):
|
|
1138
|
+
try:
|
|
1139
|
+
if wrap:
|
|
1140
|
+
_, lat2, lon2 = _Wrap.latlon3(lon1, lat2, lon2, wrap)
|
|
1141
|
+
kwds = _xkwds(kwds, wrap=wrap) # for _xError
|
|
1142
|
+
m = small if small is _100km else Meter_(small=small)
|
|
1143
|
+
n = (intersections2 if s else intersection2).__name__
|
|
1144
|
+
if datum is None or euclidean(lat1, lon1, lat2, lon2) < m:
|
|
1145
|
+
d, m = None, _MODS.vector3d
|
|
1146
|
+
_i = m._intersects2 if s else m._intersect3d3
|
|
1147
|
+
elif _isRadius(datum) and datum < 0 and not s:
|
|
1148
|
+
d = _spherical_datum(-datum, name=n)
|
|
1149
|
+
m = _MODS.sphericalNvector
|
|
1150
|
+
_i = m.intersection
|
|
1151
|
+
else:
|
|
1152
|
+
d = _spherical_datum(datum, name=n)
|
|
1153
|
+
if d.isSpherical:
|
|
1154
|
+
m = _MODS.sphericalTrigonometry
|
|
1155
|
+
_i = m._intersects2 if s else m._intersect
|
|
1156
|
+
elif d.isEllipsoidal:
|
|
1157
|
+
try:
|
|
1158
|
+
if d.ellipsoid.geodesic:
|
|
1159
|
+
pass
|
|
1160
|
+
m = _MODS.ellipsoidalKarney
|
|
1161
|
+
except ImportError:
|
|
1162
|
+
m = _MODS.ellipsoidalExact
|
|
1163
|
+
_i = m._intersections2 if s else m._intersection3 # ellispoidalBaseDI
|
|
1164
|
+
else:
|
|
1165
|
+
raise _TypeError(datum=datum)
|
|
1166
|
+
yield _i, d, lat2, lon2, m, n
|
|
1167
|
+
|
|
1168
|
+
except (TypeError, ValueError) as x:
|
|
1169
|
+
raise _xError(x, lat1=lat1, lon1=lon1, datum=datum,
|
|
1170
|
+
lat2=lat2, lon2=lon2, small=small, **kwds)
|
|
1171
|
+
|
|
1172
|
+
_idllmn6 = _idllmn6() # PYCHOK singleton
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def intersection2(lat1, lon1, bearing1,
|
|
1176
|
+
lat2, lon2, bearing2, datum=None, wrap=False, small=_100km): # was=True
|
|
1177
|
+
'''I{Conveniently} compute the intersection of two lines each defined
|
|
1178
|
+
by a (geodetic) point and a bearing from North, using either ...
|
|
1179
|
+
|
|
1180
|
+
1) L{vector3d.intersection3d3} for B{C{small}} distances (below 100 Km
|
|
1181
|
+
or about 0.88 degrees) or if I{no} B{C{datum}} is specified, or ...
|
|
1182
|
+
|
|
1183
|
+
2) L{sphericalTrigonometry.intersection} for a spherical B{C{datum}}
|
|
1184
|
+
or a C{scalar B{datum}} representing the earth radius, conventionally
|
|
1185
|
+
in C{meter} or ...
|
|
1186
|
+
|
|
1187
|
+
3) L{sphericalNvector.intersection} if B{C{datum}} is a I{negative}
|
|
1188
|
+
C{scalar}, (negative) earth radius, conventionally in C{meter} or ...
|
|
1189
|
+
|
|
1190
|
+
4) L{ellipsoidalKarney.intersection3} for an ellipsoidal B{C{datum}}
|
|
1191
|
+
and if I{Karney}'s U{geographiclib<https://PyPI.org/project/geographiclib>}
|
|
1192
|
+
is installed, otherwise ...
|
|
1193
|
+
|
|
1194
|
+
5) L{ellipsoidalExact.intersection3}, provided B{C{datum}} is ellipsoidal.
|
|
1195
|
+
|
|
1196
|
+
@arg lat1: Latitude of the first point (C{degrees}).
|
|
1197
|
+
@arg lon1: Longitude of the first point (C{degrees}).
|
|
1198
|
+
@arg bearing1: Bearing at the first point (compass C{degrees}).
|
|
1199
|
+
@arg lat2: Latitude of the second point (C{degrees}).
|
|
1200
|
+
@arg lon2: Longitude of the second point (C{degrees}).
|
|
1201
|
+
@arg bearing2: Bearing at the second point (compass C{degrees}).
|
|
1202
|
+
@kwarg datum: Optional datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
1203
|
+
L{Ellipsoid2} or L{a_f2Tuple}) or C{scalar} earth
|
|
1204
|
+
radius (C{meter}, same units as B{C{radius1}} and
|
|
1205
|
+
B{C{radius2}}) or C{None}.
|
|
1206
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{lat2}}
|
|
1207
|
+
and B{C{lon2}} (C{bool}).
|
|
1208
|
+
@kwarg small: Upper limit for small distances (C{meter}).
|
|
1209
|
+
|
|
1210
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)} with the lat- and
|
|
1211
|
+
longitude of the intersection point.
|
|
1212
|
+
|
|
1213
|
+
@raise IntersectionError: Ambiguous or infinite intersection
|
|
1214
|
+
or colinear, parallel or otherwise
|
|
1215
|
+
non-intersecting lines.
|
|
1216
|
+
|
|
1217
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
1218
|
+
|
|
1219
|
+
@raise UnitError: Invalid B{C{lat1}}, B{C{lon1}}, B{C{bearing1}},
|
|
1220
|
+
B{C{lat2}}, B{C{lon2}} or B{C{bearing2}}.
|
|
1221
|
+
|
|
1222
|
+
@see: Method L{RhumbLine.intersection2}.
|
|
1223
|
+
|
|
1224
|
+
@note: The returned intersections may be near-antipodal.
|
|
1225
|
+
'''
|
|
1226
|
+
b1 = Bearing(bearing1=bearing1)
|
|
1227
|
+
b2 = Bearing(bearing2=bearing2)
|
|
1228
|
+
with _idllmn6(datum, lat1, lon1, lat2, lon2,
|
|
1229
|
+
small, wrap, False, bearing1=b1, bearing2=b2) as t:
|
|
1230
|
+
_i, d, lat2, lon2, m, n = t
|
|
1231
|
+
if d is None:
|
|
1232
|
+
t, _, _ = _i(m.Vector3d(lon1, lat1, 0), b1,
|
|
1233
|
+
m.Vector3d(lon2, lat2, 0), b2, useZ=False)
|
|
1234
|
+
t = LatLon2Tuple(t.y, t.x, name=n)
|
|
1235
|
+
|
|
1236
|
+
else:
|
|
1237
|
+
t = _i(m.LatLon(lat1, lon1, datum=d), b1,
|
|
1238
|
+
m.LatLon(lat2, lon2, datum=d), b2, height=0, wrap=False)
|
|
1239
|
+
if isinstance(t, Intersection3Tuple): # ellipsoidal
|
|
1240
|
+
t, _, _ = t
|
|
1241
|
+
t = LatLon2Tuple(t.lat, t.lon, name=n)
|
|
1242
|
+
return t
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
def intersections2(lat1, lon1, radius1,
|
|
1246
|
+
lat2, lon2, radius2, datum=None, wrap=False, small=_100km): # was=True
|
|
1247
|
+
'''I{Conveniently} compute the intersections of two circles each defined
|
|
1248
|
+
by a (geodetic) center point and a radius, using either ...
|
|
1249
|
+
|
|
1250
|
+
1) L{vector3d.intersections2} for B{C{small}} distances (below 100 Km
|
|
1251
|
+
or about 0.88 degrees) or if I{no} B{C{datum}} is specified, or ...
|
|
1252
|
+
|
|
1253
|
+
2) L{sphericalTrigonometry.intersections2} for a spherical B{C{datum}}
|
|
1254
|
+
or a C{scalar B{datum}} representing the earth radius, conventionally
|
|
1255
|
+
in C{meter} or ...
|
|
1256
|
+
|
|
1257
|
+
3) L{ellipsoidalKarney.intersections2} for an ellipsoidal B{C{datum}}
|
|
1258
|
+
and if I{Karney}'s U{geographiclib<https://PyPI.org/project/geographiclib>}
|
|
1259
|
+
is installed, otherwise ...
|
|
1260
|
+
|
|
1261
|
+
4) L{ellipsoidalExact.intersections2}, provided B{C{datum}} is ellipsoidal.
|
|
1262
|
+
|
|
1263
|
+
@arg lat1: Latitude of the first circle center (C{degrees}).
|
|
1264
|
+
@arg lon1: Longitude of the first circle center (C{degrees}).
|
|
1265
|
+
@arg radius1: Radius of the first circle (C{meter}, conventionally).
|
|
1266
|
+
@arg lat2: Latitude of the second circle center (C{degrees}).
|
|
1267
|
+
@arg lon2: Longitude of the second circle center (C{degrees}).
|
|
1268
|
+
@arg radius2: Radius of the second circle (C{meter}, same units as B{C{radius1}}).
|
|
1269
|
+
@kwarg datum: Optional datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
1270
|
+
L{Ellipsoid2} or L{a_f2Tuple}) or C{scalar} earth
|
|
1271
|
+
radius (C{meter}, same units as B{C{radius1}} and
|
|
1272
|
+
B{C{radius2}}) or C{None}.
|
|
1273
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{lat2}}
|
|
1274
|
+
and B{C{lon2}} (C{bool}).
|
|
1275
|
+
@kwarg small: Upper limit for small distances (C{meter}).
|
|
1276
|
+
|
|
1277
|
+
@return: 2-Tuple of the intersection points, each a
|
|
1278
|
+
L{LatLon2Tuple}C{(lat, lon)}. For abutting circles, the
|
|
1279
|
+
points are the same instance, aka the I{radical center}.
|
|
1280
|
+
|
|
1281
|
+
@raise IntersectionError: Concentric, antipodal, invalid or
|
|
1282
|
+
non-intersecting circles or no
|
|
1283
|
+
convergence.
|
|
1284
|
+
|
|
1285
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
1286
|
+
|
|
1287
|
+
@raise UnitError: Invalid B{C{lat1}}, B{C{lon1}}, B{C{radius1}},
|
|
1288
|
+
B{C{lat2}}, B{C{lon2}} or B{C{radius2}}.
|
|
1289
|
+
'''
|
|
1290
|
+
r1 = Radius_(radius1=radius1)
|
|
1291
|
+
r2 = Radius_(radius2=radius2)
|
|
1292
|
+
with _idllmn6(datum, lat1, lon1, lat2, lon2,
|
|
1293
|
+
small, wrap, True, radius1=r1, radius2=r2) as t:
|
|
1294
|
+
_i, d, lat2, lon2, m, n = t
|
|
1295
|
+
if d is None:
|
|
1296
|
+
r1 = m2degrees(r1, radius=R_M, lat=lat1)
|
|
1297
|
+
r2 = m2degrees(r2, radius=R_M, lat=lat2)
|
|
1298
|
+
|
|
1299
|
+
def _V2T(x, y, _, **unused): # _ == z unused
|
|
1300
|
+
return LatLon2Tuple(y, x, name=n)
|
|
1301
|
+
|
|
1302
|
+
t = _i(m.Vector3d(lon1, lat1, 0), r1,
|
|
1303
|
+
m.Vector3d(lon2, lat2, 0), r2, sphere=False,
|
|
1304
|
+
Vector=_V2T)
|
|
1305
|
+
else:
|
|
1306
|
+
def _LL2T(lat, lon, **unused):
|
|
1307
|
+
return LatLon2Tuple(lat, lon, name=n)
|
|
1308
|
+
|
|
1309
|
+
t = _i(m.LatLon(lat1, lon1, datum=d), r1,
|
|
1310
|
+
m.LatLon(lat2, lon2, datum=d), r2,
|
|
1311
|
+
LatLon=_LL2T, height=0, wrap=False)
|
|
1312
|
+
return t
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
def isantipode(lat1, lon1, lat2, lon2, eps=EPS):
|
|
1316
|
+
'''Check whether two points are I{antipodal}, on diametrically
|
|
1317
|
+
opposite sides of the earth.
|
|
1318
|
+
|
|
1319
|
+
@arg lat1: Latitude of one point (C{degrees}).
|
|
1320
|
+
@arg lon1: Longitude of one point (C{degrees}).
|
|
1321
|
+
@arg lat2: Latitude of the other point (C{degrees}).
|
|
1322
|
+
@arg lon2: Longitude of the other point (C{degrees}).
|
|
1323
|
+
@kwarg eps: Tolerance for near-equality (C{degrees}).
|
|
1324
|
+
|
|
1325
|
+
@return: C{True} if points are antipodal within the
|
|
1326
|
+
B{C{eps}} tolerance, C{False} otherwise.
|
|
1327
|
+
|
|
1328
|
+
@see: Functions L{isantipode_} and L{antipode}.
|
|
1329
|
+
'''
|
|
1330
|
+
return (fabs(lat1 + lat2) <= eps and
|
|
1331
|
+
fabs(lon1 + lon2) <= eps) or _isequalTo(
|
|
1332
|
+
normal(lat1, lon1), antipode(lat2, lon2), eps)
|
|
1333
|
+
|
|
1334
|
+
|
|
1335
|
+
def isantipode_(phi1, lam1, phi2, lam2, eps=EPS):
|
|
1336
|
+
'''Check whether two points are I{antipodal}, on diametrically
|
|
1337
|
+
opposite sides of the earth.
|
|
1338
|
+
|
|
1339
|
+
@arg phi1: Latitude of one point (C{radians}).
|
|
1340
|
+
@arg lam1: Longitude of one point (C{radians}).
|
|
1341
|
+
@arg phi2: Latitude of the other point (C{radians}).
|
|
1342
|
+
@arg lam2: Longitude of the other point (C{radians}).
|
|
1343
|
+
@kwarg eps: Tolerance for near-equality (C{radians}).
|
|
1344
|
+
|
|
1345
|
+
@return: C{True} if points are antipodal within the
|
|
1346
|
+
B{C{eps}} tolerance, C{False} otherwise.
|
|
1347
|
+
|
|
1348
|
+
@see: Functions L{isantipode} and L{antipode_}.
|
|
1349
|
+
'''
|
|
1350
|
+
return (fabs(phi1 + phi2) <= eps and
|
|
1351
|
+
fabs(lam1 + lam2) <= eps) or _isequalTo_(
|
|
1352
|
+
normal_(phi1, lam1), antipode_(phi2, lam2), eps)
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
def _isequalTo(p1, p2, eps=EPS):
|
|
1356
|
+
'''Compare 2 point lat-/lons ignoring C{class}.
|
|
1357
|
+
'''
|
|
1358
|
+
return (fabs(p1.lat - p2.lat) <= eps and
|
|
1359
|
+
fabs(p1.lon - p2.lon) <= eps) if eps else (p1.latlon == p2.latlon)
|
|
1360
|
+
|
|
1361
|
+
|
|
1362
|
+
def _isequalTo_(p1, p2, eps=EPS):
|
|
1363
|
+
'''(INTERNAL) Compare 2 point phi-/lams ignoring C{class}.
|
|
1364
|
+
'''
|
|
1365
|
+
return (fabs(p1.phi - p2.phi) <= eps and
|
|
1366
|
+
fabs(p1.lam - p2.lam) <= eps) if eps else (p1.philam == p2.philam)
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
def isnormal(lat, lon, eps=0):
|
|
1370
|
+
'''Check whether B{C{lat}} I{and} B{C{lon}} are within their
|
|
1371
|
+
respective I{normal} range in C{degrees}.
|
|
1372
|
+
|
|
1373
|
+
@arg lat: Latitude (C{degrees}).
|
|
1374
|
+
@arg lon: Longitude (C{degrees}).
|
|
1375
|
+
@kwarg eps: Optional tolerance C{degrees}).
|
|
1376
|
+
|
|
1377
|
+
@return: C{True} if C{(abs(B{lat}) + B{eps}) <= 90} and
|
|
1378
|
+
C{(abs(B{lon}) + B{eps}) <= 180}, C{False} othwerwise.
|
|
1379
|
+
|
|
1380
|
+
@see: Functions L{isnormal_} and L{normal}.
|
|
1381
|
+
'''
|
|
1382
|
+
return (_90_0 - fabs(lat)) >= eps and _loneg(fabs(lon)) >= eps
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
def isnormal_(phi, lam, eps=0):
|
|
1386
|
+
'''Check whether B{C{phi}} I{and} B{C{lam}} are within their
|
|
1387
|
+
respective I{normal} range in C{radians}.
|
|
1388
|
+
|
|
1389
|
+
@arg phi: Latitude (C{radians}).
|
|
1390
|
+
@arg lam: Longitude (C{radians}).
|
|
1391
|
+
@kwarg eps: Optional tolerance C{radians}).
|
|
1392
|
+
|
|
1393
|
+
@return: C{True} if C{(abs(B{phi}) + B{eps}) <= PI/2} and
|
|
1394
|
+
C{(abs(B{lam}) + B{eps}) <= PI}, C{False} othwerwise.
|
|
1395
|
+
|
|
1396
|
+
@see: Functions L{isnormal} and L{normal_}.
|
|
1397
|
+
'''
|
|
1398
|
+
return (PI_2 - fabs(phi)) >= eps and (PI - fabs(lam)) >= eps
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
def latlon2n_xyz(lat, lon, name=NN):
|
|
1402
|
+
'''Convert lat-, longitude to C{n-vector} (I{normal} to the
|
|
1403
|
+
earth's surface) X, Y and Z components.
|
|
1404
|
+
|
|
1405
|
+
@arg lat: Latitude (C{degrees}).
|
|
1406
|
+
@arg lon: Longitude (C{degrees}).
|
|
1407
|
+
@kwarg name: Optional name (C{str}).
|
|
1408
|
+
|
|
1409
|
+
@return: A L{Vector3Tuple}C{(x, y, z)}.
|
|
1410
|
+
|
|
1411
|
+
@see: Function L{philam2n_xyz}.
|
|
1412
|
+
|
|
1413
|
+
@note: These are C{n-vector} x, y and z components,
|
|
1414
|
+
I{NOT} geocentric ECEF x, y and z coordinates!
|
|
1415
|
+
'''
|
|
1416
|
+
return _2n_xyz(name, *sincos2d_(lat, lon))
|
|
1417
|
+
|
|
1418
|
+
|
|
1419
|
+
def _normal2(a, b, n_2, n, n2):
|
|
1420
|
+
'''(INTERNAL) Helper for C{normal} and C{normal_}.
|
|
1421
|
+
'''
|
|
1422
|
+
if fabs(b) > n:
|
|
1423
|
+
b = remainder(b, n2)
|
|
1424
|
+
if fabs(a) > n_2:
|
|
1425
|
+
r = remainder(a, n)
|
|
1426
|
+
if r != a:
|
|
1427
|
+
a = -r
|
|
1428
|
+
b -= n if b > 0 else -n
|
|
1429
|
+
return float0_(a, b)
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
def normal(lat, lon, name=NN):
|
|
1433
|
+
'''Normalize a lat- I{and} longitude pair in C{degrees}.
|
|
1434
|
+
|
|
1435
|
+
@arg lat: Latitude (C{degrees}).
|
|
1436
|
+
@arg lon: Longitude (C{degrees}).
|
|
1437
|
+
@kwarg name: Optional name (C{str}).
|
|
1438
|
+
|
|
1439
|
+
@return: L{LatLon2Tuple}C{(lat, lon)} with C{abs(lat) <= 90}
|
|
1440
|
+
and C{abs(lon) <= 180}.
|
|
1441
|
+
|
|
1442
|
+
@see: Functions L{normal_} and L{isnormal}.
|
|
1443
|
+
'''
|
|
1444
|
+
return LatLon2Tuple(*_normal2(lat, lon, _90_0, _180_0, _360_0),
|
|
1445
|
+
name=name or normal.__name__)
|
|
1446
|
+
|
|
1447
|
+
|
|
1448
|
+
def normal_(phi, lam, name=NN):
|
|
1449
|
+
'''Normalize a lat- I{and} longitude pair in C{radians}.
|
|
1450
|
+
|
|
1451
|
+
@arg phi: Latitude (C{radians}).
|
|
1452
|
+
@arg lam: Longitude (C{radians}).
|
|
1453
|
+
@kwarg name: Optional name (C{str}).
|
|
1454
|
+
|
|
1455
|
+
@return: L{PhiLam2Tuple}C{(phi, lam)} with C{abs(phi) <= PI/2}
|
|
1456
|
+
and C{abs(lam) <= PI}.
|
|
1457
|
+
|
|
1458
|
+
@see: Functions L{normal} and L{isnormal_}.
|
|
1459
|
+
'''
|
|
1460
|
+
return PhiLam2Tuple(*_normal2(phi, lam, PI_2, PI, PI2),
|
|
1461
|
+
name=name or normal_.__name__)
|
|
1462
|
+
|
|
1463
|
+
|
|
1464
|
+
def _2n_xyz(name, sa, ca, sb, cb):
|
|
1465
|
+
'''(INTERNAL) Helper for C{latlon2n_xyz} and C{philam2n_xyz}.
|
|
1466
|
+
'''
|
|
1467
|
+
# Kenneth Gade eqn 3, but using right-handed
|
|
1468
|
+
# vector x -> 0°E,0°N, y -> 90°E,0°N, z -> 90°N
|
|
1469
|
+
return Vector3Tuple(ca * cb, ca * sb, sa, name=name)
|
|
1470
|
+
|
|
1471
|
+
|
|
1472
|
+
def n_xyz2latlon(x, y, z, name=NN):
|
|
1473
|
+
'''Convert C{n-vector} components to lat- and longitude in C{degrees}.
|
|
1474
|
+
|
|
1475
|
+
@arg x: X component (C{scalar}).
|
|
1476
|
+
@arg y: Y component (C{scalar}).
|
|
1477
|
+
@arg z: Z component (C{scalar}).
|
|
1478
|
+
@kwarg name: Optional name (C{str}).
|
|
1479
|
+
|
|
1480
|
+
@return: A L{LatLon2Tuple}C{(lat, lon)}.
|
|
1481
|
+
|
|
1482
|
+
@see: Function L{n_xyz2philam}.
|
|
1483
|
+
'''
|
|
1484
|
+
return LatLon2Tuple(atan2d(z, hypot(x, y)), atan2d(y, x), name=name)
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def n_xyz2philam(x, y, z, name=NN):
|
|
1488
|
+
'''Convert C{n-vector} components to lat- and longitude in C{radians}.
|
|
1489
|
+
|
|
1490
|
+
@arg x: X component (C{scalar}).
|
|
1491
|
+
@arg y: Y component (C{scalar}).
|
|
1492
|
+
@arg z: Z component (C{scalar}).
|
|
1493
|
+
@kwarg name: Optional name (C{str}).
|
|
1494
|
+
|
|
1495
|
+
@return: A L{PhiLam2Tuple}C{(phi, lam)}.
|
|
1496
|
+
|
|
1497
|
+
@see: Function L{n_xyz2latlon}.
|
|
1498
|
+
'''
|
|
1499
|
+
return PhiLam2Tuple(atan2(z, hypot(x, y)), atan2(y, x), name=name)
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def _opposes(d, m, n, n2):
|
|
1503
|
+
'''(INTERNAL) Helper for C{opposing} and C{opposing_}.
|
|
1504
|
+
'''
|
|
1505
|
+
d = d % n2 # -20 % 360 == 340, -1 % PI2 == PI2 - 1
|
|
1506
|
+
return False if d < m or d > (n2 - m) else (
|
|
1507
|
+
True if (n - m) < d < (n + m) else None)
|
|
1508
|
+
|
|
1509
|
+
|
|
1510
|
+
def opposing(bearing1, bearing2, margin=_90_0):
|
|
1511
|
+
'''Compare the direction of two bearings given in C{degrees}.
|
|
1512
|
+
|
|
1513
|
+
@arg bearing1: First bearing (compass C{degrees}).
|
|
1514
|
+
@arg bearing2: Second bearing (compass C{degrees}).
|
|
1515
|
+
@kwarg margin: Optional, interior angle bracket (C{degrees}).
|
|
1516
|
+
|
|
1517
|
+
@return: C{True} if both bearings point in opposite, C{False} if
|
|
1518
|
+
in similar or C{None} if in I{perpendicular} directions.
|
|
1519
|
+
|
|
1520
|
+
@see: Function L{opposing_}.
|
|
1521
|
+
'''
|
|
1522
|
+
m = Degrees_(margin=margin, low=EPS0, high=_90_0)
|
|
1523
|
+
return _opposes(bearing2 - bearing1, m, _180_0, _360_0)
|
|
1524
|
+
|
|
1525
|
+
|
|
1526
|
+
def opposing_(radians1, radians2, margin=PI_2):
|
|
1527
|
+
'''Compare the direction of two bearings given in C{radians}.
|
|
1528
|
+
|
|
1529
|
+
@arg radians1: First bearing (C{radians}).
|
|
1530
|
+
@arg radians2: Second bearing (C{radians}).
|
|
1531
|
+
@kwarg margin: Optional, interior angle bracket (C{radians}).
|
|
1532
|
+
|
|
1533
|
+
@return: C{True} if both bearings point in opposite, C{False} if
|
|
1534
|
+
in similar or C{None} if in perpendicular directions.
|
|
1535
|
+
|
|
1536
|
+
@see: Function L{opposing}.
|
|
1537
|
+
'''
|
|
1538
|
+
m = Radians_(margin=margin, low=EPS0, high=PI_2)
|
|
1539
|
+
return _opposes(radians2 - radians1, m, PI, PI2)
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def philam2n_xyz(phi, lam, name=NN):
|
|
1543
|
+
'''Convert lat-, longitude to C{n-vector} (I{normal} to the
|
|
1544
|
+
earth's surface) X, Y and Z components.
|
|
1545
|
+
|
|
1546
|
+
@arg phi: Latitude (C{radians}).
|
|
1547
|
+
@arg lam: Longitude (C{radians}).
|
|
1548
|
+
@kwarg name: Optional name (C{str}).
|
|
1549
|
+
|
|
1550
|
+
@return: A L{Vector3Tuple}C{(x, y, z)}.
|
|
1551
|
+
|
|
1552
|
+
@see: Function L{latlon2n_xyz}.
|
|
1553
|
+
|
|
1554
|
+
@note: These are C{n-vector} x, y and z components,
|
|
1555
|
+
I{NOT} geocentric ECEF x, y and z coordinates!
|
|
1556
|
+
'''
|
|
1557
|
+
return _2n_xyz(name, *sincos2_(phi, lam))
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
def _radical2(d, r1, r2): # in .ellipsoidalBaseDI, .sphericalTrigonometry, .vector3d
|
|
1561
|
+
# (INTERNAL) See C{radical2} below
|
|
1562
|
+
# assert d > EPS0
|
|
1563
|
+
r = fsumf_(_1_0, (r1 / d)**2, -(r2 / d)**2) * _0_5
|
|
1564
|
+
return Radical2Tuple(max(_0_0, min(_1_0, r)), r * d)
|
|
1565
|
+
|
|
1566
|
+
|
|
1567
|
+
def radical2(distance, radius1, radius2):
|
|
1568
|
+
'''Compute the I{radical ratio} and I{radical line} of two
|
|
1569
|
+
U{intersecting circles<https://MathWorld.Wolfram.com/
|
|
1570
|
+
Circle-CircleIntersection.html>}.
|
|
1571
|
+
|
|
1572
|
+
The I{radical line} is perpendicular to the axis thru the
|
|
1573
|
+
centers of the circles at C{(0, 0)} and C{(B{distance}, 0)}.
|
|
1574
|
+
|
|
1575
|
+
@arg distance: Distance between the circle centers (C{scalar}).
|
|
1576
|
+
@arg radius1: Radius of the first circle (C{scalar}).
|
|
1577
|
+
@arg radius2: Radius of the second circle (C{scalar}).
|
|
1578
|
+
|
|
1579
|
+
@return: A L{Radical2Tuple}C{(ratio, xline)} where C{0.0 <=
|
|
1580
|
+
ratio <= 1.0} and C{xline} is along the B{C{distance}}.
|
|
1581
|
+
|
|
1582
|
+
@raise IntersectionError: The B{C{distance}} exceeds the sum
|
|
1583
|
+
of B{C{radius1}} and B{C{radius2}}.
|
|
1584
|
+
|
|
1585
|
+
@raise UnitError: Invalid B{C{distance}}, B{C{radius1}} or
|
|
1586
|
+
B{C{radius2}}.
|
|
1587
|
+
|
|
1588
|
+
@see: U{Circle-Circle Intersection
|
|
1589
|
+
<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>}.
|
|
1590
|
+
'''
|
|
1591
|
+
d = Distance_(distance, low=_0_0)
|
|
1592
|
+
r1 = Radius_(radius1=radius1)
|
|
1593
|
+
r2 = Radius_(radius2=radius2)
|
|
1594
|
+
if d > (r1 + r2):
|
|
1595
|
+
raise IntersectionError(distance=d, radius1=r1, radius2=r2,
|
|
1596
|
+
txt=_too_(_distant_))
|
|
1597
|
+
return _radical2(d, r1, r2) if d > EPS0 else \
|
|
1598
|
+
Radical2Tuple(_0_5, _0_0)
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
class Radical2Tuple(_NamedTuple):
|
|
1602
|
+
'''2-Tuple C{(ratio, xline)} of the I{radical} C{ratio} and
|
|
1603
|
+
I{radical} C{xline}, both C{scalar} and C{0.0 <= ratio <= 1.0}
|
|
1604
|
+
'''
|
|
1605
|
+
_Names_ = (_ratio_, _xline_)
|
|
1606
|
+
_Units_ = ( Scalar, Scalar)
|
|
1607
|
+
|
|
1608
|
+
|
|
1609
|
+
def _radistance(inst):
|
|
1610
|
+
'''(INTERNAL) Helper for the L{frechet._FrecherMeterRadians}
|
|
1611
|
+
and L{hausdorff._HausdorffMeterRedians} classes.
|
|
1612
|
+
'''
|
|
1613
|
+
wrap_, kwds_ = _xkwds_pop2(inst._kwds, wrap=False)
|
|
1614
|
+
func_ = inst._func_
|
|
1615
|
+
try: # calling lower-overhead C{func_}
|
|
1616
|
+
func_(0, _0_25, _0_5, **kwds_)
|
|
1617
|
+
wrap_ = _Wrap._philamop(wrap_)
|
|
1618
|
+
except TypeError:
|
|
1619
|
+
return inst.distance
|
|
1620
|
+
|
|
1621
|
+
def _philam(p):
|
|
1622
|
+
try:
|
|
1623
|
+
return p.phi, p.lam
|
|
1624
|
+
except AttributeError: # no .phi or .lam
|
|
1625
|
+
return radians(p.lat), radians(p.lon)
|
|
1626
|
+
|
|
1627
|
+
def _func_wrap(point1, point2):
|
|
1628
|
+
phi1, lam1 = wrap_(*_philam(point1))
|
|
1629
|
+
phi2, lam2 = wrap_(*_philam(point2))
|
|
1630
|
+
return func_(phi2, phi1, lam2 - lam1, **kwds_)
|
|
1631
|
+
|
|
1632
|
+
inst._units = inst._units_
|
|
1633
|
+
return _func_wrap
|
|
1634
|
+
|
|
1635
|
+
|
|
1636
|
+
def _scale_deg(lat1, lat2): # degrees
|
|
1637
|
+
# scale factor cos(mean of lats) for delta lon
|
|
1638
|
+
m = fabs(lat1 + lat2) * _0_5
|
|
1639
|
+
return cos(radians(m)) if m < 90 else _0_0
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
def _scale_rad(phi1, phi2): # radians, by .frechet, .hausdorff, .heights
|
|
1643
|
+
# scale factor cos(mean of phis) for delta lam
|
|
1644
|
+
m = fabs(phi1 + phi2) * _0_5
|
|
1645
|
+
return cos(m) if m < PI_2 else _0_0
|
|
1646
|
+
|
|
1647
|
+
|
|
1648
|
+
def _sincosa6(phi2, phi1, lam21): # [4] in cosineLaw
|
|
1649
|
+
'''(INTERNAL) C{sin}es, C{cos}ines and C{acos}ine.
|
|
1650
|
+
'''
|
|
1651
|
+
s2, c2, s1, c1, _, c21 = sincos2_(phi2, phi1, lam21)
|
|
1652
|
+
return s2, c2, s1, c1, acos1(s1 * s2 + c1 * c2 * c21), c21
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
def thomas(lat1, lon1, lat2, lon2, datum=_WGS84, wrap=False):
|
|
1656
|
+
'''Compute the distance between two (ellipsoidal) points using
|
|
1657
|
+
U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>}
|
|
1658
|
+
formula.
|
|
1659
|
+
|
|
1660
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
1661
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
1662
|
+
@arg lat2: End latitude (C{degrees}).
|
|
1663
|
+
@arg lon2: End longitude (C{degrees}).
|
|
1664
|
+
@kwarg datum: Datum (L{Datum}) or ellipsoid (L{Ellipsoid},
|
|
1665
|
+
L{Ellipsoid2} or L{a_f2Tuple}) to use.
|
|
1666
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
1667
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
1668
|
+
|
|
1669
|
+
@return: Distance (C{meter}, same units as the B{C{datum}}'s
|
|
1670
|
+
ellipsoid axes).
|
|
1671
|
+
|
|
1672
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
1673
|
+
|
|
1674
|
+
@see: Functions L{thomas_}, L{cosineAndoyerLambert}, L{cosineForsytheAndoyerLambert},
|
|
1675
|
+
L{cosineLaw}, L{equirectangular}, L{euclidean}, L{flatLocal}/L{hubeny},
|
|
1676
|
+
L{flatPolar}, L{haversine}, L{vincentys} and method L{Ellipsoid.distance2}.
|
|
1677
|
+
'''
|
|
1678
|
+
return _dE(thomas_, datum, wrap, lat1, lon1, lat2, lon2)
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def thomas_(phi2, phi1, lam21, datum=_WGS84):
|
|
1682
|
+
'''Compute the I{angular} distance between two (ellipsoidal) points using
|
|
1683
|
+
U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>}
|
|
1684
|
+
formula.
|
|
1685
|
+
|
|
1686
|
+
@arg phi2: End latitude (C{radians}).
|
|
1687
|
+
@arg phi1: Start latitude (C{radians}).
|
|
1688
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
1689
|
+
@kwarg datum: Datum or ellipsoid to use (L{Datum}, L{Ellipsoid},
|
|
1690
|
+
L{Ellipsoid2} or L{a_f2Tuple}).
|
|
1691
|
+
|
|
1692
|
+
@return: Angular distance (C{radians}).
|
|
1693
|
+
|
|
1694
|
+
@raise TypeError: Invalid B{C{datum}}.
|
|
1695
|
+
|
|
1696
|
+
@see: Functions L{thomas}, L{cosineAndoyerLambert_},
|
|
1697
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_},
|
|
1698
|
+
L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
1699
|
+
L{flatPolar_}, L{haversine_} and L{vincentys_} and U{Geodesy-PHP
|
|
1700
|
+
<https://GitHub.com/jtejido/geodesy-php/blob/master/src/Geodesy/
|
|
1701
|
+
Distance/ThomasFormula.php>}.
|
|
1702
|
+
'''
|
|
1703
|
+
s2, c2, s1, c1, r, _ = _sincosa6(phi2, phi1, lam21)
|
|
1704
|
+
if r and isnon0(c1) and isnon0(c2):
|
|
1705
|
+
E = _ellipsoidal(datum, thomas_)
|
|
1706
|
+
if E.f:
|
|
1707
|
+
r1 = atan2(E.b_a * s1, c1)
|
|
1708
|
+
r2 = atan2(E.b_a * s2, c2)
|
|
1709
|
+
|
|
1710
|
+
j = (r2 + r1) * _0_5
|
|
1711
|
+
k = (r2 - r1) * _0_5
|
|
1712
|
+
sj, cj, sk, ck, h, _ = sincos2_(j, k, lam21 * _0_5)
|
|
1713
|
+
|
|
1714
|
+
h = fsumf_(sk**2, (ck * h)**2, -(sj * h)**2)
|
|
1715
|
+
u = _1_0 - h
|
|
1716
|
+
if isnon0(u) and isnon0(h):
|
|
1717
|
+
r = atan(sqrt0(h / u)) * 2 # == acos(1 - 2 * h)
|
|
1718
|
+
sr, cr = sincos2(r)
|
|
1719
|
+
if isnon0(sr):
|
|
1720
|
+
u = 2 * (sj * ck)**2 / u
|
|
1721
|
+
h = 2 * (sk * cj)**2 / h
|
|
1722
|
+
x = u + h
|
|
1723
|
+
y = u - h
|
|
1724
|
+
|
|
1725
|
+
s = r / sr
|
|
1726
|
+
e = 4 * s**2
|
|
1727
|
+
d = 2 * cr
|
|
1728
|
+
a = e * d
|
|
1729
|
+
b = 2 * r
|
|
1730
|
+
c = s - (a - d) * _0_5
|
|
1731
|
+
f = E.f * _0_25
|
|
1732
|
+
|
|
1733
|
+
t = fsumf_(a * x, -b * y, c * x**2, -d * y**2, e * x * y)
|
|
1734
|
+
r -= fsumf_(s * x, -y, -t * f * _0_25) * f * sr
|
|
1735
|
+
return r
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def vincentys(lat1, lon1, lat2, lon2, radius=R_M, wrap=False):
|
|
1739
|
+
'''Compute the distance between two (spherical) points using
|
|
1740
|
+
U{Vincenty's<https://WikiPedia.org/wiki/Great-circle_distance>}
|
|
1741
|
+
spherical formula.
|
|
1742
|
+
|
|
1743
|
+
@arg lat1: Start latitude (C{degrees}).
|
|
1744
|
+
@arg lon1: Start longitude (C{degrees}).
|
|
1745
|
+
@arg lat2: End latitude (C{degrees}).
|
|
1746
|
+
@arg lon2: End longitude (C{degrees}).
|
|
1747
|
+
@kwarg radius: Mean earth radius (C{meter}), datum (L{Datum})
|
|
1748
|
+
or ellipsoid (L{Ellipsoid}, L{Ellipsoid2} or
|
|
1749
|
+
L{a_f2Tuple}) to use.
|
|
1750
|
+
@kwarg wrap: If C{True}, wrap or I{normalize} and unroll
|
|
1751
|
+
B{C{lat2}} and B{C{lon2}} (C{bool}).
|
|
1752
|
+
|
|
1753
|
+
@return: Distance (C{meter}, same units as B{C{radius}}).
|
|
1754
|
+
|
|
1755
|
+
@raise UnitError: Invalid B{C{radius}}.
|
|
1756
|
+
|
|
1757
|
+
@see: Functions L{vincentys_}, L{cosineAndoyerLambert},
|
|
1758
|
+
L{cosineForsytheAndoyerLambert},L{cosineLaw}, L{equirectangular},
|
|
1759
|
+
L{euclidean}, L{flatLocal}/L{hubeny}, L{flatPolar},
|
|
1760
|
+
L{haversine} and L{thomas} and methods L{Ellipsoid.distance2},
|
|
1761
|
+
C{LatLon.distanceTo*} and C{LatLon.equirectangularTo}.
|
|
1762
|
+
|
|
1763
|
+
@note: See note at function L{vincentys_}.
|
|
1764
|
+
'''
|
|
1765
|
+
return _dS(vincentys_, radius, wrap, lat1, lon1, lat2, lon2)
|
|
1766
|
+
|
|
1767
|
+
|
|
1768
|
+
def vincentys_(phi2, phi1, lam21):
|
|
1769
|
+
'''Compute the I{angular} distance between two (spherical) points using
|
|
1770
|
+
U{Vincenty's<https://WikiPedia.org/wiki/Great-circle_distance>}
|
|
1771
|
+
spherical formula.
|
|
1772
|
+
|
|
1773
|
+
@arg phi2: End latitude (C{radians}).
|
|
1774
|
+
@arg phi1: Start latitude (C{radians}).
|
|
1775
|
+
@arg lam21: Longitudinal delta, M{end-start} (C{radians}).
|
|
1776
|
+
|
|
1777
|
+
@return: Angular distance (C{radians}).
|
|
1778
|
+
|
|
1779
|
+
@see: Functions L{vincentys}, L{cosineAndoyerLambert_},
|
|
1780
|
+
L{cosineForsytheAndoyerLambert_}, L{cosineLaw_},
|
|
1781
|
+
L{equirectangular_}, L{euclidean_}, L{flatLocal_}/L{hubeny_},
|
|
1782
|
+
L{flatPolar_}, L{haversine_} and L{thomas_}.
|
|
1783
|
+
|
|
1784
|
+
@note: Functions L{vincentys_}, L{haversine_} and L{cosineLaw_}
|
|
1785
|
+
produce equivalent results, but L{vincentys_} is suitable
|
|
1786
|
+
for antipodal points and slightly more expensive (M{3 cos,
|
|
1787
|
+
3 sin, 1 hypot, 1 atan2, 6 mul, 2 add}) than L{haversine_}
|
|
1788
|
+
(M{2 cos, 2 sin, 2 sqrt, 1 atan2, 5 mul, 1 add}) and
|
|
1789
|
+
L{cosineLaw_} (M{3 cos, 3 sin, 1 acos, 3 mul, 1 add}).
|
|
1790
|
+
'''
|
|
1791
|
+
s1, c1, s2, c2, s21, c21 = sincos2_(phi1, phi2, lam21)
|
|
1792
|
+
|
|
1793
|
+
c = c2 * c21
|
|
1794
|
+
x = s1 * s2 + c1 * c
|
|
1795
|
+
y = c1 * s2 - s1 * c
|
|
1796
|
+
return atan2(hypot(c2 * s21, y), x)
|
|
1797
|
+
|
|
1798
|
+
# **) MIT License
|
|
1799
|
+
#
|
|
1800
|
+
# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
|
|
1801
|
+
#
|
|
1802
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
1803
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
1804
|
+
# to deal in the Software without restriction, including without limitation
|
|
1805
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
1806
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
1807
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
1808
|
+
#
|
|
1809
|
+
# The above copyright notice and this permission notice shall be included
|
|
1810
|
+
# in all copies or substantial portions of the Software.
|
|
1811
|
+
#
|
|
1812
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
1813
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1814
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
1815
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
1816
|
+
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
1817
|
+
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
1818
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|