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
@@ -0,0 +1,669 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''A pure Python version of I{Karney}'s C++ class U{GeodesicLineExact
5
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1GeodesicLineExact.html>}.
6
+
7
+ Class L{GeodesicLineExact} follows the naming, methods and return
8
+ values from class C{GeodesicLine} from I{Karney}'s Python U{geographiclib
9
+ <https://GeographicLib.SourceForge.io/1.52/python/index.html>}.
10
+
11
+ Copyright (C) U{Charles Karney<mailto:Karney@Alum.MIT.edu>} (2008-2023)
12
+ and licensed under the MIT/X11 License. For more information, see the
13
+ U{GeographicLib<https://GeographicLib.SourceForge.io>} documentation.
14
+ '''
15
+ # make sure int/int division yields float quotient
16
+ from __future__ import division as _; del _ # PYCHOK semicolon
17
+
18
+ # A copy of comments from Karney's C{GeodesicLineExact.cpp}:
19
+ #
20
+ # This is a reformulation of the geodesic problem. The
21
+ # notation is as follows:
22
+ # - at a general point (no suffix or 1 or 2 as suffix)
23
+ # - phi = latitude
24
+ # - lambda = longitude
25
+ # - beta = latitude on auxiliary sphere
26
+ # - omega = longitude on auxiliary sphere
27
+ # - alpha = azimuth of great circle
28
+ # - sigma = arc length along great circle
29
+ # - s = distance
30
+ # - tau = scaled distance (= sigma at multiples of PI/2)
31
+ # - at northwards equator crossing
32
+ # - beta = phi = 0
33
+ # - omega = lambda = 0
34
+ # - alpha = alpha0
35
+ # - sigma = s = 0
36
+ # - a 12 suffix means a difference, e.g., s12 = s2 - s1.
37
+ # - s and c prefixes mean sin and cos
38
+
39
+ # from pygeodesy.basics import _xinstanceof # _MODS
40
+ from pygeodesy.constants import NAN, _EPSmin, _EPSqrt as _TOL, _0_0, \
41
+ _1_0, _180_0, _2__PI, _copysign_1_0
42
+ from pygeodesy.errors import _xError, _xkwds_get
43
+ from pygeodesy.fsums import fsumf_, fsum1f_
44
+ from pygeodesy.geodesicx.gxbases import _cosSeries, _GeodesicBase, \
45
+ _sincos12, _sin1cos2
46
+ # from pygeodesy.geodesicw import _Intersecant2 # _MODS
47
+ from pygeodesy.interns import NN, _COMMASPACE_
48
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_MODS as _MODS
49
+ from pygeodesy.karney import _around, _atan2d, Caps, GDict, _fix90, \
50
+ _K_2_0, _norm2, _norm180, _sincos2, _sincos2d
51
+ from pygeodesy.props import Property_RO, _update_all
52
+ # from pygeodesy.streprs import pairs # _MODS
53
+ from pygeodesy.utily import atan2d as _atan2d_reverse, sincos2
54
+
55
+ from math import atan2, cos, degrees, fabs, floor, radians, sin
56
+
57
+ __all__ = ()
58
+ __version__ = '24.02.21'
59
+
60
+ _glXs = [] # instances of C{[_]GeodesicLineExact} to be updated
61
+ # underflow guard, we require _TINY * EPS > 0, _TINY + EPS == EPS
62
+ _TINY = _EPSmin
63
+ # assert (_TINY * EPS) > 0 and (_TINY + EPS) == EPS
64
+
65
+
66
+ def _update_glXs(gX): # see GeodesicExact.C4order and -._ef_reset_k2
67
+ '''(INTERNAL) Zap cached/memoized C{Property[_RO]}s of
68
+ any L{GeodesicLineExact} instances tied to the given
69
+ L{GeodesicExact} instance B{C{gX}}.
70
+ '''
71
+ _xGeodesicExact(gX=gX)
72
+ for glX in _glXs: # PYCHOK use weakref?
73
+ if glX._gX is gX:
74
+ _update_all(glX)
75
+
76
+
77
+ def _xGeodesicExact(**gX):
78
+ '''(INTERNAL) Check a L{GeodesicExact} instance.
79
+ '''
80
+ _MODS.basics._xinstanceof(_MODS.geodesicx.GeodesicExact, **gX)
81
+
82
+
83
+ class _GeodesicLineExact(_GeodesicBase):
84
+ '''(INTERNAL) Base class for L{GeodesicLineExact}.
85
+ '''
86
+ _a13 = _s13 = NAN
87
+ # _azi1 = _0_0
88
+ # _cchi1 = NAN
89
+ # _dn1 = NAN
90
+ _gX = None # Exact only
91
+ # _k2 = NAN
92
+ # _lat1 = _lon1 = _0_0
93
+ # _salp0 = _calp0 = NAN
94
+ # _salp1 = _calp1 = NAN
95
+ # _somg1 = _comg1 = NAN
96
+ # _ssig1 = _csig1 = NAN
97
+
98
+ def __init__(self, gX, lat1, lon1, azi1, caps, _debug, *salp1_calp1, **name): # name=NN
99
+ '''(INTERNAL) New C{[_]GeodesicLineExact} instance.
100
+ '''
101
+ _xGeodesicExact(gX=gX)
102
+ Cs = Caps
103
+ if _debug: # PYCHOK no cover
104
+ self._debug |= _debug & Cs._DEBUG_ALL
105
+ # _CapsBase.debug._update(self)
106
+ if salp1_calp1:
107
+ salp1, calp1 = salp1_calp1
108
+ else:
109
+ azi1 = _norm180(azi1)
110
+ # guard against salp0 underflow,
111
+ # also -0 is converted to +0
112
+ salp1, calp1 = _sincos2d(_around(azi1))
113
+ if name: # *args, name=NN): Python3
114
+ name = _xkwds_get(name, name=NN)
115
+ if name:
116
+ self.name = name
117
+
118
+ self._gX = gX # GeodesicExact only
119
+ self._lat1 = lat1 = _fix90(lat1)
120
+ self._lon1 = lon1
121
+ self._azi1 = azi1
122
+ self._salp1 = salp1
123
+ self._calp1 = calp1
124
+ # allow lat, azimuth and unrolling of lon
125
+ self._caps = caps | Cs._LINE
126
+
127
+ sbet1, cbet1 = gX._sinf1cos2d(_around(lat1))
128
+ self._dn1 = gX._dn(sbet1, cbet1)
129
+ # Evaluate alp0 from sin(alp1) * cos(bet1) = sin(alp0), with alp0
130
+ # in [0, pi/2 - |bet1|]. Alt: calp0 = hypot(sbet1, calp1 * cbet1),
131
+ # but the following is slightly better, consider the case salp1 = 0.
132
+ self._salp0, self._calp0 = _sin1cos2(salp1, calp1, sbet1, cbet1)
133
+ self._k2 = self._calp0**2 * gX.ep2
134
+ # Evaluate sig with tan(bet1) = tan(sig1) * cos(alp1).
135
+ # sig = 0 is nearest northward crossing of equator.
136
+ # With bet1 = 0, alp1 = pi/2, we have sig1 = 0 (equatorial line).
137
+ # With bet1 = pi/2, alp1 = -pi, sig1 = pi/2
138
+ # With bet1 = -pi/2, alp1 = 0 , sig1 = -pi/2
139
+ # Evaluate omg1 with tan(omg1) = sin(alp0) * tan(sig1).
140
+ # With alp0 in (0, pi/2], quadrants for sig and omg coincide.
141
+ # No atan2(0,0) ambiguity at poles since cbet1 = +epsilon.
142
+ # With alp0 = 0, omg1 = 0 for alp1 = 0, omg1 = pi for alp1 = pi.
143
+ self._somg1 = sbet1 * self._salp0
144
+ self._comg1 = c = (cbet1 * calp1) if (sbet1 or calp1) else _1_0
145
+ # Without normalization we have schi1 = somg1.
146
+ self._cchi1 = gX.f1 * self._dn1 * c
147
+ self._ssig1, self._csig1 = _norm2(sbet1, c) # sig1 in (-pi, pi]
148
+ # _norm2(somg1, comg1) # no need to normalize!
149
+ # _norm2(schi1?, cchi1) # no need to normalize!
150
+ if not (caps & Cs.LINE_OFF):
151
+ _glXs.append(self)
152
+ # no need to pre-compute other attrs based on _Caps.X. All are
153
+ # Property_RO's, computed once and cached/memoized until reset
154
+ # when C4order is changed or Elliptic function reset is invoked.
155
+
156
+ def __del__(self): # XXX use weakref?
157
+ if _glXs: # may be empty or None
158
+ try: # PYCHOK no cover
159
+ _glXs.remove(self)
160
+ except (TypeError, ValueError):
161
+ pass
162
+ self._gX = None
163
+ # _update_all(self) # throws TypeError during Python 2 cleanup
164
+
165
+ def _update(self, updated, *attrs, **unused):
166
+ if updated:
167
+ _update_all(self, *attrs)
168
+
169
+ @Property_RO
170
+ def a1(self):
171
+ '''Get the I{equatorial arc} (C{degrees}), the arc length between
172
+ the northward equatorial crossing and the first point.
173
+ '''
174
+ return _atan2d(self._ssig1, self._csig1) # or NAN
175
+
176
+ equatorarc = a1
177
+
178
+ @Property_RO
179
+ def a13(self):
180
+ '''Get the arc length to reference point 3 (C{degrees}).
181
+
182
+ @see: Methods L{Arc} and L{SetArc}.
183
+ '''
184
+ return self._a13
185
+
186
+ def Arc(self):
187
+ '''Return the arc length to reference point 3 (C{degrees} or C{NAN}).
188
+
189
+ @see: Method L{SetArc} and property L{a13}.
190
+ '''
191
+ return self.a13
192
+
193
+ def ArcPosition(self, a12, outmask=Caps.STANDARD):
194
+ '''Find the position on the line given B{C{a12}}.
195
+
196
+ @arg a12: Spherical arc length from the first point to the
197
+ second point (C{degrees}).
198
+ @kwarg outmask: Bit-or'ed combination of L{Caps} values specifying
199
+ the quantities to be returned.
200
+
201
+ @return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
202
+ lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
203
+ C{lon1}, C{azi1} and arc length C{a12} always included,
204
+ except when C{a12=NAN}.
205
+
206
+ @note: By default, C{B{outmask}=STANDARD}, meaning thc C{lat1},
207
+ C{lon1}, C{azi1}, C{lat2}, C{lon2}, C{azi2}, C{s12} and
208
+ C{a12} entries are returned, except when C{a12=NAN}.
209
+ '''
210
+ return self._GDictPosition(True, a12, outmask)
211
+
212
+ @Property_RO
213
+ def azi0(self):
214
+ '''Get the I{equatorial azimuth}, the azimuth of this geodesic line
215
+ as it crosses the equator in a northward direction (C{degrees90}).
216
+ '''
217
+ return _atan2d(*self.azi0_sincos2) # or NAN
218
+
219
+ equatorazimuth = azi0
220
+
221
+ @Property_RO
222
+ def azi0_sincos2(self):
223
+ '''Get the sine and cosine of the I{equatorial azimuth} (2-tuple C{(sin, cos)}).
224
+ '''
225
+ return self._salp0, self._calp0
226
+
227
+ @Property_RO
228
+ def azi1(self):
229
+ '''Get the azimuth at the first point (compass C{degrees}).
230
+ '''
231
+ return self._azi1
232
+
233
+ @Property_RO
234
+ def azi1_sincos2(self):
235
+ '''Get the sine and cosine of the first point's azimuth (2-tuple C{(sin, cos)}).
236
+ '''
237
+ return self._salp1, self._calp1
238
+
239
+ @Property_RO
240
+ def _B41(self):
241
+ '''(INTERNAL) Cached/memoized.
242
+ '''
243
+ return _cosSeries(self._C4a, self._ssig1, self._csig1)
244
+
245
+ @Property_RO
246
+ def _C4a(self):
247
+ '''(INTERNAL) Cached/memoized.
248
+ '''
249
+ return self.geodesic._C4f_k2(self._k2)
250
+
251
+ @Property_RO
252
+ def _caps_DISTANCE_IN(self):
253
+ '''(INTERNAL) Get C{Caps.DISTANCE_IN} and C{_OUT}.
254
+ '''
255
+ return self.caps & (Caps.DISTANCE_IN & Caps._OUT_MASK)
256
+
257
+ @Property_RO
258
+ def _D0k2(self):
259
+ '''(INTERNAL) Cached/memoized.
260
+ '''
261
+ return self._eF.cD * _2__PI * self._k2
262
+
263
+ @Property_RO
264
+ def _D1(self):
265
+ '''(INTERNAL) Cached/memoized.
266
+ '''
267
+ return self._eF.deltaD(self._ssig1, self._csig1, self._dn1)
268
+
269
+ def Distance(self):
270
+ '''Return the distance to reference point 3 (C{meter} or C{NAN}).
271
+
272
+ @see: Method L{SetDistance} and property L{s13}.
273
+ '''
274
+ return self.s13
275
+
276
+ @Property_RO
277
+ def _E0b(self):
278
+ '''(INTERNAL) Cached/memoized.
279
+ '''
280
+ return self._eF.cE * _2__PI * self.geodesic.b
281
+
282
+ @Property_RO
283
+ def _E1(self):
284
+ '''(INTERNAL) Cached/memoized.
285
+ '''
286
+ return self._eF.deltaE(self._ssig1, self._csig1, self._dn1)
287
+
288
+ @Property_RO
289
+ def _eF(self):
290
+ '''(INTERNAL) Cached/memoized C{Elliptic} function.
291
+ '''
292
+ # see .gx.GeodesicExact._ef_reset_k2
293
+ return _MODS.elliptic.Elliptic(k2=-self._k2, alpha2=-self.geodesic.ep2)
294
+
295
+ def _GDictPosition(self, arcmode, s12_a12, outmask=Caps.STANDARD): # MCCABE 17
296
+ '''(INTERNAL) Generate a new position along the geodesic.
297
+
298
+ @return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
299
+ lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
300
+ C{lon1}, C{azi1} and arc length C{a12} always included,
301
+ except when C{a12=NAN}.
302
+ '''
303
+
304
+ r = GDict(a12=NAN, s12=NAN) # note both a12 and s12, always
305
+ if not (arcmode or self._caps_DISTANCE_IN): # PYCHOK no cover
306
+ return r # Uninitialized or impossible distance requested
307
+
308
+ Cs = Caps
309
+ if self._debug: # PYCHOK no cover
310
+ outmask |= self._debug & Cs._DEBUG_DIRECT_LINE
311
+ outmask &= self._caps & Cs._OUT_MASK
312
+
313
+ eF = self._eF
314
+ gX = self.geodesic # ._gX
315
+
316
+ if arcmode:
317
+ # s12_a12 is spherical arc length
318
+ E2 = _0_0
319
+ sig12 = radians(s12_a12)
320
+ if _K_2_0:
321
+ ssig12, csig12 = sincos2(sig12) # utily, no NEG0
322
+ else: # PYCHOK no cover
323
+ a = fabs(s12_a12) # 0 <= fabs(_remainder(s12_a12, _180_0)) <= 90
324
+ a -= floor(a / _180_0) * _180_0 # 0 <= 0 < 180
325
+ ssig12 = _0_0 if a == 0 else sin(sig12)
326
+ csig12 = _0_0 if a == 90 else cos(sig12)
327
+ else: # s12_a12 is distance
328
+ t = s12_a12 / self._E0b
329
+ s, c = _sincos2(t) # tau12
330
+ # tau2 = tau1 + tau12
331
+ E2 = -eF.deltaEinv(*_sincos12(-s, c, *self._stau1_ctau1))
332
+ sig12 = fsum1f_(self._E1, -E2, t) # == t - (E2 - E1)
333
+ ssig12, csig12 = _sincos2(sig12)
334
+
335
+ salp0, calp0 = self._salp0, self._calp0
336
+ ssig1, csig1 = self._ssig1, self._csig1
337
+
338
+ # sig2 = sig1 + sig12
339
+ ssig2, csig2 = _sincos12(-ssig12, csig12, ssig1, csig1)
340
+ dn2 = eF.fDelta(ssig2, csig2)
341
+ # sin(bet2) = cos(alp0) * sin(sig2) and
342
+ # cbet2 = hypot(salp0, calp0 * csig2). Alt:
343
+ # cbet2 = hypot(csig2, salp0 * ssig2)
344
+ sbet2, cbet2 = _sin1cos2(calp0, salp0, csig2, ssig2)
345
+ if cbet2 == 0: # salp0 = 0, csig2 = 0, break degeneracy
346
+ cbet2 = csig2 = _TINY
347
+ # tan(alp0) = cos(sig2) * tan(alp2)
348
+ salp2 = salp0
349
+ calp2 = calp0 * csig2 # no need to normalize
350
+
351
+ if (outmask & Cs.DISTANCE):
352
+ if arcmode: # or f_0_01
353
+ E2 = eF.deltaE(ssig2, csig2, dn2)
354
+ # AB1 = _E0 * (E2 - _E1)
355
+ # s12 = _b * (_E0 * sig12 + AB1)
356
+ # = _b * _E0 * (sig12 + (E2 - _E1))
357
+ # = _b * _E0 * (E2 - _E1 + sig12)
358
+ s12 = self._E0b * fsum1f_(E2, -self._E1, sig12)
359
+ else:
360
+ s12 = s12_a12
361
+ r.set_(s12=s12)
362
+
363
+ if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
364
+ r.set_(sig12=sig12, dn2=dn2, b=gX.b, e2=gX.e2, f1=gX.f1,
365
+ E0b=self._E0b, E1=self._E1, E2=E2, eFk2=eF.k2, eFa2=eF.alpha2)
366
+
367
+ if (outmask & Cs.LONGITUDE):
368
+ schi1 = self._somg1
369
+ cchi1 = self._cchi1
370
+ schi2 = ssig2 * salp0
371
+ cchi2 = gX.f1 * dn2 * csig2 # schi2 = somg2 without normalization
372
+ lam12 = salp0 * self._H0e2_f1 * fsum1f_(eF.deltaH(ssig2, csig2, dn2),
373
+ -self._H1, sig12)
374
+ if (outmask & Cs.LONG_UNROLL):
375
+ _a, t = atan2, _copysign_1_0(salp0) # east-going?
376
+ tchi1 = t * schi1
377
+ tchi2 = t * schi2
378
+ chi12 = t * fsum1f_(_a(ssig1, csig1), -_a(ssig2, csig2),
379
+ _a(tchi2, cchi2), -_a(tchi1, cchi1), sig12)
380
+ lon2 = self.lon1 + degrees(chi12 - lam12)
381
+ else:
382
+ chi12 = atan2(*_sincos12(schi1, cchi1, schi2, cchi2))
383
+ lon2 = _norm180(self._lon1_norm180 + _norm180(degrees(chi12 - lam12)))
384
+ r.set_(lon2=lon2)
385
+ if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
386
+ r.set_(ssig2=ssig2, chi12=chi12, H0e2_f1=self._H0e2_f1,
387
+ csig2=csig2, lam12=lam12, H1=self._H1)
388
+
389
+ if (outmask & Cs.LATITUDE):
390
+ r.set_(lat2=_atan2d(sbet2, gX.f1 * cbet2))
391
+
392
+ if (outmask & Cs.AZIMUTH):
393
+ r.set_(azi2=_atan2d_reverse(salp2, calp2, reverse=outmask & Cs.REVERSE2))
394
+
395
+ if (outmask & Cs._REDUCEDLENGTH_GEODESICSCALE):
396
+ dn1 = self._dn1
397
+ J12 = self._D0k2 * fsumf_(eF.deltaD(ssig2, csig2, dn2), -self._D1, sig12)
398
+ if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
399
+ r.set_(ssig1=ssig1, dn1=dn1, D0k2=self._D0k2,
400
+ csig1=csig1, J12=J12, D1=self._D1)
401
+ if (outmask & Cs.REDUCEDLENGTH):
402
+ # Add parens around (csig1 * ssig2) and (ssig1 * csig2) to
403
+ # ensure accurate cancellation in the case of coincident points.
404
+ r.set_(m12=gX.b * fsum1f_(dn2 * (csig1 * ssig2),
405
+ -dn1 * (ssig1 * csig2),
406
+ -J12 * (csig1 * csig2)))
407
+ if (outmask & Cs.GEODESICSCALE):
408
+ t = self._k2 * (ssig2 - ssig1) * (ssig2 + ssig1) / (dn2 + dn1)
409
+ r.set_(M12=csig12 + ssig1 * (t * ssig2 - csig2 * J12) / dn1,
410
+ M21=csig12 - ssig2 * (t * ssig1 - csig1 * J12) / dn2)
411
+
412
+ if (outmask & Cs.AREA):
413
+ A4 = salp0 * calp0
414
+ if A4:
415
+ # tan(alp) = tan(alp0) * sec(sig)
416
+ # tan(alp2-alp1) = (tan(alp2) - tan(alp1)) / (tan(alp2) * tan(alp1) + 1)
417
+ # = calp0 * salp0 * (csig1 - csig2) / (salp0^2 + calp0^2 * csig1 * csig2)
418
+ # If csig12 > 0, write
419
+ # csig1 - csig2 = ssig12 * (csig1 * ssig12 / (1 + csig12) + ssig1)
420
+ # else
421
+ # csig1 - csig2 = csig1 * (1 - csig12) + ssig12 * ssig1
422
+ # No need to normalize
423
+ salp12 = (((ssig12 * csig1 / (_1_0 + csig12) + ssig1) * ssig12) if csig12 > 0 else
424
+ (csig1 * (_1_0 - csig12) + ssig1 * ssig12)) * A4
425
+ calp12 = salp0**2 + calp0**2 * csig1 * csig2
426
+ A4 *= gX._e2a2
427
+ B41 = self._B41
428
+ B42 = _cosSeries(self._C4a, ssig2, csig2)
429
+ S12 = (B42 - B41) * A4
430
+ else:
431
+ S12 = A4 = B41 = B42 = _0_0
432
+ # alp12 = alp2 - alp1, used in atan2 so no need to normalize
433
+ salp12, calp12 = _sincos12(self._salp1, self._calp1, salp2, calp2)
434
+ # We used to include some patch up code that purported to deal
435
+ # with nearly meridional geodesics properly. However, this turned
436
+ # out to be wrong once salp1 = -0 was allowed (via InverseLine).
437
+ # In fact, the calculation of {s,c}alp12 was already correct
438
+ # (following the IEEE rules for handling signed zeros). So,
439
+ # the patch up code was unnecessary (as well as dangerous).
440
+ if (outmask & Cs._DEBUG_DIRECT_LINE): # PYCHOK no cover
441
+ r.set_(salp12=salp12, salp0=salp0, B41=B41, A4=A4,
442
+ calp12=calp12, calp0=calp0, B42=B42, c2=gX.c2)
443
+ S12 += gX.c2 * atan2(salp12, calp12)
444
+ r.set_(S12=S12)
445
+
446
+ r.set_(a12=s12_a12 if arcmode else degrees(sig12),
447
+ lat1=self.lat1, # == _fix90(lat1)
448
+ lon1=self.lon1 if (outmask & Cs.LONG_UNROLL) else self._lon1_norm180,
449
+ azi1=_norm180(self.azi1))
450
+ return r
451
+
452
+ def _GenPosition(self, arcmode, s12_a12, outmask):
453
+ '''(INTERNAL) Generate a new position along the geodesic.
454
+
455
+ @return: L{Direct9Tuple}C{(a12, lat2, lon2, azi2,
456
+ s12, m12, M12, M21, S12)}.
457
+ '''
458
+ r = self._GDictPosition(arcmode, s12_a12, outmask)
459
+ return r.toDirect9Tuple()
460
+
461
+ def _GenSet(self, arcmode, s13_a13):
462
+ '''(INTERNAL) Aka C++ C{GenSetDistance}.
463
+ '''
464
+ if arcmode:
465
+ self.SetArc(s13_a13)
466
+ else:
467
+ self.SetDistance(s13_a13)
468
+ return self # for gx.GeodesicExact.InverseLine and -._GenDirectLine
469
+
470
+ @Property_RO
471
+ def geodesic(self):
472
+ '''Get the I{exact} geodesic (L{GeodesicExact}).
473
+ '''
474
+ _xGeodesicExact(geodesic=self._gX)
475
+ return self._gX
476
+
477
+ def Intersecant2(self, lat0, lon0, radius, tol=_TOL):
478
+ '''Compute the intersection(s) of this geodesic line and a circle.
479
+
480
+ @arg lat0: Latitude of the circle center (C{degrees}).
481
+ @arg lon0: Longitude of the circle center (C{degrees}).
482
+ @arg radius: Radius of the circle (C{meter}, conventionally).
483
+ @kwarg tol: Convergence tolerance (C{scalar}).
484
+
485
+ @return: 2-Tuple C{(P, Q)} with both intersections (representing
486
+ a geodesic chord), each a L{GDict} from method L{Position}
487
+ extended to 14 items by C{lon0, lat0, azi0, a02, s02, at}
488
+ with the circle center C{lat0}, C{lon0}, azimuth C{azi0}
489
+ at, distance C{a02} in C{degrees} and C{s02} in C{meter}
490
+ along the geodesic from the circle center to the intersection
491
+ C{lat2}, C{lon2} and the angle C{at} between the geodesic
492
+ and this line at the intersection. The geodesic azimuth
493
+ at the intersection is C{(at + azi2)}. If this geodesic
494
+ line is tangential to the circle, both points are the same
495
+ L{GDict} instance.
496
+
497
+ @raise IntersectionError: The circle and this geodesic line do not
498
+ intersect, no I{perpencular} geodetic
499
+ intersection or no convergence.
500
+
501
+ @raise UnitError: Invalid B{C{radius}}.
502
+ '''
503
+ try:
504
+ return _MODS.geodesicw._Intersecant2(self, lat0, lon0, radius, tol=tol)
505
+ except (TypeError, ValueError) as x:
506
+ raise _xError(x, lat0, lon0, radius, tol=_TOL)
507
+
508
+ @Property_RO
509
+ def _H0e2_f1(self):
510
+ '''(INTERNAL) Cached/memoized.
511
+ '''
512
+ return self._eF.cH * _2__PI * self.geodesic._e2_f1
513
+
514
+ @Property_RO
515
+ def _H1(self):
516
+ '''(INTERNAL) Cached/memoized.
517
+ '''
518
+ return self._eF.deltaH(self._ssig1, self._csig1, self._dn1)
519
+
520
+ @Property_RO
521
+ def lat1(self):
522
+ '''Get the latitude of the first point (C{degrees}).
523
+ '''
524
+ return self._lat1
525
+
526
+ @Property_RO
527
+ def lon1(self):
528
+ '''Get the longitude of the first point (C{degrees}).
529
+ '''
530
+ return self._lon1
531
+
532
+ @Property_RO
533
+ def _lon1_norm180(self):
534
+ '''(INTERNAL) Cached/memoized.
535
+ '''
536
+ return _norm180(self._lon1)
537
+
538
+ def PlumbTo(self, lat0, lon0, est=None, tol=_TOL):
539
+ '''Compute the I{perpendicular} intersection of this geodesic line
540
+ and a geodesic from the given point.
541
+
542
+ @arg lat0: Latitude of the point (C{degrees}).
543
+ @arg lon0: Longitude of the point (C{degrees}).
544
+ @kwarg est: Optional, initial estimate for the distance C{s12} of
545
+ the intersection I{along} this geodesic line (C{meter}).
546
+ @kwarg tol: Convergence tolerance (C(meter)).
547
+
548
+ @return: The intersection point on this geodesic line, a L{GDict}
549
+ from method L{Position} extended to 14 items C{lat1, lon1,
550
+ azi1, lat2, lon2, azi2, a12, s12, lat0, lon0, azi0, a02,
551
+ s02, at} with distance C{a02} in C{degrees} and C{s02} in
552
+ C{meter} between the given C{lat0, lon0} point and the
553
+ intersection C{lat2, lon2}, azimuth C{azi0} at the given
554
+ point and C{at} the (perpendicular) angle between the
555
+ geodesic and this line at the intersection. The geodesic
556
+ azimuth at the intersection is C{(at + azi2)}. See method
557
+ L{Position} for further details.
558
+
559
+ @see: Methods C{Intersecant2}, C{Intersection} and C{Position}.
560
+ '''
561
+ return _MODS.geodesicw._PlumbTo(self, lat0, lon0, est=est, tol=tol)
562
+
563
+ def Position(self, s12, outmask=Caps.STANDARD):
564
+ '''Find the position on the line given B{C{s12}}.
565
+
566
+ @arg s12: Distance from this this line's first point (C{meter}).
567
+ @kwarg outmask: Bit-or'ed combination of L{Caps} values specifying
568
+ the quantities to be returned.
569
+
570
+ @return: A L{GDict} with up to 12 items C{lat1, lon1, azi1, lat2,
571
+ lon2, azi2, m12, a12, s12, M12, M21, S12} with C{lat1},
572
+ C{lon1}, C{azi1} and arc length C{a12} always included,
573
+ except when C{a12=NAN}.
574
+
575
+ @note: By default, C{B{outmask}=STANDARD}, meaning thc C{lat1},
576
+ C{lon1}, C{azi1}, C{lat2}, C{lon2}, C{azi2}, C{s12} and
577
+ C{a12} entries are returned, except when C{a12=NAN}.
578
+
579
+ @note: This L{GeodesicLineExact} instance must have been
580
+ constructed with capability C{Caps.DISTANCE_IN} set.
581
+ '''
582
+ return self._GDictPosition(False, s12, outmask)
583
+
584
+ @Property_RO
585
+ def s13(self):
586
+ '''Get the distance to reference point 3 (C{meter} or C{NAN}).
587
+
588
+ @see: Methods L{Distance} and L{SetDistance}.
589
+ '''
590
+ return self._s13
591
+
592
+ def SetArc(self, a13):
593
+ '''Set reference point 3 in terms relative to the first point.
594
+
595
+ @arg a13: Spherical arc length from the first to the reference
596
+ point (C{degrees}).
597
+
598
+ @return: The distance C{s13} (C{meter}) between the first and
599
+ the reference point or C{NAN}.
600
+ '''
601
+ if self._a13 != a13:
602
+ self._a13 = a13
603
+ self._s13 = self._GDictPosition(True, a13, Caps.DISTANCE).s12 # if a13 else _0_0
604
+ _update_all(self)
605
+ return self._s13
606
+
607
+ def SetDistance(self, s13):
608
+ '''Set reference point 3 in terms relative to the first point.
609
+
610
+ @arg s13: Distance from the first to the reference point (C{meter}).
611
+
612
+ @return: The arc length C{a13} (C{degrees}) between the first
613
+ and the reference point or C{NAN}.
614
+ '''
615
+ if self._s13 != s13:
616
+ self._s13 = s13
617
+ self._a13 = self._GDictPosition(False, s13, 0).a12 if s13 else _0_0
618
+ _update_all(self)
619
+ return self._a13 # NAN for GeodesicLineExact without Cap.DISTANCE_IN
620
+
621
+ @Property_RO
622
+ def _stau1_ctau1(self):
623
+ '''(INTERNAL) Cached/memoized.
624
+ '''
625
+ s, c = _sincos2(self._E1)
626
+ # tau1 = sig1 + B11
627
+ return _sincos12(-s, c, self._ssig1, self._csig1)
628
+ # unnecessary because Einv inverts E
629
+ # return -self._eF.deltaEinv(stau1, ctau1)
630
+
631
+ def toStr(self, prec=6, sep=_COMMASPACE_, **unused): # PYCHOK signature
632
+ '''Return this C{GeodesicLineExact} as string.
633
+
634
+ @kwarg prec: The C{float} precision, number of decimal digits (0..9).
635
+ Trailing zero decimals are stripped for B{C{prec}} values
636
+ of 1 and above, but kept for negative B{C{prec}} values.
637
+ @kwarg sep: Separator to join (C{str}).
638
+
639
+ @return: C{GeodesicLineExact} (C{str}).
640
+ '''
641
+ d = dict(geodesic=self.geodesic,
642
+ lat1=self.lat1, lon1=self.lon1, azi1=self.azi1,
643
+ a13=self.a13, s13=self.s13)
644
+ return sep.join(_MODS.streprs.pairs(d, prec=prec))
645
+
646
+
647
+ __all__ += _ALL_DOCS(_GeodesicLineExact)
648
+
649
+ # **) MIT License
650
+ #
651
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
652
+ #
653
+ # Permission is hereby granted, free of charge, to any person obtaining a
654
+ # copy of this software and associated documentation files (the "Software"),
655
+ # to deal in the Software without restriction, including without limitation
656
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
657
+ # and/or sell copies of the Software, and to permit persons to whom the
658
+ # Software is furnished to do so, subject to the following conditions:
659
+ #
660
+ # The above copyright notice and this permission notice shall be included
661
+ # in all copies or substantial portions of the Software.
662
+ #
663
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
664
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
665
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
666
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
667
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
668
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
669
+ # OTHER DEALINGS IN THE SOFTWARE.