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