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,1766 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) Base class L{LatLonBase} for all elliposiodal, spherical and N-vectorial C{LatLon} classes.
5
+
6
+ @see: I{(C) Chris Veness}' U{latlong<https://www.Movable-Type.co.UK/scripts/latlong.html>},
7
+ U{-ellipsoidal<https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>} and
8
+ U{-vectors<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>} and I{Charles Karney}'s
9
+ U{Rhumb<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Rhumb.html>} and
10
+ U{RhumbLine<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1RhumbLine.html>} classes.
11
+ '''
12
+
13
+ from pygeodesy.basics import isstr, map1, _xinstanceof
14
+ from pygeodesy.constants import EPS, EPS0, EPS1, EPS4, INT0, R_M, \
15
+ _EPSqrt as _TOL, _0_0, _0_5, _1_0, \
16
+ _360_0, _umod_360
17
+ from pygeodesy.datums import _spherical_datum
18
+ from pygeodesy.dms import F_D, F_DMS, latDMS, lonDMS, parse3llh
19
+ # from pygeodesy.ecef import EcefKarney # _MODS
20
+ from pygeodesy.errors import _AttributeError, IntersectionError, \
21
+ _incompatible, _IsnotError, _TypeError, \
22
+ _ValueError, _xattr, _xdatum, _xError, \
23
+ _xkwds, _xkwds_item2, _xkwds_not
24
+ # from pygeodesy.fmath import favg # _MODS
25
+ # from pygeodesy.formy import antipode, compassAngle, cosineAndoyerLambert_, \
26
+ # cosineForsytheAndoyerLambert_, cosineLaw, \
27
+ # equirectangular, euclidean, flatLocal_, \
28
+ # flatPolar, _hartzell, haversine, isantipode, \
29
+ # _isequalTo, isnormal, normal, philam2n_xyz, \
30
+ # thomas_, vincentys # as _formy
31
+ from pygeodesy.interns import NN, _COMMASPACE_, _concentric_, _height_, \
32
+ _intersection_, _LatLon_, _m_, _negative_, \
33
+ _no_, _overlap_, _too_, _point_ # PYCHOK used!
34
+ # from pygeodesy.iters import PointsIter, points2 # from .vector3d, _MODS
35
+ # from pygeodesy.karney import Caps # _MODS
36
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
37
+ # from pygeodesy.ltp import Ltp, _xLtp # _MODS
38
+ from pygeodesy.named import _NamedBase, notImplemented, notOverloaded, Fmt
39
+ from pygeodesy.namedTuples import Bounds2Tuple, LatLon2Tuple, PhiLam2Tuple, \
40
+ Trilaterate5Tuple
41
+ # from pygeodesy.nvectorBase import _N_vector_ # _MODS
42
+ from pygeodesy.props import deprecated_method, Property, Property_RO, \
43
+ property_RO, _update_all
44
+ # from pygeodesy.streprs import Fmt, hstr # from .named, _MODS
45
+ from pygeodesy.units import _isDegrees, _isRadius, Distance_, Lat, Lon, \
46
+ Height, Radius, Radius_, Scalar, Scalar_
47
+ from pygeodesy.utily import _unrollon, _unrollon3, _Wrap
48
+ from pygeodesy.vector2d import _circin6, Circin6Tuple, _circum3, circum4_, \
49
+ Circum3Tuple, _radii11ABC
50
+ from pygeodesy.vector3d import nearestOn6, Vector3d, PointsIter
51
+
52
+ from contextlib import contextmanager
53
+ from math import asin, cos, degrees, fabs, radians
54
+
55
+ __all__ = _ALL_LAZY.latlonBase
56
+ __version__ = '24.03.15'
57
+
58
+
59
+ class LatLonBase(_NamedBase):
60
+ '''(INTERNAL) Base class for C{LatLon} points on spherical or
61
+ ellipsoidal earth models.
62
+ '''
63
+ _clipid = INT0 # polygonal clip, see .booleans
64
+ _datum = None # L{Datum}, to be overriden
65
+ _height = INT0 # height (C{meter}), default
66
+ _lat = 0 # latitude (C{degrees})
67
+ _lon = 0 # longitude (C{degrees})
68
+
69
+ def __init__(self, latlonh, lon=None, height=0, wrap=False, name=NN, datum=None):
70
+ '''New C{LatLon}.
71
+
72
+ @arg latlonh: Latitude (C{degrees} or DMS C{str} with N or S suffix) or
73
+ a previous C{LatLon} instance provided C{B{lon}=None}.
74
+ @kwarg lon: Longitude (C{degrees} or DMS C{str} with E or W suffix) or
75
+ C(None), indicating B{C{latlonh}} is a C{LatLon}.
76
+ @kwarg height: Optional height above (or below) the earth surface
77
+ (C{meter}, conventionally).
78
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{lat}} and B{C{lon}}
79
+ (C{bool}).
80
+ @kwarg name: Optional name (C{str}).
81
+ @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2},
82
+ L{a_f2Tuple} or I{scalar} radius) or C{None}.
83
+
84
+ @return: New instance (C{LatLon}).
85
+
86
+ @raise RangeError: A B{C{lon}} or C{lat} value outside the valid
87
+ range and L{rangerrors} set to C{True}.
88
+
89
+ @raise TypeError: If B{C{latlonh}} is not a C{LatLon}.
90
+
91
+ @raise UnitError: Invalid B{C{lat}}, B{C{lon}} or B{C{height}}.
92
+ '''
93
+ if name:
94
+ self.name = name
95
+
96
+ if lon is None:
97
+ lat, lon, height = _latlonheight3(latlonh, height, wrap)
98
+ elif wrap:
99
+ lat, lon = _Wrap.latlonDMS2(latlonh, lon)
100
+ else:
101
+ lat = latlonh
102
+
103
+ self._lat = Lat(lat) # parseDMS2(lat, lon)
104
+ self._lon = Lon(lon) # PYCHOK LatLon2Tuple
105
+ if height: # elevation
106
+ self._height = Height(height)
107
+ if datum is not None:
108
+ self._datum = _spherical_datum(datum, name=self.name)
109
+
110
+ def __eq__(self, other):
111
+ return self.isequalTo(other)
112
+
113
+ def __ne__(self, other):
114
+ return not self.isequalTo(other)
115
+
116
+ def __str__(self):
117
+ return self.toStr(form=F_D, prec=6)
118
+
119
+ def antipode(self, height=None):
120
+ '''Return the antipode, the point diametrically opposite to
121
+ this point.
122
+
123
+ @kwarg height: Optional height of the antipode (C{meter}),
124
+ this point's height otherwise.
125
+
126
+ @return: The antipodal point (C{LatLon}).
127
+ '''
128
+ a = self._formy.antipode(*self.latlon)
129
+ h = self._heigHt(height)
130
+ return self.classof(*a, height=h)
131
+
132
+ @deprecated_method
133
+ def bounds(self, wide, tall, radius=R_M): # PYCHOK no cover
134
+ '''DEPRECATED, use method C{boundsOf}.'''
135
+ return self.boundsOf(wide, tall, radius=radius)
136
+
137
+ def boundsOf(self, wide, tall, radius=R_M, height=None):
138
+ '''Return the SW and NE lat-/longitude of a great circle
139
+ bounding box centered at this location.
140
+
141
+ @arg wide: Longitudinal box width (C{meter}, same units as
142
+ B{C{radius}} or C{degrees} if B{C{radius}} is C{None}).
143
+ @arg tall: Latitudinal box size (C{meter}, same units as
144
+ B{C{radius}} or C{degrees} if B{C{radius}} is C{None}).
145
+ @kwarg radius: Mean earth radius (C{meter}) or C{None} if I{both}
146
+ B{C{wide}} and B{C{tall}} are in C{degrees}.
147
+ @kwarg height: Height for C{latlonSW} and C{latlonNE} (C{meter}),
148
+ overriding the point's height.
149
+
150
+ @return: A L{Bounds2Tuple}C{(latlonSW, latlonNE)}, the
151
+ lower-left and upper-right corner (C{LatLon}).
152
+
153
+ @see: U{https://www.Movable-Type.co.UK/scripts/latlong-db.html}
154
+ '''
155
+ w = Scalar_(wide=wide) * _0_5
156
+ t = Scalar_(tall=tall) * _0_5
157
+ if radius is not None:
158
+ r = Radius_(radius)
159
+ c = cos(self.phi)
160
+ w = degrees(asin(w / r) / c) if fabs(c) > EPS0 else _0_0 # XXX
161
+ t = degrees(t / r)
162
+ y, t = self.lat, fabs(t)
163
+ x, w = self.lon, fabs(w)
164
+
165
+ h = self._heigHt(height)
166
+ sw = self.classof(y - t, x - w, height=h)
167
+ ne = self.classof(y + t, x + w, height=h)
168
+ return Bounds2Tuple(sw, ne, name=self.name)
169
+
170
+ def chordTo(self, other, height=None, wrap=False):
171
+ '''Compute the length of the chord through the earth between
172
+ this and an other point.
173
+
174
+ @arg other: The other point (C{LatLon}).
175
+ @kwarg height: Overriding height for both points (C{meter})
176
+ or C{None} for each point's height.
177
+ @kwarg wrap: If C{True}, wrap or I{normalize} the B{C{other}}
178
+ point (C{bool}).
179
+
180
+ @return: The chord length (conventionally C{meter}).
181
+
182
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
183
+ '''
184
+ def _v3d(ll):
185
+ t = ll.toEcef(height=height) # .toVector(Vector=Vector3d)
186
+ return Vector3d(t.x, t.y, t.z)
187
+
188
+ p = self.others(other)
189
+ if wrap:
190
+ p = _Wrap.point(p)
191
+ return _v3d(self).minus(_v3d(p)).length
192
+
193
+ def circin6(self, point2, point3, eps=EPS4, wrap=False):
194
+ '''Return the radius and center of the I{inscribed} aka I{In-}circle
195
+ of the (planar) triangle formed by this and two other points.
196
+
197
+ @arg point2: Second point (C{LatLon}).
198
+ @arg point3: Third point (C{LatLon}).
199
+ @kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2}.
200
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
201
+ B{C{point3}} (C{bool}).
202
+
203
+ @return: L{Circin6Tuple}C{(radius, center, deltas, cA, cB, cC)}. The
204
+ C{center} and contact points C{cA}, C{cB} and C{cC}, each an
205
+ instance of this (sub-)class, are co-planar with this and the
206
+ two given points, see the B{Note} below.
207
+
208
+ @raise ImportError: Package C{numpy} not found, not installed or older
209
+ than version 1.10.
210
+
211
+ @raise IntersectionError: Near-coincident or -colinear points or
212
+ a trilateration or C{numpy} issue.
213
+
214
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
215
+
216
+ @note: The C{center} is trilaterated in cartesian (ECEF) space and converted
217
+ back to geodetic lat-, longitude and height. The latter, conventionally
218
+ in C{meter} indicates whether the C{center} is above, below or on the
219
+ surface of the earth model. If C{deltas} is C{None}, the C{center} is
220
+ I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat, lon,
221
+ height)} representing the differences between both results from
222
+ L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
223
+
224
+ @see: Function L{pygeodesy.circin6}, method L{circum3}, U{Incircle
225
+ <https://MathWorld.Wolfram.com/Incircle.html>} and U{Contact Triangle
226
+ <https://MathWorld.Wolfram.com/ContactTriangle.html>}.
227
+ '''
228
+ with _toCartesian3(self, point2, point3, wrap) as cs:
229
+ r, c, d, cA, cB, cC = _circin6(*cs, eps=eps, useZ=True, dLL3=True,
230
+ datum=self.datum) # PYCHOK unpack
231
+ return Circin6Tuple(r, c.toLatLon(), d, cA.toLatLon(), cB.toLatLon(), cC.toLatLon())
232
+
233
+ def circum3(self, point2, point3, circum=True, eps=EPS4, wrap=False):
234
+ '''Return the radius and center of the smallest circle I{through} or I{containing}
235
+ this and two other points.
236
+
237
+ @arg point2: Second point (C{LatLon}).
238
+ @arg point3: Third point (C{LatLon}).
239
+ @kwarg circum: If C{True} return the C{circumradius} and C{circumcenter},
240
+ always, ignoring the I{Meeus}' Type I case (C{bool}).
241
+ @kwarg eps: Tolerance for function L{pygeodesy.trilaterate3d2}.
242
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
243
+ B{C{point3}} (C{bool}).
244
+
245
+ @return: A L{Circum3Tuple}C{(radius, center, deltas)}. The C{center}, an
246
+ instance of this (sub-)class, is co-planar with this and the two
247
+ given points. If C{deltas} is C{None}, the C{center} is
248
+ I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat,
249
+ lon, height)} representing the difference between both results
250
+ from L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
251
+
252
+ @raise ImportError: Package C{numpy} not found, not installed or older than
253
+ version 1.10.
254
+
255
+ @raise IntersectionError: Near-concentric, -coincident or -colinear points,
256
+ incompatible C{Ecef} classes or a trilateration
257
+ or C{numpy} issue.
258
+
259
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
260
+
261
+ @note: The C{center} is trilaterated in cartesian (ECEF) space and converted
262
+ back to geodetic lat-, longitude and height. The latter, conventionally
263
+ in C{meter} indicates whether the C{center} is above, below or on the
264
+ surface of the earth model. If C{deltas} is C{None}, the C{center} is
265
+ I{un}ambigous. Otherwise C{deltas} is a L{LatLon3Tuple}C{(lat, lon,
266
+ height)} representing the difference between both results from
267
+ L{pygeodesy.trilaterate3d2} and C{center} is the mean thereof.
268
+
269
+ @see: Function L{pygeodesy.circum3} and methods L{circin6} and L{circum4_}.
270
+ '''
271
+ with _toCartesian3(self, point2, point3, wrap, circum=circum) as cs:
272
+ r, c, d = _circum3(*cs, circum=circum, eps=eps, useZ=True, dLL3=True, # XXX -3d2
273
+ clas=cs[0].classof, datum=self.datum) # PYCHOK unpack
274
+ return Circum3Tuple(r, c.toLatLon(), d)
275
+
276
+ def circum4_(self, *points, **wrap):
277
+ '''Best-fit a sphere through this and two or more other points.
278
+
279
+ @arg points: The other points (each a C{LatLon}).
280
+ @kwarg wrap: If C{True}, wrap or I{normalize} the B{C{points}}
281
+ (C{bool}), default C{False}.
282
+
283
+ @return: L{Circum4Tuple}C{(radius, center, rank, residuals)} with C{center}
284
+ an instance of this (sub-)class.
285
+
286
+ @raise ImportError: Package C{numpy} not found, not installed or older than
287
+ version 1.10.
288
+
289
+ @raise NumPyError: Some C{numpy} issue.
290
+
291
+ @raise TypeError: One of the B{C{points}} invalid.
292
+
293
+ @raise ValueError: Too few B{C{points}}.
294
+
295
+ @see: Function L{pygeodesy.circum4_} and L{circum3}.
296
+ '''
297
+ def _cs(ps, C, wrap=False):
298
+ _wp = _Wrap.point if wrap else (lambda p: p)
299
+ for i, p in enumerate(ps):
300
+ yield C(i=i, points=_wp(p))
301
+
302
+ C = self._toCartesianEcef
303
+ c = C(point=self)
304
+ t = circum4_(c, Vector=c.classof, *_cs(points, C, **wrap))
305
+ c = t.center.toLatLon(LatLon=self.classof)
306
+ return t.dup(center=c)
307
+
308
+ @property
309
+ def clipid(self):
310
+ '''Get the (polygonal) clip (C{int}).
311
+ '''
312
+ return self._clipid
313
+
314
+ @clipid.setter # PYCHOK setter!
315
+ def clipid(self, clipid):
316
+ '''Get the (polygonal) clip (C{int}).
317
+ '''
318
+ self._clipid = int(clipid)
319
+
320
+ @deprecated_method
321
+ def compassAngle(self, other, **adjust_wrap): # PYCHOK no cover
322
+ '''DEPRECATED, use method L{compassAngleTo}.'''
323
+ return self.compassAngleTo(other, **adjust_wrap)
324
+
325
+ def compassAngleTo(self, other, **adjust_wrap):
326
+ '''Return the angle from North for the direction vector between
327
+ this and an other point.
328
+
329
+ Suitable only for short, non-near-polar vectors up to a few
330
+ hundred Km or Miles. Use method C{initialBearingTo} for
331
+ larger distances.
332
+
333
+ @arg other: The other point (C{LatLon}).
334
+ @kwarg adjust_wrap: Optional keyword arguments for function
335
+ L{pygeodesy.compassAngle}.
336
+
337
+ @return: Compass angle from North (C{degrees360}).
338
+
339
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
340
+
341
+ @note: Courtesy of Martin Schultz.
342
+
343
+ @see: U{Local, flat earth approximation
344
+ <https://www.EdWilliams.org/avform.htm#flat>}.
345
+ '''
346
+ p = self.others(other)
347
+ return self._formy.compassAngle(self.lat, self.lon, p.lat, p.lon, **adjust_wrap)
348
+
349
+ def cosineAndoyerLambertTo(self, other, wrap=False):
350
+ '''Compute the distance between this and an other point using the U{Andoyer-Lambert correction<https://
351
+ navlib.net/wp-content/uploads/2013/10/admiralty-manual-of-navigation-vol-1-1964-english501c.pdf>}
352
+ of the U{Law of Cosines<https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>} formula.
353
+
354
+ @arg other: The other point (C{LatLon}).
355
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
356
+ the B{C{other}} point (C{bool}).
357
+
358
+ @return: Distance (C{meter}, same units as the axes of this
359
+ point's datum ellipsoid).
360
+
361
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
362
+
363
+ @see: Function L{pygeodesy.cosineAndoyerLambert} and methods
364
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo},
365
+ C{distanceTo*}, L{equirectangularTo}, L{euclideanTo},
366
+ L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo}, L{haversineTo},
367
+ L{thomasTo} and L{vincentysTo}.
368
+ '''
369
+ return self._distanceTo_(self._formy.cosineAndoyerLambert_, other, wrap=wrap)
370
+
371
+ def cosineForsytheAndoyerLambertTo(self, other, wrap=False):
372
+ '''Compute the distance between this and an other point using
373
+ the U{Forsythe-Andoyer-Lambert correction
374
+ <https://www2.UNB.Ca/gge/Pubs/TR77.pdf>} of the U{Law of Cosines
375
+ <https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>}
376
+ formula.
377
+
378
+ @arg other: The other point (C{LatLon}).
379
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
380
+ the B{C{other}} point (C{bool}).
381
+
382
+ @return: Distance (C{meter}, same units as the axes of
383
+ this point's datum ellipsoid).
384
+
385
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
386
+
387
+ @see: Function L{pygeodesy.cosineForsytheAndoyerLambert} and methods
388
+ L{cosineAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
389
+ L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
390
+ L{flatPolarTo}, L{haversineTo}, L{thomasTo} and L{vincentysTo}.
391
+ '''
392
+ return self._distanceTo_(self._formy.cosineForsytheAndoyerLambert_, other, wrap=wrap)
393
+
394
+ def cosineLawTo(self, other, radius=None, wrap=False):
395
+ '''Compute the distance between this and an other point using the
396
+ U{spherical Law of Cosines
397
+ <https://www.Movable-Type.co.UK/scripts/latlong.html#cosine-law>}
398
+ formula.
399
+
400
+ @arg other: The other point (C{LatLon}).
401
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}
402
+ for the mean radius of this point's datum
403
+ ellipsoid.
404
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
405
+ the B{C{other}} point (C{bool}).
406
+
407
+ @return: Distance (C{meter}, same units as B{C{radius}}).
408
+
409
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
410
+
411
+ @see: Function L{pygeodesy.cosineLaw} and methods L{cosineAndoyerLambertTo},
412
+ L{cosineForsytheAndoyerLambertTo}, C{distanceTo*}, L{equirectangularTo},
413
+ L{euclideanTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
414
+ L{haversineTo}, L{thomasTo} and L{vincentysTo}.
415
+ '''
416
+ return self._distanceTo(self._formy.cosineLaw, other, radius, wrap=wrap)
417
+
418
+ @property_RO
419
+ def datum(self): # PYCHOK no cover
420
+ '''I{Must be overloaded}.'''
421
+ notOverloaded(self)
422
+
423
+ def destinationXyz(self, delta, LatLon=None, **LatLon_kwds):
424
+ '''Calculate the destination using a I{local} delta from this point.
425
+
426
+ @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu},
427
+ L{Ned} or L{Local9Tuple}).
428
+ @kwarg LatLon: Optional (geodetic) class to return the destination
429
+ or C{None}.
430
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
431
+ arguments, ignored if C{B{LatLon} is None}.
432
+
433
+ @return: Destination as a C{B{LatLon}(lat, lon, **B{LatLon_kwds})}
434
+ instance or if C{B{LatLon} is None}, a L{LatLon3Tuple}C{(lat,
435
+ lon, height)} respectively L{LatLon4Tuple}C{(lat, lon,
436
+ height, datum)} depending on whether a C{datum} keyword
437
+ is un-/specified.
438
+
439
+ @raise TypeError: Invalid B{C{delta}}, B{C{LatLon}} or B{C{LatLon_kwds}}.
440
+ '''
441
+ t = self._Ltp._local2ecef(delta, nine=True)
442
+ return t.toLatLon(LatLon=LatLon, **_xkwds(LatLon_kwds, name=self.name))
443
+
444
+ def _distanceTo(self, func, other, radius=None, **kwds):
445
+ '''(INTERNAL) Helper for distance methods C{<func>To}.
446
+ '''
447
+ p, r = self.others(other, up=2), radius
448
+ if r is None:
449
+ r = self._datum.ellipsoid.R1 if self._datum else R_M
450
+ return func(self.lat, self.lon, p.lat, p.lon, radius=r, **kwds)
451
+
452
+ def _distanceTo_(self, func_, other, wrap=False, radius=None):
453
+ '''(INTERNAL) Helper for (ellipsoidal) distance methods C{<func>To}.
454
+ '''
455
+ p = self.others(other, up=2)
456
+ D = self.datum
457
+ lam21, phi2, _ = _Wrap.philam3(self.lam, p.phi, p.lam, wrap)
458
+ r = func_(phi2, self.phi, lam21, datum=D)
459
+ return r * (D.ellipsoid.a if radius is None else radius)
460
+
461
+ @property_RO
462
+ def Ecef(self):
463
+ '''Get the ECEF I{class} (L{EcefKarney}), I{once}.
464
+ '''
465
+ LatLonBase.Ecef = E = _MODS.ecef.EcefKarney # overwrite property_RO
466
+ return E
467
+
468
+ @Property_RO
469
+ def _Ecef_forward(self):
470
+ '''(INTERNAL) Helper for L{_ecef9} and L{toEcef} (C{callable}).
471
+ '''
472
+ return self.Ecef(self.datum, name=self.name).forward
473
+
474
+ @Property_RO
475
+ def _ecef9(self):
476
+ '''(INTERNAL) Helper for L{toCartesian}, L{toEcef} and L{toCartesian} (L{Ecef9Tuple}).
477
+ '''
478
+ return self._Ecef_forward(self, M=True)
479
+
480
+ @property_RO
481
+ def ellipsoidalLatLon(self):
482
+ '''Get the C{LatLon type} iff ellipsoidal, overloaded in L{LatLonEllipsoidalBase}.
483
+ '''
484
+ return False
485
+
486
+ @deprecated_method
487
+ def equals(self, other, eps=None): # PYCHOK no cover
488
+ '''DEPRECATED, use method L{isequalTo}.'''
489
+ return self.isequalTo(other, eps=eps)
490
+
491
+ @deprecated_method
492
+ def equals3(self, other, eps=None): # PYCHOK no cover
493
+ '''DEPRECATED, use method L{isequalTo3}.'''
494
+ return self.isequalTo3(other, eps=eps)
495
+
496
+ def equirectangularTo(self, other, **radius_adjust_limit_wrap):
497
+ '''Compute the distance between this and an other point
498
+ using the U{Equirectangular Approximation / Projection
499
+ <https://www.Movable-Type.co.UK/scripts/latlong.html#equirectangular>}.
500
+
501
+ Suitable only for short, non-near-polar distances up to a
502
+ few hundred Km or Miles. Use method L{haversineTo} or
503
+ C{distanceTo*} for more accurate and/or larger distances.
504
+
505
+ @arg other: The other point (C{LatLon}).
506
+ @kwarg radius_adjust_limit_wrap: Optional keyword arguments
507
+ for function L{pygeodesy.equirectangular},
508
+ overriding the default mean C{radius} of this
509
+ point's datum ellipsoid.
510
+
511
+ @return: Distance (C{meter}, same units as B{C{radius}}).
512
+
513
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
514
+
515
+ @see: Function L{pygeodesy.equirectangular} and methods L{cosineAndoyerLambertTo},
516
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
517
+ C{euclideanTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
518
+ L{haversineTo}, L{thomasTo} and L{vincentysTo}.
519
+ '''
520
+ return self._distanceTo(self._formy.equirectangular, other, **radius_adjust_limit_wrap)
521
+
522
+ def euclideanTo(self, other, **radius_adjust_wrap):
523
+ '''Approximate the C{Euclidian} distance between this and
524
+ an other point.
525
+
526
+ See function L{pygeodesy.euclidean} for the available B{C{options}}.
527
+
528
+ @arg other: The other point (C{LatLon}).
529
+ @kwarg radius_adjust_wrap: Optional keyword arguments for function
530
+ L{pygeodesy.euclidean}, overriding the default mean
531
+ C{radius} of this point's datum ellipsoid.
532
+
533
+ @return: Distance (C{meter}, same units as B{C{radius}}).
534
+
535
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
536
+
537
+ @see: Function L{pygeodesy.euclidean} and methods L{cosineAndoyerLambertTo},
538
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
539
+ L{equirectangularTo}, L{flatLocalTo}/L{hubenyTo}, L{flatPolarTo},
540
+ L{haversineTo}, L{thomasTo} and L{vincentysTo}.
541
+ '''
542
+ return self._distanceTo(self._formy.euclidean, other, **radius_adjust_wrap)
543
+
544
+ def flatLocalTo(self, other, radius=None, wrap=False):
545
+ '''Compute the distance between this and an other point using the
546
+ U{ellipsoidal Earth to plane projection
547
+ <https://WikiPedia.org/wiki/Geographical_distance#Ellipsoidal_Earth_projected_to_a_plane>}
548
+ aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>} formula.
549
+
550
+ @arg other: The other point (C{LatLon}).
551
+ @kwarg radius: Mean earth radius (C{meter}) or C{None} for
552
+ the I{equatorial radius} of this point's
553
+ datum ellipsoid.
554
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
555
+ the B{C{other}} point (C{bool}).
556
+
557
+ @return: Distance (C{meter}, same units as B{C{radius}}).
558
+
559
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
560
+
561
+ @raise ValueError: Invalid B{C{radius}}.
562
+
563
+ @see: Function L{pygeodesy.flatLocal}/L{pygeodesy.hubeny}, methods
564
+ L{cosineAndoyerLambertTo}, L{cosineForsytheAndoyerLambertTo},
565
+ L{cosineLawTo}, C{distanceTo*}, L{equirectangularTo}, L{euclideanTo},
566
+ L{flatPolarTo}, L{haversineTo}, L{thomasTo} and L{vincentysTo} and
567
+ U{local, flat Earth approximation<https://www.edwilliams.org/avform.htm#flat>}.
568
+ '''
569
+ return self._distanceTo_(self._formy.flatLocal_, other, wrap=wrap, radius=
570
+ radius if radius in (None, R_M, _1_0, 1) else Radius(radius)) # PYCHOK kwargs
571
+
572
+ hubenyTo = flatLocalTo # for Karl Hubeny
573
+
574
+ def flatPolarTo(self, other, **radius_wrap):
575
+ '''Compute the distance between this and an other point using
576
+ the U{polar coordinate flat-Earth<https://WikiPedia.org/wiki/
577
+ Geographical_distance#Polar_coordinate_flat-Earth_formula>} formula.
578
+
579
+ @arg other: The other point (C{LatLon}).
580
+ @kwarg radius_wrap: Optional keyword arguments for function
581
+ L{pygeodesy.flatPolar}, overriding the
582
+ default mean C{radius} of this point's
583
+ datum ellipsoid.
584
+
585
+ @return: Distance (C{meter}, same units as B{C{radius}}).
586
+
587
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
588
+
589
+ @see: Function L{pygeodesy.flatPolar} and methods L{cosineAndoyerLambertTo},
590
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
591
+ L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
592
+ L{haversineTo}, L{thomasTo} and L{vincentysTo}.
593
+ '''
594
+ return self._distanceTo(self._formy.flatPolar, other, **radius_wrap)
595
+
596
+ @property_RO
597
+ def _formy(self):
598
+ '''(INTERNAL) Get module C{formy}, I{once}.
599
+ '''
600
+ LatLonBase._formy = f = _MODS.formy # overwrite property_RO
601
+ return f
602
+
603
+ def hartzell(self, los=False, earth=None):
604
+ '''Compute the intersection of a Line-Of-Sight from this (geodetic) Point-Of-View
605
+ (pov) with this point's ellipsoid surface.
606
+
607
+ @kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}),
608
+ C{True} for the I{normal, plumb} onto the surface or I{False} or
609
+ C{None} to point to the center of the ellipsoid.
610
+ @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}
611
+ or C{scalar} radius in C{meter}), overriding this point's C{datum}
612
+ ellipsoid.
613
+
614
+ @return: The intersection (C{LatLon}) with C{.height} set to the distance to
615
+ this C{pov}.
616
+
617
+ @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside
618
+ the ellipsoid or B{C{los}} points outside or away from
619
+ the ellipsoid.
620
+
621
+ @raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}.
622
+
623
+ @see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details.
624
+ '''
625
+ return self._formy._hartzell(self, los, earth, LatLon=self.classof)
626
+
627
+ def haversineTo(self, other, **radius_wrap):
628
+ '''Compute the distance between this and an other point using the
629
+ U{Haversine<https://www.Movable-Type.co.UK/scripts/latlong.html>}
630
+ formula.
631
+
632
+ @arg other: The other point (C{LatLon}).
633
+ @kwarg radius_wrap: Optional keyword arguments for function
634
+ L{pygeodesy.haversine}, overriding the
635
+ default mean C{radius} of this point's
636
+ datum ellipsoid.
637
+
638
+ @return: Distance (C{meter}, same units as B{C{radius}}).
639
+
640
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
641
+
642
+ @see: Function L{pygeodesy.haversine} and methods L{cosineAndoyerLambertTo},
643
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
644
+ L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
645
+ L{flatPolarTo}, L{thomasTo} and L{vincentysTo}.
646
+ '''
647
+ return self._distanceTo(self._formy.haversine, other, **radius_wrap)
648
+
649
+ def _havg(self, other, f=_0_5, h=None):
650
+ '''(INTERNAL) Weighted, average height.
651
+
652
+ @arg other: An other point (C{LatLon}).
653
+ @kwarg f: Optional fraction (C{float}).
654
+ @kwarg h: Overriding height (C{meter}).
655
+
656
+ @return: Average, fractional height (C{float}) or
657
+ the overriding height B{C{h}} (C{Height}).
658
+ '''
659
+ return Height(h) if h is not None else \
660
+ _MODS.fmath.favg(self.height, other.height, f=f)
661
+
662
+ @Property
663
+ def height(self):
664
+ '''Get the height (C{meter}).
665
+ '''
666
+ return self._height
667
+
668
+ @height.setter # PYCHOK setter!
669
+ def height(self, height):
670
+ '''Set the height (C{meter}).
671
+
672
+ @raise TypeError: Invalid B{C{height}} C{type}.
673
+
674
+ @raise ValueError: Invalid B{C{height}}.
675
+ '''
676
+ h = Height(height)
677
+ if self._height != h:
678
+ _update_all(self)
679
+ self._height = h
680
+
681
+ def _heigHt(self, height):
682
+ '''(INTERNAL) Overriding this C{height}.
683
+ '''
684
+ return self.height if height is None else Height(height)
685
+
686
+ def height4(self, earth=None, normal=True, LatLon=None, **LatLon_kwds):
687
+ '''Compute the projection of this point on and the height above or below
688
+ this datum's ellipsoid surface.
689
+
690
+ @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,
691
+ I{overriding} this datum (L{Datum}, L{Ellipsoid},
692
+ L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},
693
+ L{JacobiConformal} or C{meter}, conventionally).
694
+ @kwarg normal: If C{True} the projection is the normal to this
695
+ ellipsoid's surface, otherwise the intersection of the
696
+ I{radial} line to this ellipsoid's center (C{bool}).
697
+ @kwarg LatLon: Optional class to return the projection, height and
698
+ datum (C{LatLon}) or C{None}.
699
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments,
700
+ ignored if C{B{LatLon} is None}.
701
+
702
+ @note: Use keyword argument C{height=0} to override C{B{LatLon}.height}
703
+ to {0} or any other C{scalar}, conventionally in C{meter}.
704
+
705
+ @return: An instance of class B{C{LatLon}} or if C{B{LatLon} is None}, a
706
+ L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}
707
+ and C{z} coordinates and height C{h} in C{meter}, conventionally.
708
+
709
+ @raise TriaxialError: No convergence in triaxial root finding.
710
+
711
+ @raise TypeError: Invalid B{C{earth}} or triaxial B{C{earth}} couldn't be
712
+ converted to biaxial B{C{LatLon}} datum.
713
+
714
+ @see: Methods L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.
715
+ '''
716
+ c = self.toCartesian()
717
+ if LatLon is None:
718
+ r = c.height4(earth=earth, normal=normal)
719
+ else:
720
+ c = c.height4(earth=earth, normal=normal, Cartesian=c.classof, height=0)
721
+ r = c.toLatLon(LatLon=LatLon, **_xkwds(LatLon_kwds, datum=c.datum, height=c.height))
722
+ if r.datum != c.datum:
723
+ raise _TypeError(earth=earth, datum=r.datum)
724
+ return r
725
+
726
+ def heightStr(self, prec=-2, m=_m_):
727
+ '''Return this point's B{C{height}} as C{str}ing.
728
+
729
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
730
+ @kwarg m: Optional unit of the height (C{str}).
731
+
732
+ @see: Function L{pygeodesy.hstr}.
733
+ '''
734
+ return _MODS.streprs.hstr(self.height, prec=prec, m=m)
735
+
736
+ def intersecant2(self, *args, **kwds): # PYCHOK no cover
737
+ '''B{Not implemented}, throws a C{NotImplementedError} always.'''
738
+ notImplemented(self, *args, **kwds)
739
+
740
+ def _intersecend2(self, p, q, wrap, height, g_or_r, P, Q, unused): # in .LatLonEllipsoidalBaseDI.intersecant2
741
+ '''(INTERNAL) Interpolate 2 heights along a geodesic or rhumb
742
+ line and return the 2 intercant points accordingly.
743
+ '''
744
+ if height is None:
745
+ hp = hq = _xattr(p, height=INT0)
746
+ h = _xattr(q, height=hp) # if isLatLon(q) else hp
747
+ if h != hp:
748
+ s = g_or_r._Inverse(p, q, wrap).s12
749
+ if s: # fmath.fidw?
750
+ s = (h - hp) / s # slope
751
+ hq += s * Q.s12
752
+ hp += s * P.s12
753
+ else:
754
+ hp = hq = _MODS.fmath.favg(hp, h)
755
+ else:
756
+ hp = hq = Height(height)
757
+
758
+ # n = self.name or unused.__name__
759
+ p = q = self.classof(P.lat2, P.lon2, datum=g_or_r.datum, height=hp) # name=n
760
+ p._iteration = P.iteration
761
+ if P is not Q:
762
+ q = self.classof(Q.lat2, Q.lon2, datum=g_or_r.datum, height=hq) # name=n
763
+ q._iteration = Q.iteration
764
+ return p, q
765
+
766
+ @deprecated_method
767
+ def isantipode(self, other, eps=EPS): # PYCHOK no cover
768
+ '''DEPRECATED, use method L{isantipodeTo}.'''
769
+ return self.isantipodeTo(other, eps=eps)
770
+
771
+ def isantipodeTo(self, other, eps=EPS):
772
+ '''Check whether this and an other point are antipodal,
773
+ on diametrically opposite sides of the earth.
774
+
775
+ @arg other: The other point (C{LatLon}).
776
+ @kwarg eps: Tolerance for near-equality (C{degrees}).
777
+
778
+ @return: C{True} if points are antipodal within the given
779
+ tolerance, C{False} otherwise.
780
+ '''
781
+ p = self.others(other)
782
+ return self._formy.isantipode(*(self.latlon + p.latlon), eps=eps)
783
+
784
+ @Property_RO
785
+ def isEllipsoidal(self):
786
+ '''Check whether this point is ellipsoidal (C{bool} or C{None} if unknown).
787
+ '''
788
+ return self.datum.isEllipsoidal if self._datum else None
789
+
790
+ def isequalTo(self, other, eps=None):
791
+ '''Compare this point with an other point, I{ignoring} height.
792
+
793
+ @arg other: The other point (C{LatLon}).
794
+ @kwarg eps: Tolerance for equality (C{degrees}).
795
+
796
+ @return: C{True} if both points are identical,
797
+ I{ignoring} height, C{False} otherwise.
798
+
799
+ @raise TypeError: The B{C{other}} point is not C{LatLon}
800
+ or mismatch of the B{C{other}} and
801
+ this C{class} or C{type}.
802
+
803
+ @raise UnitError: Invalid B{C{eps}}.
804
+
805
+ @see: Method L{isequalTo3}.
806
+ '''
807
+ return self._formy._isequalTo(self, self.others(other), eps=eps)
808
+
809
+ def isequalTo3(self, other, eps=None):
810
+ '''Compare this point with an other point, I{including} height.
811
+
812
+ @arg other: The other point (C{LatLon}).
813
+ @kwarg eps: Tolerance for equality (C{degrees}).
814
+
815
+ @return: C{True} if both points are identical I{including}
816
+ height, C{False} otherwise.
817
+
818
+ @raise TypeError: The B{C{other}} point is not C{LatLon}
819
+ or mismatch of the B{C{other}} and this
820
+ C{class} or C{type}.
821
+
822
+ @see: Method L{isequalTo}.
823
+ '''
824
+ return self.height == self.others(other).height and \
825
+ self._formy._isequalTo(self, other, eps=eps)
826
+
827
+ @Property_RO
828
+ def isnormal(self):
829
+ '''Return C{True} if this point is normal (C{bool}),
830
+ meaning C{abs(lat) <= 90} and C{abs(lon) <= 180}.
831
+
832
+ @see: Methods L{normal}, L{toNormal} and functions L{isnormal
833
+ <pygeodesy.isnormal>} and L{normal<pygeodesy.normal>}.
834
+ '''
835
+ return self._formy.isnormal(self.lat, self.lon, eps=0)
836
+
837
+ @Property_RO
838
+ def isSpherical(self):
839
+ '''Check whether this point is spherical (C{bool} or C{None} if unknown).
840
+ '''
841
+ return self.datum.isSpherical if self._datum else None
842
+
843
+ @Property_RO
844
+ def lam(self):
845
+ '''Get the longitude (B{C{radians}}).
846
+ '''
847
+ return radians(self.lon)
848
+
849
+ @Property
850
+ def lat(self):
851
+ '''Get the latitude (C{degrees90}).
852
+ '''
853
+ return self._lat
854
+
855
+ @lat.setter # PYCHOK setter!
856
+ def lat(self, lat):
857
+ '''Set the latitude (C{str[N|S]} or C{degrees}).
858
+
859
+ @raise ValueError: Invalid B{C{lat}}.
860
+ '''
861
+ lat = Lat(lat) # parseDMS(lat, suffix=_NS_, clip=90)
862
+ if self._lat != lat:
863
+ _update_all(self)
864
+ self._lat = lat
865
+
866
+ @Property
867
+ def latlon(self):
868
+ '''Get the lat- and longitude (L{LatLon2Tuple}C{(lat, lon)}).
869
+ '''
870
+ return LatLon2Tuple(self._lat, self._lon, name=self.name)
871
+
872
+ @latlon.setter # PYCHOK setter!
873
+ def latlon(self, latlonh):
874
+ '''Set the lat- and longitude and optionally the height
875
+ (2- or 3-tuple or comma- or space-separated C{str}
876
+ of C{degrees90}, C{degrees180} and C{meter}).
877
+
878
+ @raise TypeError: Height of B{C{latlonh}} not C{scalar} or
879
+ B{C{latlonh}} not C{list} or C{tuple}.
880
+
881
+ @raise ValueError: Invalid B{C{latlonh}} or M{len(latlonh)}.
882
+
883
+ @see: Function L{pygeodesy.parse3llh} to parse a B{C{latlonh}}
884
+ string into a 3-tuple C{(lat, lon, h)}.
885
+ '''
886
+ if isstr(latlonh):
887
+ latlonh = parse3llh(latlonh, height=self.height)
888
+ else:
889
+ _xinstanceof(list, tuple, latlonh=latlonh)
890
+ if len(latlonh) == 3:
891
+ h = Height(latlonh[2], name=Fmt.SQUARE(latlonh=2))
892
+ elif len(latlonh) != 2:
893
+ raise _ValueError(latlonh=latlonh)
894
+ else:
895
+ h = self.height
896
+
897
+ llh = Lat(latlonh[0]), Lon(latlonh[1]), h # parseDMS2(latlonh[0], latlonh[1])
898
+ if (self._lat, self._lon, self._height) != llh:
899
+ _update_all(self)
900
+ self._lat, self._lon, self._height = llh
901
+
902
+ def latlon2(self, ndigits=0):
903
+ '''Return this point's lat- and longitude in C{degrees}, rounded.
904
+
905
+ @kwarg ndigits: Number of (decimal) digits (C{int}).
906
+
907
+ @return: A L{LatLon2Tuple}C{(lat, lon)}, both C{float}
908
+ and rounded away from zero.
909
+
910
+ @note: The C{round}ed values are always C{float}, also
911
+ if B{C{ndigits}} is omitted.
912
+ '''
913
+ return LatLon2Tuple(round(self.lat, ndigits),
914
+ round(self.lon, ndigits), name=self.name)
915
+
916
+ @deprecated_method
917
+ def latlon_(self, ndigits=0): # PYCHOK no cover
918
+ '''DEPRECATED, use method L{latlon2}.'''
919
+ return self.latlon2(ndigits=ndigits)
920
+
921
+ latlon2round = latlon_ # PYCHOK no cover
922
+
923
+ @Property
924
+ def latlonheight(self):
925
+ '''Get the lat-, longitude and height (L{LatLon3Tuple}C{(lat, lon, height)}).
926
+ '''
927
+ return self.latlon.to3Tuple(self.height)
928
+
929
+ @latlonheight.setter # PYCHOK setter!
930
+ def latlonheight(self, latlonh):
931
+ '''Set the lat- and longitude and optionally the height
932
+ (2- or 3-tuple or comma- or space-separated C{str} of
933
+ C{degrees90}, C{degrees180} and C{meter}).
934
+
935
+ @see: Property L{latlon} for more details.
936
+ '''
937
+ self.latlon = latlonh
938
+
939
+ @Property
940
+ def lon(self):
941
+ '''Get the longitude (C{degrees180}).
942
+ '''
943
+ return self._lon
944
+
945
+ @lon.setter # PYCHOK setter!
946
+ def lon(self, lon):
947
+ '''Set the longitude (C{str[E|W]} or C{degrees}).
948
+
949
+ @raise ValueError: Invalid B{C{lon}}.
950
+ '''
951
+ lon = Lon(lon) # parseDMS(lon, suffix=_EW_, clip=180)
952
+ if self._lon != lon:
953
+ _update_all(self)
954
+ self._lon = lon
955
+
956
+ @property_RO
957
+ def _ltp(self):
958
+ '''(INTERNAL) Get the C{.ltp} module, I{once}.
959
+ '''
960
+ LatLonBase._ltp = m = _MODS.ltp # overwrite property_RO
961
+ return m
962
+
963
+ @Property_RO
964
+ def _Ltp(self):
965
+ '''(INTERNAL) Cache for L{toLtp}.
966
+ '''
967
+ return self._ltp.Ltp(self, ecef=self.Ecef(self.datum), name=self.name)
968
+
969
+ def nearestOn6(self, points, closed=False, height=None, wrap=False):
970
+ '''Locate the point on a path or polygon closest to this point.
971
+
972
+ Points are converted to and distances are computed in
973
+ I{geocentric}, cartesian space.
974
+
975
+ @arg points: The path or polygon points (C{LatLon}[]).
976
+ @kwarg closed: Optionally, close the polygon (C{bool}).
977
+ @kwarg height: Optional height, overriding the height of
978
+ this and all other points (C{meter}). If
979
+ C{None}, take the height of points into
980
+ account for distances.
981
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
982
+ the B{C{points}} (C{bool}).
983
+
984
+ @return: A L{NearestOn6Tuple}C{(closest, distance, fi, j,
985
+ start, end)} with the C{closest}, the C{start}
986
+ and the C{end} point each an instance of this
987
+ C{LatLon} and C{distance} in C{meter}, same
988
+ units as the cartesian axes.
989
+
990
+ @raise PointsError: Insufficient number of B{C{points}}.
991
+
992
+ @raise TypeError: Some B{C{points}} or some B{C{points}}'
993
+ C{Ecef} invalid.
994
+
995
+ @raise ValueError: Some B{C{points}}' C{Ecef} is incompatible.
996
+
997
+ @see: Function L{nearestOn6<pygeodesy.nearestOn6>}.
998
+ '''
999
+ def _cs(Ps, h, w, C):
1000
+ p = None # not used
1001
+ for i, q in Ps.enumerate():
1002
+ if w and i:
1003
+ q = _unrollon(p, q)
1004
+ yield C(height=h, i=i, up=3, points=q)
1005
+ p = q
1006
+
1007
+ C = self._toCartesianEcef # to verify datum and Ecef
1008
+ Ps = self.PointsIter(points, wrap=wrap)
1009
+
1010
+ c = C(height=height, this=self) # this Cartesian
1011
+ t = nearestOn6(c, _cs(Ps, height, wrap, C), closed=closed)
1012
+ c, s, e = t.closest, t.start, t.end
1013
+
1014
+ kwds = _xkwds_not(None, LatLon=self.classof, # this LatLon
1015
+ height=height)
1016
+ _r = self.Ecef(self.datum).reverse
1017
+ p = _r(c).toLatLon(**kwds)
1018
+ s = _r(s).toLatLon(**kwds) if s is not c else p
1019
+ e = _r(e).toLatLon(**kwds) if e is not c else p
1020
+ return t.dup(closest=p, start=s, end=e)
1021
+
1022
+ def nearestTo(self, *args, **kwds): # PYCHOK no cover
1023
+ '''B{Not implemented}, throws a C{NotImplementedError} always.'''
1024
+ notImplemented(self, *args, **kwds)
1025
+
1026
+ def normal(self):
1027
+ '''Normalize this point I{in-place} to C{abs(lat) <= 90} and
1028
+ C{abs(lon) <= 180}.
1029
+
1030
+ @return: C{True} if this point was I{normal}, C{False} if it
1031
+ wasn't (but is now).
1032
+
1033
+ @see: Property L{isnormal} and method L{toNormal}.
1034
+ '''
1035
+ n = self.isnormal
1036
+ if not n:
1037
+ self.latlon = self._formy.normal(*self.latlon)
1038
+ return n
1039
+
1040
+ @property_RO
1041
+ def _N_vector(self):
1042
+ '''(INTERNAL) Get the C{Nvector} (C{nvectorBase._N_vector_})
1043
+ '''
1044
+ x, y, z = self._n_xyz3
1045
+ return _MODS.nvectorBase._N_vector_(x, y, z, h=self.height, name=self.name)
1046
+
1047
+ @Property_RO
1048
+ def _n_xyz3(self):
1049
+ '''(INTERNAL) Get the n-vector components as L{Vector3Tuple}.
1050
+ '''
1051
+ return self._formy.philam2n_xyz(self.phi, self.lam, name=self.name)
1052
+
1053
+ @Property_RO
1054
+ def phi(self):
1055
+ '''Get the latitude (B{C{radians}}).
1056
+ '''
1057
+ return radians(self.lat)
1058
+
1059
+ @Property_RO
1060
+ def philam(self):
1061
+ '''Get the lat- and longitude (L{PhiLam2Tuple}C{(phi, lam)}).
1062
+ '''
1063
+ return PhiLam2Tuple(self.phi, self.lam, name=self.name)
1064
+
1065
+ def philam2(self, ndigits=0):
1066
+ '''Return this point's lat- and longitude in C{radians}, rounded.
1067
+
1068
+ @kwarg ndigits: Number of (decimal) digits (C{int}).
1069
+
1070
+ @return: A L{PhiLam2Tuple}C{(phi, lam)}, both C{float}
1071
+ and rounded away from zero.
1072
+
1073
+ @note: The C{round}ed values are always C{float}, also
1074
+ if B{C{ndigits}} is omitted.
1075
+ '''
1076
+ return PhiLam2Tuple(round(self.phi, ndigits),
1077
+ round(self.lam, ndigits), name=self.name)
1078
+
1079
+ @Property_RO
1080
+ def philamheight(self):
1081
+ '''Get the lat-, longitude in C{radians} and height (L{PhiLam3Tuple}C{(phi, lam, height)}).
1082
+ '''
1083
+ return self.philam.to3Tuple(self.height)
1084
+
1085
+ @deprecated_method
1086
+ def points(self, points, closed=True): # PYCHOK no cover
1087
+ '''DEPRECATED, use method L{points2}.'''
1088
+ return self.points2(points, closed=closed)
1089
+
1090
+ def points2(self, points, closed=True):
1091
+ '''Check a path or polygon represented by points.
1092
+
1093
+ @arg points: The path or polygon points (C{LatLon}[])
1094
+ @kwarg closed: Optionally, consider the polygon closed,
1095
+ ignoring any duplicate or closing final
1096
+ B{C{points}} (C{bool}).
1097
+
1098
+ @return: A L{Points2Tuple}C{(number, points)}, an C{int}
1099
+ and C{list} or C{tuple}.
1100
+
1101
+ @raise PointsError: Insufficient number of B{C{points}}.
1102
+
1103
+ @raise TypeError: Some B{C{points}} are not C{LatLon}.
1104
+ '''
1105
+ return _MODS.iters.points2(points, closed=closed, base=self)
1106
+
1107
+ def PointsIter(self, points, loop=0, dedup=False, wrap=False):
1108
+ '''Return a C{PointsIter} iterator.
1109
+
1110
+ @arg points: The path or polygon points (C{LatLon}[])
1111
+ @kwarg loop: Number of loop-back points (non-negative C{int}).
1112
+ @kwarg dedup: Skip duplicate points (C{bool}).
1113
+ @kwarg wrap: If C{True}, wrap or I{normalize} the
1114
+ enum-/iterated B{C{points}} (C{bool}).
1115
+
1116
+ @return: A new C{PointsIter} iterator.
1117
+
1118
+ @raise PointsError: Insufficient number of B{C{points}}.
1119
+ '''
1120
+ return PointsIter(points, base=self, loop=loop, dedup=dedup, wrap=wrap)
1121
+
1122
+ def radii11(self, point2, point3, wrap=False):
1123
+ '''Return the radii of the C{Circum-}, C{In-}, I{Soddy} and C{Tangent}
1124
+ circles of a (planar) triangle formed by this and two other points.
1125
+
1126
+ @arg point2: Second point (C{LatLon}).
1127
+ @arg point3: Third point (C{LatLon}).
1128
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{point2}} and
1129
+ B{C{point3}} (C{bool}).
1130
+
1131
+ @return: L{Radii11Tuple}C{(rA, rB, rC, cR, rIn, riS, roS, a, b, c, s)}.
1132
+
1133
+ @raise IntersectionError: Near-coincident or -colinear points.
1134
+
1135
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
1136
+
1137
+ @see: Function L{pygeodesy.radii11}, U{Incircle
1138
+ <https://MathWorld.Wolfram.com/Incircle.html>}, U{Soddy Circles
1139
+ <https://MathWorld.Wolfram.com/SoddyCircles.html>} and U{Tangent
1140
+ Circles<https://MathWorld.Wolfram.com/TangentCircles.html>}.
1141
+ '''
1142
+ with _toCartesian3(self, point2, point3, wrap) as cs:
1143
+ return _radii11ABC(*cs, useZ=True)[0]
1144
+
1145
+ def _rhumb3(self, exact, radius): # != .sphericalBase._rhumbs3
1146
+ '''(INTERNAL) Get the C{rhumb} for this point's datum or for
1147
+ the B{C{radius}}' earth model iff non-C{None}.
1148
+ '''
1149
+ try:
1150
+ d = self._rhumb3dict
1151
+ t = d[(exact, radius)]
1152
+ except KeyError:
1153
+ D = self.datum if radius is None else \
1154
+ _spherical_datum(radius) # ellipsoidal OK
1155
+ try:
1156
+ r = D.ellipsoid.rhumb_(exact=exact) # or D.isSpherical
1157
+ except AttributeError as x:
1158
+ raise _AttributeError(datum=D, radius=radius, cause=x)
1159
+ t = r, D, _MODS.karney.Caps
1160
+ while d:
1161
+ d.popitem()
1162
+ d[(exact, radius)] = t # cache 3-tuple
1163
+ return t
1164
+
1165
+ @Property_RO
1166
+ def _rhumb3dict(self): # in rhumbIntersecant2 below
1167
+ return {} # single-item cache
1168
+
1169
+ def rhumbAzimuthTo(self, other, exact=False, radius=None, wrap=False, b360=False):
1170
+ '''Return the azimuth (bearing) of a rhumb line (loxodrome) between this
1171
+ and an other (ellipsoidal) point.
1172
+
1173
+ @arg other: The other point (C{LatLon}).
1174
+ @kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
1175
+ method L{Ellipsoid.rhumb_}.
1176
+ @kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
1177
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
1178
+ this point's datum.
1179
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
1180
+ point (C{bool}).
1181
+ @kwarg b360: If C{True}, return the azimuth in the bearing range.
1182
+
1183
+ @return: Rhumb azimuth (compass C{degrees180} or C{degrees360}).
1184
+
1185
+ @raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
1186
+ is invalid.
1187
+ '''
1188
+ r, _, Cs = self._rhumb3(exact, radius)
1189
+ z = r._Inverse(self, other, wrap, outmask=Cs.AZIMUTH).azi12
1190
+ return _umod_360(z + _360_0) if b360 else z
1191
+
1192
+ def rhumbDestination(self, distance, azimuth, exact=False, radius=None, height=None):
1193
+ '''Return the destination point having travelled the given distance from
1194
+ this point along a rhumb line (loxodrome) of the given azimuth.
1195
+
1196
+ @arg distance: Distance travelled (C{meter}, same units as this point's
1197
+ datum (ellipsoid) axes or B{C{radius}}, may be negative.
1198
+ @arg azimuth: Azimuth (bearing) of the rhumb line (compass C{degrees}).
1199
+ @kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
1200
+ method L{Ellipsoid.rhumb_}.
1201
+ @kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
1202
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
1203
+ this point's datum.
1204
+ @kwarg height: Optional height, overriding the default height (C{meter}).
1205
+
1206
+ @return: The destination point (ellipsoidal C{LatLon}).
1207
+
1208
+ @raise TypeError: Invalid B{C{radius}}.
1209
+
1210
+ @raise ValueError: Invalid B{C{distance}}, B{C{azimuth}}, B{C{radius}}
1211
+ or B{C{height}}.
1212
+ '''
1213
+ r, D, _ = self._rhumb3(exact, radius)
1214
+ d = r._Direct(self, azimuth, distance)
1215
+ h = self._heigHt(height)
1216
+ return self.classof(d.lat2, d.lon2, datum=D, height=h)
1217
+
1218
+ def rhumbDistanceTo(self, other, exact=False, radius=None, wrap=False):
1219
+ '''Return the distance from this to an other point along a rhumb line
1220
+ (loxodrome).
1221
+
1222
+ @arg other: The other point (C{LatLon}).
1223
+ @kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
1224
+ method L{Ellipsoid.rhumb_}.
1225
+ @kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
1226
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
1227
+ this point's datum.
1228
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
1229
+ point (C{bool}).
1230
+
1231
+ @return: Distance (C{meter}, the same units as this point's datum
1232
+ (ellipsoid) axes or B{C{radius}}.
1233
+
1234
+ @raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
1235
+ is invalid.
1236
+
1237
+ @raise ValueError: Invalid B{C{radius}}.
1238
+ '''
1239
+ r, _, Cs = self._rhumb3(exact, radius)
1240
+ return r._Inverse(self, other, wrap, outmask=Cs.DISTANCE).s12
1241
+
1242
+ def rhumbIntersecant2(self, circle, point, other, height=None,
1243
+ **exact_radius_wrap_eps_tol):
1244
+ '''Compute the intersections of a circle and a rhumb line given as two
1245
+ points or as a point and azimuth.
1246
+
1247
+ @arg circle: Radius of the circle centered at this location (C{meter}),
1248
+ or a point on the circle (this C{LatLon}).
1249
+ @arg point: The start point of the rhumb line (this C{LatLon}).
1250
+ @arg other: An other point I{on} (this C{LatLon}) or the azimuth I{of}
1251
+ (compass C{degrees}) the rhumb line.
1252
+ @kwarg height: Optional height for the intersection points (C{meter},
1253
+ conventionally) or C{None} for interpolated heights.
1254
+ @kwarg exact_radius_wrap_eps_tol: Optional keyword arguments, see
1255
+ methods L{rhumbLine} and L{RhumbLineAux.Intersecant2}
1256
+ or L{RhumbLine.Intersecant2}.
1257
+
1258
+ @return: 2-Tuple of the intersection points (representing a chord),
1259
+ each an instance of this class. Both points are the same
1260
+ instance if the rhumb line is tangent to the circle.
1261
+
1262
+ @raise IntersectionError: The circle and rhumb line do not intersect.
1263
+
1264
+ @raise TypeError: If B{C{point}} is not this C{LatLon} or B{C{circle}}
1265
+ or B{C{other}} invalid.
1266
+
1267
+ @raise ValueError: Invalid B{C{circle}}, B{C{other}}, B{C{height}}
1268
+ or B{C{exact_radius_wrap}}.
1269
+
1270
+ @see: Methods L{RhumbLineAux.Intersecant2} and L{RhumbLine.Intersecant2}.
1271
+ '''
1272
+ def _kwds3(eps=EPS, tol=_TOL, wrap=False, **kwds):
1273
+ return kwds, wrap, dict(eps=eps, tol=tol)
1274
+
1275
+ exact_radius, w, eps_tol = _kwds3(**exact_radius_wrap_eps_tol)
1276
+
1277
+ p = _unrollon(self, self.others(point=point), wrap=w)
1278
+ try:
1279
+ r = Radius_(circle=circle) if _isRadius(circle) else \
1280
+ self.rhumbDistanceTo(self.others(circle=circle), wrap=w, **exact_radius)
1281
+ rl = p.rhumbLine(other, wrap=w, **exact_radius)
1282
+ P, Q = rl.Intersecant2(self.lat, self.lon, r, **eps_tol)
1283
+
1284
+ return self._intersecend2(p, other, w, height, rl.rhumb, P, Q,
1285
+ self.rhumbIntersecant2)
1286
+
1287
+ except (TypeError, ValueError) as x:
1288
+ raise _xError(x, center=self, circle=circle, point=point, other=other,
1289
+ **exact_radius_wrap_eps_tol)
1290
+
1291
+ def rhumbLine(self, other, exact=False, radius=None, wrap=False, **name_caps):
1292
+ '''Get a rhumb line through this point at a given azimuth or through
1293
+ this and an other point.
1294
+
1295
+ @arg other: The azimuth I{of} (compass C{degrees}) or an other point
1296
+ I{on} (this C{LatLon}) the rhumb line.
1297
+ @kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
1298
+ method L{Ellipsoid.rhumb_}.
1299
+ @kwarg radius: Optional earth radius (C{meter}) or earth model
1300
+ (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}),
1301
+ overriding this point's datum.
1302
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
1303
+ point (C{bool}).
1304
+ @kwarg name_caps: Optional C{B{name}=str} and C{caps}, see L{RhumbLine}
1305
+ or L{RhumbLineAux} C{B{caps}}.
1306
+
1307
+ @return: A C{RhumbLine} instance.
1308
+
1309
+ @raise TypeError: Invalid B{C{radius}} or B{C{other}} not C{scalar} nor
1310
+ this C{LatLon}.
1311
+
1312
+ @see: Modules L{rhumb.aux_} and L{rhumb.ekx}.
1313
+ '''
1314
+ r, _, Cs = self._rhumb3(exact, radius)
1315
+ kwds = _xkwds(name_caps, name=self.name, caps=Cs.LINE_OFF)
1316
+ rl = r._DirectLine( self, other, **kwds) if _isDegrees(other) else \
1317
+ r._InverseLine(self, self.others(other), wrap, **kwds)
1318
+ return rl
1319
+
1320
+ def rhumbMidpointTo(self, other, exact=False, radius=None,
1321
+ height=None, fraction=_0_5, wrap=False):
1322
+ '''Return the (loxodromic) midpoint on the rhumb line between this and
1323
+ an other point.
1324
+
1325
+ @arg other: The other point (this C{LatLon}).
1326
+ @kwarg exact: Exact C{Rhumb...} to use (C{bool} or C{Rhumb...}), see
1327
+ method L{Ellipsoid.rhumb_}.
1328
+ @kwarg radius: Optional earth radius (C{meter}) or earth model (L{Datum},
1329
+ L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}), overriding
1330
+ this point's datum.
1331
+ @kwarg height: Optional height, overriding the mean height (C{meter}).
1332
+ @kwarg fraction: Midpoint location from this point (C{scalar}), 0 for this,
1333
+ 1 for the B{C{other}}, 0.5 for halfway between this and
1334
+ the B{C{other}} point, may be negative or greater than 1.
1335
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
1336
+ point (C{bool}).
1337
+
1338
+ @return: The midpoint at the given B{C{fraction}} along the rhumb line
1339
+ (this C{LatLon}).
1340
+
1341
+ @raise TypeError: The B{C{other}} point is incompatible or B{C{radius}}
1342
+ is invalid.
1343
+
1344
+ @raise ValueError: Invalid B{C{height}} or B{C{fraction}}.
1345
+ '''
1346
+ r, D, _ = self._rhumb3(exact, radius)
1347
+ f = Scalar(fraction=fraction)
1348
+ d = r._Inverse(self, self.others(other), wrap) # C.AZIMUTH_DISTANCE
1349
+ d = r._Direct( self, d.azi12, d.s12 * f)
1350
+ h = self._havg(other, f=f, h=height)
1351
+ return self.classof(d.lat2, d.lon2, datum=D, height=h)
1352
+
1353
+ @property_RO
1354
+ def sphericalLatLon(self):
1355
+ '''Get the C{LatLon type} iff spherical, overloaded in L{LatLonSphericalBase}.
1356
+ '''
1357
+ return False
1358
+
1359
+ def thomasTo(self, other, wrap=False):
1360
+ '''Compute the distance between this and an other point using
1361
+ U{Thomas'<https://apps.DTIC.mil/dtic/tr/fulltext/u2/703541.pdf>}
1362
+ formula.
1363
+
1364
+ @arg other: The other point (C{LatLon}).
1365
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
1366
+ the B{C{other}} point (C{bool}).
1367
+
1368
+ @return: Distance (C{meter}, same units as the axes of
1369
+ this point's datum ellipsoid).
1370
+
1371
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
1372
+
1373
+ @see: Function L{pygeodesy.thomas} and methods L{cosineAndoyerLambertTo},
1374
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
1375
+ L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
1376
+ L{flatPolarTo}, L{haversineTo} and L{vincentysTo}.
1377
+ '''
1378
+ return self._distanceTo_(self._formy.thomas_, other, wrap=wrap)
1379
+
1380
+ @deprecated_method
1381
+ def to2ab(self): # PYCHOK no cover
1382
+ '''DEPRECATED, use property L{philam}.'''
1383
+ return self.philam
1384
+
1385
+ def toCartesian(self, height=None, Cartesian=None, **Cartesian_kwds):
1386
+ '''Convert this point to cartesian, I{geocentric} coordinates,
1387
+ also known as I{Earth-Centered, Earth-Fixed} (ECEF).
1388
+
1389
+ @kwarg height: Optional height, overriding this point's height
1390
+ (C{meter}, conventionally).
1391
+ @kwarg Cartesian: Optional class to return the geocentric
1392
+ coordinates (C{Cartesian}) or C{None}.
1393
+ @kwarg Cartesian_kwds: Optional, additional B{C{Cartesian}}
1394
+ keyword arguments, ignored if
1395
+ C{B{Cartesian} is None}.
1396
+
1397
+ @return: A B{C{Cartesian}} or if B{C{Cartesian}} is C{None},
1398
+ an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
1399
+ datum)} with C{C=0} and C{M} if available.
1400
+
1401
+ @raise TypeError: Invalid B{C{Cartesian}} or B{C{Cartesian_kwds}}.
1402
+
1403
+ @see: Methods C{toNvector}, C{toVector} and C{toVector3d}.
1404
+ '''
1405
+ r = self._ecef9 if height is None else self.toEcef(height=height)
1406
+ if Cartesian is not None: # class or .classof
1407
+ r = Cartesian(r, **_xkwds(Cartesian_kwds, name=self.name))
1408
+ _xdatum(r.datum, self.datum)
1409
+ return r
1410
+
1411
+ def _toCartesianEcef(self, height=None, i=None, up=2, **name_point):
1412
+ '''(INTERNAL) Convert to cartesian and check Ecef's before and after.
1413
+ '''
1414
+ p = self.others(up=up, **name_point)
1415
+ c = p.toCartesian(height=height)
1416
+ E = self.Ecef
1417
+ if E:
1418
+ for p in (p, c):
1419
+ e = _xattr(p, Ecef=None)
1420
+ if e not in (None, E): # PYCHOK no cover
1421
+ n = Fmt.INDEX(_xkwds_item2(name_point)[0], i)
1422
+ raise _ValueError(n, e, txt=_incompatible(E.__name__))
1423
+ return c
1424
+
1425
+ def toDatum(self, datum2, height=None, name=NN):
1426
+ '''I{Must be overloaded}.'''
1427
+ notOverloaded(self, datum2, height=height, name=name)
1428
+
1429
+ def toEcef(self, height=None, M=False):
1430
+ '''Convert this point to I{geocentric} coordinates, also known as
1431
+ I{Earth-Centered, Earth-Fixed} (U{ECEF<https://WikiPedia.org/wiki/ECEF>}).
1432
+
1433
+ @kwarg height: Optional height, overriding this point's height
1434
+ (C{meter}, conventionally).
1435
+ @kwarg M: Optionally, include the rotation L{EcefMatrix} (C{bool}).
1436
+
1437
+ @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}
1438
+ with C{C=0} and C{M} if available.
1439
+
1440
+ @raise EcefError: A C{.datum} or an ECEF issue.
1441
+ '''
1442
+ return self._ecef9 if height in (None, self.height) else \
1443
+ self._Ecef_forward(self.lat, self.lon, height=height, M=M)
1444
+
1445
+ @deprecated_method
1446
+ def to3llh(self, height=None): # PYCHOK no cover
1447
+ '''DEPRECATED, use property L{latlonheight} or C{latlon.to3Tuple(B{height})}.'''
1448
+ return self.latlonheight if height in (None, self.height) else \
1449
+ self.latlon.to3Tuple(height)
1450
+
1451
+ def toLocal(self, Xyz=None, ltp=None, **Xyz_kwds):
1452
+ '''Convert this I{geodetic} point to I{local} C{X}, C{Y} and C{Z}.
1453
+
1454
+ @kwarg Xyz: Optional class to return C{X}, C{Y} and C{Z}
1455
+ (L{XyzLocal}, L{Enu}, L{Ned}) or C{None}.
1456
+ @kwarg ltp: The I{local tangent plane} (LTP) to use,
1457
+ overriding this point's LTP (L{Ltp}).
1458
+ @kwarg Xyz_kwds: Optional, additional B{C{Xyz}} keyword
1459
+ arguments, ignored if C{B{Xyz} is None}.
1460
+
1461
+ @return: An B{C{Xyz}} instance or if C{B{Xyz} is None},
1462
+ a L{Local9Tuple}C{(x, y, z, lat, lon, height,
1463
+ ltp, ecef, M)} with C{M=None}, always.
1464
+
1465
+ @raise TypeError: Invalid B{C{ltp}}.
1466
+ '''
1467
+ p = self._ltp._xLtp(ltp, self._Ltp)
1468
+ return p._ecef2local(self._ecef9, Xyz, Xyz_kwds)
1469
+
1470
+ def toLtp(self, Ecef=None):
1471
+ '''Return the I{local tangent plane} (LTP) for this point.
1472
+
1473
+ @kwarg Ecef: Optional ECEF I{class} (L{EcefKarney}, ...
1474
+ L{EcefYou}), overriding this point's C{Ecef}.
1475
+ '''
1476
+ return self._Ltp if Ecef in (None, self.Ecef) else self._ltp.Ltp(
1477
+ self, ecef=Ecef(self.datum), name=self.name)
1478
+
1479
+ def toNormal(self, deep=False, name=NN):
1480
+ '''Get this point I{normalized} to C{abs(lat) <= 90}
1481
+ and C{abs(lon) <= 180}.
1482
+
1483
+ @kwarg deep: If C{True} make a deep, otherwise a
1484
+ shallow copy (C{bool}).
1485
+ @kwarg name: Optional name of the copy (C{str}).
1486
+
1487
+ @return: A copy of this point, I{normalized} and
1488
+ optionally renamed (C{LatLon}).
1489
+
1490
+ @see: Property L{isnormal}, method L{normal} and function
1491
+ L{pygeodesy.normal}.
1492
+ '''
1493
+ ll = self.copy(deep=deep)
1494
+ _ = ll.normal()
1495
+ if name:
1496
+ ll.rename(name)
1497
+ return ll
1498
+
1499
+ def toNvector(self, h=None, Nvector=None, **Nvector_kwds):
1500
+ '''Convert this point to C{n-vector} (normal to the earth's surface)
1501
+ components, I{including height}.
1502
+
1503
+ @kwarg h: Optional height, overriding this point's height (C{meter}).
1504
+ @kwarg Nvector: Optional class to return the C{n-vector} components
1505
+ (C{Nvector}) or C{None}.
1506
+ @kwarg Nvector_kwds: Optional, additional B{C{Nvector}} keyword
1507
+ arguments, ignored if C{B{Nvector} is None}.
1508
+
1509
+ @return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if
1510
+ B{C{Nvector}} is C{None}.
1511
+
1512
+ @raise TypeError: Invalid B{C{h}}, B{C{Nvector}} or B{C{Nvector_kwds}}
1513
+ item.
1514
+
1515
+ @see: Methods C{toCartesian}, C{toVector} and C{toVector3d}.
1516
+ '''
1517
+ h = self._heigHt(h)
1518
+ if Nvector is None:
1519
+ r = self._n_xyz3.to4Tuple(h)
1520
+ else:
1521
+ x, y, z = self._n_xyz3
1522
+ r = Nvector(x, y, z, h=h, ll=self, **_xkwds(Nvector_kwds, name=self.name))
1523
+ return r
1524
+
1525
+ def toStr(self, form=F_DMS, joined=_COMMASPACE_, m=_m_, **prec_sep_s_D_M_S): # PYCHOK expected
1526
+ '''Convert this point to a "lat, lon[, +/-height]" string, formatted
1527
+ in the given C{B{form}at}.
1528
+
1529
+ @kwarg form: The lat-/longitude C{B{form}at} to use (C{str}), see
1530
+ functions L{pygeodesy.latDMS} or L{pygeodesy.lonDMS}.
1531
+ @kwarg joined: Separator to join the lat-, longitude and heigth
1532
+ strings (C{str} or C{None} or C{NN} for non-joined).
1533
+ @kwarg m: Optional unit of the height (C{str}), use C{None} to
1534
+ exclude height from the returned string.
1535
+ @kwarg prec_sep_s_D_M_S: Optional C{B{prec}ision}, C{B{sep}arator},
1536
+ B{C{s_D}}, B{C{s_M}}, B{C{s_S}} and B{C{s_DMS}} keyword
1537
+ arguments, see function L{pygeodesy.latDMS} or
1538
+ L{pygeodesy.lonDMS}.
1539
+
1540
+ @return: This point in the specified C{B{form}at}, etc. (C{str} or
1541
+ a 2- or 3-tuple C{(lat_str, lon_str[, height_str])} if
1542
+ C{B{joined}=NN} or C{B{joined}=None}).
1543
+
1544
+ @see: Function L{pygeodesy.latDMS} or L{pygeodesy.lonDMS} for more
1545
+ details about keyword arguments C{B{form}at}, C{B{prec}ision},
1546
+ C{B{sep}arator}, B{C{s_D}}, B{C{s_M}}, B{C{s_S}} and B{C{s_DMS}}.
1547
+ '''
1548
+ t = (latDMS(self.lat, form=form, **prec_sep_s_D_M_S),
1549
+ lonDMS(self.lon, form=form, **prec_sep_s_D_M_S))
1550
+ if self.height and m is not None:
1551
+ t += (self.heightStr(m=m),)
1552
+ return joined.join(t) if joined else t
1553
+
1554
+ def toVector(self, Vector=None, **Vector_kwds):
1555
+ '''Convert this point to a C{Vector} with the I{geocentric} C{(x,
1556
+ y, z)} (ECEF) coordinates, I{ignoring height}.
1557
+
1558
+ @kwarg Vector: Optional class to return the I{geocentric}
1559
+ components (L{Vector3d}) or C{None}.
1560
+ @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
1561
+ arguments, ignored if C{B{Vector} is None}.
1562
+
1563
+ @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)}
1564
+ if B{C{Vector}} is C{None}.
1565
+
1566
+ @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}} item.
1567
+
1568
+ @see: Methods C{toCartesian}, C{toNvector} and C{toVector3d}.
1569
+ '''
1570
+ return self._ecef9.toVector(Vector=Vector, **Vector_kwds)
1571
+
1572
+ def toVector3d(self, norm=True, **Vector3d_kwds):
1573
+ '''Convert this point to a L{Vector3d} with the I{geocentric} C{(x,
1574
+ y, z)} (ECEF) I{unit} coordinates, I{ignoring height}.
1575
+
1576
+ @kwarg norm: Normalize the 3-D vector (C{bool}).
1577
+ @kwarg Vector3d_kwds: Optional L{Vector3d} keyword arguments.
1578
+
1579
+ @return: Unit vector (L{Vector3d}).
1580
+
1581
+ @raise TypeError: Invalid B{C{Vector3d_kwds}} item.
1582
+
1583
+ @see: Methods C{toCartesian}, C{toNvector} and C{toVector}.
1584
+ '''
1585
+ r = self.toVector(Vector=Vector3d, **Vector3d_kwds)
1586
+ if norm:
1587
+ r = r.unit(ll=self)
1588
+ return r
1589
+
1590
+ def toWm(self, **toWm_kwds):
1591
+ '''Convert this point to a WM coordinate.
1592
+
1593
+ @kwarg toWm_kwds: Optional L{pygeodesy.toWm} keyword arguments.
1594
+
1595
+ @return: The WM coordinate (L{Wm}).
1596
+
1597
+ @see: Function L{pygeodesy.toWm}.
1598
+ '''
1599
+ return self._wm if not toWm_kwds else _MODS.webmercator.toWm(
1600
+ self, **_xkwds(toWm_kwds, name=self.name))
1601
+
1602
+ @deprecated_method
1603
+ def to3xyz(self): # PYCHOK no cover
1604
+ '''DEPRECATED, use property L{xyz} or method L{toNvector}, L{toVector},
1605
+ L{toVector3d} or perhaps (geocentric) L{toEcef}.'''
1606
+ return self.xyz # self.toVector()
1607
+
1608
+ def vincentysTo(self, other, **radius_wrap):
1609
+ '''Compute the distance between this and an other point using
1610
+ U{Vincenty's<https://WikiPedia.org/wiki/Great-circle_distance>}
1611
+ spherical formula.
1612
+
1613
+ @arg other: The other point (C{LatLon}).
1614
+ @kwarg radius_wrap: Optional keyword arguments for function
1615
+ L{pygeodesy.vincentys}, overriding the
1616
+ default mean C{radius} of this point's
1617
+ datum ellipsoid.
1618
+
1619
+ @return: Distance (C{meter}, same units as B{C{radius}}).
1620
+
1621
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
1622
+
1623
+ @see: Function L{pygeodesy.vincentys} and methods L{cosineAndoyerLambertTo},
1624
+ L{cosineForsytheAndoyerLambertTo}, L{cosineLawTo}, C{distanceTo*},
1625
+ L{equirectangularTo}, L{euclideanTo}, L{flatLocalTo}/L{hubenyTo},
1626
+ L{flatPolarTo}, L{haversineTo} and L{thomasTo}.
1627
+ '''
1628
+ return self._distanceTo(self._formy.vincentys, other, **_xkwds(radius_wrap, radius=None))
1629
+
1630
+ @Property_RO
1631
+ def _wm(self):
1632
+ '''(INTERNAL) Get this point as webmercator (L{Wm}).
1633
+ '''
1634
+ return _MODS.webmercator.toWm(self)
1635
+
1636
+ @property_RO
1637
+ def xyz(self):
1638
+ '''Get the I{geocentric} C{(x, y, z)} coordinates (L{Vector3Tuple}C{(x, y, z)})
1639
+ '''
1640
+ return self._ecef9.xyz
1641
+
1642
+ @Property_RO
1643
+ def xyzh(self):
1644
+ '''Get the I{geocentric} C{(x, y, z)} coordinates and height (L{Vector4Tuple}C{(x, y, z, h)})
1645
+ '''
1646
+ return self.xyz.to4Tuple(self.height)
1647
+
1648
+
1649
+ class _toCartesian3(object): # see also .formy._idllmn6, .geodesicw._wargs, .vector2d._numpy
1650
+ '''(INTERNAL) Wrapper to convert 2 other points.
1651
+ '''
1652
+ @contextmanager # <https://www.Python.org/dev/peps/pep-0343/> Examples
1653
+ def __call__(self, p, p2, p3, wrap, **kwds):
1654
+ try:
1655
+ if wrap:
1656
+ p2, p3 = map1(_Wrap.point, p2, p3)
1657
+ kwds = _xkwds(kwds, wrap=wrap)
1658
+ yield (p. toCartesian().copy(name=_point_), # copy to rename
1659
+ p._toCartesianEcef(up=4, point2=p2),
1660
+ p._toCartesianEcef(up=4, point3=p3))
1661
+ except (AssertionError, TypeError, ValueError) as x: # Exception?
1662
+ raise _xError(x, point=p, point2=p2, point3=p3, **kwds)
1663
+
1664
+ _toCartesian3 = _toCartesian3() # PYCHOK singleton
1665
+
1666
+
1667
+ def _latlonheight3(latlonh, height, wrap): # in .points.LatLon_.__init__
1668
+ '''(INTERNAL) Get 3-tuple C{(lat, lon, height)}.
1669
+ '''
1670
+ try:
1671
+ lat, lon = latlonh.lat, latlonh.lon
1672
+ height = _xattr(latlonh, height=height)
1673
+ except AttributeError:
1674
+ raise _IsnotError(_LatLon_, latlonh=latlonh)
1675
+ if wrap:
1676
+ lat, lon = _Wrap.latlon(lat, lon)
1677
+ return lat, lon, height
1678
+
1679
+
1680
+ def _trilaterate5(p1, d1, p2, d2, p3, d3, area=True, eps=EPS1, # MCCABE 13
1681
+ radius=R_M, wrap=False):
1682
+ '''(INTERNAL) Trilaterate three points by I{area overlap} or by
1683
+ I{perimeter intersection} of three circles.
1684
+
1685
+ @note: The B{C{radius}} is only needed for the n-vectorial and
1686
+ C{sphericalTrigonometry.LatLon.distanceTo} methods and
1687
+ silently ignored by the C{ellipsoidalExact}, C{-GeodSolve},
1688
+ C{-Karney} and C{-Vincenty.LatLon.distanceTo} methods.
1689
+ '''
1690
+ p2, p3, w = _unrollon3(p1, p2, p3, wrap)
1691
+
1692
+ r1 = Distance_(distance1=d1)
1693
+ r2 = Distance_(distance2=d2)
1694
+ r3 = Distance_(distance3=d3)
1695
+ m = 0 if area else (r1 + r2 + r3)
1696
+ pc = 0
1697
+ t = []
1698
+ for _ in range(3):
1699
+ try: # intersection of circle (p1, r1) and (p2, r2)
1700
+ c1, c2 = p1.intersections2(r1, p2, r2, wrap=w)
1701
+
1702
+ if area: # check overlap
1703
+ if c1 is c2: # abutting
1704
+ c = c1
1705
+ else: # nearest point on radical
1706
+ c = p3.nearestOn(c1, c2, within=True, wrap=w)
1707
+ d = r3 - p3.distanceTo(c, radius=radius, wrap=w)
1708
+ if d > eps: # sufficient overlap
1709
+ t.append((d, c))
1710
+ m = max(m, d)
1711
+
1712
+ else: # check intersection
1713
+ for c in ((c1,) if c1 is c2 else (c1, c2)):
1714
+ d = fabs(r3 - p3.distanceTo(c, radius=radius, wrap=w))
1715
+ if d < eps: # below margin
1716
+ t.append((d, c))
1717
+ m = min(m, d)
1718
+
1719
+ except IntersectionError as x:
1720
+ if _concentric_ in str(x): # XXX ConcentricError?
1721
+ pc += 1
1722
+
1723
+ p1, r1, p2, r2, p3, r3 = p2, r2, p3, r3, p1, r1 # rotate
1724
+
1725
+ if t: # get min, max, points and count ...
1726
+ t = tuple(sorted(t))
1727
+ n = len(t), # as 1-tuple
1728
+ # ... or for a single trilaterated result,
1729
+ # min *is* max, min- *is* maxPoint and n=1, 2 or 3
1730
+ return Trilaterate5Tuple(t[0] + t[-1] + n) # *(t[0] + ...)
1731
+
1732
+ elif area and pc == 3: # all pairwise concentric ...
1733
+ r, p = min((r1, p1), (r2, p2), (r3, p3))
1734
+ m = max(r1, r2, r3)
1735
+ # ... return "smallest" point twice, the smallest
1736
+ # and largest distance and n=0 for concentric
1737
+ return Trilaterate5Tuple(float(r), p, float(m), p, 0)
1738
+
1739
+ n, f = (_overlap_, max) if area else (_intersection_, min)
1740
+ t = _COMMASPACE_(_no_(n), '%s %.3g' % (f.__name__, m))
1741
+ raise IntersectionError(area=area, eps=eps, wrap=wrap, txt=t)
1742
+
1743
+
1744
+ __all__ += _ALL_DOCS(LatLonBase)
1745
+
1746
+ # **) MIT License
1747
+ #
1748
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1749
+ #
1750
+ # Permission is hereby granted, free of charge, to any person obtaining a
1751
+ # copy of this software and associated documentation files (the "Software"),
1752
+ # to deal in the Software without restriction, including without limitation
1753
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1754
+ # and/or sell copies of the Software, and to permit persons to whom the
1755
+ # Software is furnished to do so, subject to the following conditions:
1756
+ #
1757
+ # The above copyright notice and this permission notice shall be included
1758
+ # in all copies or substantial portions of the Software.
1759
+ #
1760
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1761
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1762
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1763
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1764
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1765
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1766
+ # OTHER DEALINGS IN THE SOFTWARE.