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.
Files changed (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. 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.