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,1224 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''(INTERNAL) Private ellipsoidal base classes C{CartesianEllipsoidalBase}
5
+ and C{LatLonEllipsoidalBase}.
6
+
7
+ A pure Python implementation of geodesy tools for ellipsoidal earth models,
8
+ transcoded in part from JavaScript originals by I{(C) Chris Veness 2005-2016}
9
+ and published under the same MIT Licence**, see for example U{latlon-ellipsoidal
10
+ <https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html>}.
11
+ '''
12
+ # make sure int/int division yields float quotient, see .basics
13
+ from __future__ import division as _; del _ # PYCHOK semicolon
14
+
15
+ # from pygeodesy.basics import _xinstanceof # from .datums
16
+ from pygeodesy.constants import EPS, EPS0, EPS1, _0_0, _0_5
17
+ from pygeodesy.cartesianBase import CartesianBase # PYCHOK used!
18
+ from pygeodesy.datums import Datum, Datums, _earth_ellipsoid, _ellipsoidal_datum, \
19
+ Transform, _WGS84, _EWGS84, _xinstanceof # _spherical_datum
20
+ # from pygeodesy.ellipsoids import _EWGS84 # from .datums
21
+ from pygeodesy.errors import _incompatible, _IsnotError, RangeError, _TypeError, \
22
+ _ValueError, _xattr, _xellipsoidal, _xError, _xkwds, \
23
+ _xkwds_not
24
+ # from pygeodesy.fmath import favg # _MODS
25
+ from pygeodesy.interns import NN, _COMMA_, _ellipsoidal_
26
+ from pygeodesy.latlonBase import LatLonBase, _trilaterate5, fabs, _Wrap
27
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
28
+ # from pygeodesy.lcc import toLcc # _MODS
29
+ # from pygeodesy.named import notOverloaded # _MODS
30
+ # from pygeodesy.namedTuples import Vector3Tuple # _MODS
31
+ from pygeodesy.props import deprecated_method, deprecated_property_RO, \
32
+ Property_RO, property_doc_, property_RO, _update_all
33
+ # from pygeodesy.trf import _eT0Ds4 # _MODS
34
+ from pygeodesy.units import Epoch, _isDegrees, Radius_, _1mm as _TOL_M
35
+ # from pygeodesy.utily import _Wrap # from .latlonBase
36
+
37
+ # from math import fabs # from .latlonBase
38
+
39
+ __all__ = _ALL_LAZY.ellipsoidalBase
40
+ __version__ = '24.03.09'
41
+
42
+
43
+ class CartesianEllipsoidalBase(CartesianBase):
44
+ '''(INTERNAL) Base class for ellipsoidal C{Cartesian}s.
45
+ '''
46
+ _datum = _WGS84 # L{Datum}
47
+ _epoch = None # overriding .reframe.epoch (C{float})
48
+ _reframe = None # reference frame (L{RefFrame})
49
+
50
+ def __init__(self, x_xyz, y=None, z=None, datum=None, reframe=None,
51
+ epoch=None, ll=None, name=NN):
52
+ '''New ellispoidal C{Cartesian...}.
53
+
54
+ @kwarg reframe: Optional reference frame (L{RefFrame}).
55
+ @kwarg epoch: Optional epoch to observe for B{C{reframe}} (C{scalar}),
56
+ a non-zero, fractional calendar year; silently ignored
57
+ if C{B{reframe}=None}.
58
+
59
+ @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}} coordinate
60
+ or B{C{x_xyz}} not a C{Cartesian} L{Ecef9Tuple},
61
+ L{Vector3Tuple} or L{Vector4Tuple} or B{C{datum}} is
62
+ not a L{Datum}, B{C{reframe}} is not a L{RefFrame} or
63
+ B{C{epoch}} is not C{scalar} non-zero.
64
+
65
+ @see: Super-class L{CartesianBase<CartesianBase.__init__>} for more details.
66
+ '''
67
+ CartesianBase.__init__(self, x_xyz, y=y, z=z, datum=datum, ll=ll, name=name)
68
+ if reframe:
69
+ self.reframe = reframe
70
+ self.epoch = epoch
71
+
72
+ # def __matmul__(self, other): # PYCHOK Python 3.5+
73
+ # '''Return C{NotImplemented} for C{c_ = c @ datum}, C{c_ = c @ reframe} and C{c_ = c @ Transform}.
74
+ # '''
75
+ # RefFrame = _MODS.trf.RefFrame
76
+ # return NotImplemented if isinstance(other, (Datum, RefFrame, Transform)) else \
77
+ # _NotImplemented(self, other)
78
+
79
+ @deprecated_method
80
+ def convertRefFrame(self, reframe2, reframe, epoch=None):
81
+ '''DEPRECATED, use method L{toRefFrame}.'''
82
+ return self.toRefFrame(reframe2, reframe=reframe, epoch=epoch)
83
+
84
+ @property_RO
85
+ def ellipsoidalCartesian(self):
86
+ '''Get this C{Cartesian}'s ellipsoidal class.
87
+ '''
88
+ return type(self)
89
+
90
+ @property_doc_(''' this cartesian's observed or C{reframe} epoch (C{float}).''')
91
+ def epoch(self):
92
+ '''Get this cartesian's observed or C{reframe} epoch (C{Epoch}) or C{None}.
93
+ '''
94
+ return self._epoch or (self.reframe.epoch if self.reframe else None)
95
+
96
+ @epoch.setter # PYCHOK setter!
97
+ def epoch(self, epoch):
98
+ '''Set or clear this cartesian's observed epoch, a fractional
99
+ calendar year (L{Epoch}, C{scalar} or C{str}) or C{None}.
100
+
101
+ @raise TRFError: Invalid B{C{epoch}}.
102
+ '''
103
+ self._epoch = None if epoch is None else Epoch(epoch)
104
+
105
+ def intersections2(self, radius, center2, radius2, sphere=True,
106
+ Vector=None, **Vector_kwds):
107
+ '''Compute the intersection of two spheres or circles, each defined by a
108
+ cartesian center point and a radius.
109
+
110
+ @arg radius: Radius of this sphere or circle (same units as this point's
111
+ coordinates).
112
+ @arg center2: Center of the second sphere or circle (C{Cartesian}, L{Vector3d},
113
+ C{Vector3Tuple} or C{Vector4Tuple}).
114
+ @arg radius2: Radius of the second sphere or circle (same units as this and
115
+ the B{C{other}} point's coordinates).
116
+ @kwarg sphere: If C{True} compute the center and radius of the intersection
117
+ of two I{spheres}. If C{False}, ignore the C{z}-component and
118
+ compute the intersection of two I{circles} (C{bool}).
119
+ @kwarg Vector: Class to return intersections (C{Cartesian}, L{Vector3d} or
120
+ C{Vector3Tuple}) or C{None} for an instance of this (sub-)class.
121
+ @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments,
122
+ ignored if C{B{Vector} is None}.
123
+
124
+ @return: If B{C{sphere}} is C{True}, a 2-tuple of the C{center} and C{radius}
125
+ of the intersection of the I{spheres}. The C{radius} is C{0.0} for
126
+ abutting spheres (and the C{center} is aka the I{radical center}).
127
+
128
+ If B{C{sphere}} is C{False}, a 2-tuple with the two intersection
129
+ points of the I{circles}. For abutting circles, both points are
130
+ the same instance, aka the I{radical center}.
131
+
132
+ @raise IntersectionError: Concentric, invalid or non-intersecting spheres or circles.
133
+
134
+ @raise TypeError: Invalid B{C{center2}}.
135
+
136
+ @raise UnitError: Invalid B{C{radius}} or B{C{radius2}}.
137
+
138
+ @see: U{Sphere-Sphere<https://MathWorld.Wolfram.com/Sphere-SphereIntersection.html>},
139
+ U{Circle-Circle<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>}
140
+ Intersection and function L{pygeodesy.radical2}.
141
+ '''
142
+ try:
143
+ return _MODS.vector3d._intersects2(self, Radius_(radius=radius),
144
+ center2, Radius_(radius2=radius2),
145
+ sphere=sphere, clas=self.classof,
146
+ Vector=Vector, **Vector_kwds)
147
+ except (TypeError, ValueError) as x:
148
+ raise _xError(x, center=self, radius=radius, center2=center2, radius2=radius2)
149
+
150
+ @property_doc_(''' this cartesian's reference frame (L{RefFrame}).''')
151
+ def reframe(self):
152
+ '''Get this cartesian's reference frame (L{RefFrame}) or C{None}.
153
+ '''
154
+ return self._reframe
155
+
156
+ @reframe.setter # PYCHOK setter!
157
+ def reframe(self, reframe):
158
+ '''Set or clear this cartesian's reference frame (L{RefFrame}) or C{None}.
159
+
160
+ @raise TypeError: The B{C{reframe}} is not a L{RefFrame}.
161
+ '''
162
+ _set_reframe(self, reframe)
163
+
164
+ def toLatLon(self, datum=None, height=None, **LatLon_and_kwds): # PYCHOK signature
165
+ '''Convert this cartesian to a I{geodetic} (lat-/longitude) point.
166
+
167
+ @see: Method L{toLatLon<cartesianBase.CartesianBase.toLatLon>}
168
+ for further details.
169
+ '''
170
+ kwds = LatLon_and_kwds
171
+ if kwds:
172
+ kwds = _xkwds(kwds, reframe=self.reframe, epoch=self.epoch)
173
+ return CartesianBase.toLatLon(self, datum=datum, height=height, **kwds)
174
+
175
+ def toRefFrame(self, reframe2, reframe=None, epoch=None, epoch2=None, name=NN):
176
+ '''Convert this point to an other reference frame and epoch.
177
+
178
+ @arg reframe2: Reference frame to convert I{to} (L{RefFrame}).
179
+ @kwarg reframe: Optional reference frame to convert I{from} (L{RefFrame}),
180
+ overriding this point's reference frame.
181
+ @kwarg epoch: Optional epoch (L{Epoch}, C{scalar} or C{str}), overriding
182
+ this point's C{epoch or B{reframe}.epoch}.
183
+ @kwarg epoch2: Optional epoch to observe for the converted point (L{Epoch},
184
+ C{scalar} or C{str}), otherwise B{C{epoch}}.
185
+ @kwarg name: Optional name (C{str}), C{B{reframe2}.name} iff converted.
186
+
187
+ @return: The converted point (ellipsoidal C{Cartesian}) or if conversion
188
+ C{isunity}, this point or a copy of this point if the B{C{name}}
189
+ is non-empty.
190
+
191
+ @raise TRFError: This point's C{reframe} is not defined, invalid B{C{epoch}}
192
+ or B{C{epoch2}} or conversion from this point's C{reframe}
193
+ to B{C{reframe2}} is not available.
194
+
195
+ @raise TypeError: B{C{reframe2}} or B{C{reframe}} not a L{RefFrame}.
196
+ '''
197
+ return _MODS.trf._toRefFrame(self, reframe2, reframe=reframe, epoch=epoch,
198
+ epoch2=epoch2, name=name)
199
+
200
+ @deprecated_method
201
+ def toTransforms_(self, *transforms, **datum): # PYCHOK no cover
202
+ '''DEPRECATED on 2024.02.14, use method C{toTransform}.'''
203
+ r = self
204
+ for t in transforms:
205
+ r = r.toTransform(t)
206
+ return r.dup(**datum) if datum else r
207
+
208
+
209
+ class LatLonEllipsoidalBase(LatLonBase):
210
+ '''(INTERNAL) Base class for ellipsoidal C{LatLon}s.
211
+ '''
212
+ _datum = _WGS84 # L{Datum}
213
+ _elevation2to = None # _elevation2 timeout (C{secs})
214
+ _epoch = None # overriding .reframe.epoch (C{float})
215
+ _gamma = None # UTM/UPS meridian convergence (C{degrees})
216
+ _geoidHeight2to = None # _geoidHeight2 timeout (C{secs})
217
+ _reframe = None # reference frame (L{RefFrame})
218
+ _scale = None # UTM/UPS scale factor (C{float})
219
+ _toLLEB_args = () # Etm/Utm/Ups._toLLEB arguments
220
+
221
+ def __init__(self, latlonh, lon=None, height=0, datum=None, reframe=None,
222
+ epoch=None, wrap=False, name=NN):
223
+ '''Create an ellipsoidal C{LatLon} point from the given lat-, longitude
224
+ and height on the given datum, reference frame and epoch.
225
+
226
+ @arg latlonh: Latitude (C{degrees} or DMS C{str} with N or S suffix) or
227
+ a previous C{LatLon} instance provided C{B{lon}=None}.
228
+ @kwarg lon: Longitude (C{degrees} or DMS C{str} with E or W suffix) or
229
+ C(None), indicating B{C{latlonh}} is a C{LatLon}.
230
+ @kwarg height: Optional height above (or below) the earth surface
231
+ (C{meter}, same units as the datum's ellipsoid axes).
232
+ @kwarg datum: Optional, ellipsoidal datum to use (L{Datum}, L{Ellipsoid},
233
+ L{Ellipsoid2} or L{a_f2Tuple}).
234
+ @kwarg reframe: Optional reference frame (L{RefFrame}).
235
+ @kwarg epoch: Optional epoch to observe for B{C{reframe}} (C{scalar}),
236
+ a non-zero, fractional calendar year; silently ignored
237
+ if C{B{reframe}=None}.
238
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{lat}} and B{C{lon}}
239
+ (C{bool}).
240
+ @kwarg name: Optional name (C{str}).
241
+
242
+ @raise RangeError: Value of C{lat} or B{C{lon}} outside the valid
243
+ range and L{rangerrors} set to C{True}.
244
+
245
+ @raise TypeError: If B{C{latlonh}} is not a C{LatLon}, B{C{datum}} is
246
+ not a L{Datum}, B{C{reframe}} is not a L{RefFrame}
247
+ or B{C{epoch}} is not C{scalar} non-zero.
248
+
249
+ @raise UnitError: Invalid B{C{lat}}, B{C{lon}} or B{C{height}}.
250
+ '''
251
+ LatLonBase.__init__(self, latlonh, lon=lon, height=height, wrap=wrap, name=name)
252
+ if datum not in (None, self._datum, _EWGS84):
253
+ self.datum = _ellipsoidal_datum(datum, name=name)
254
+ if reframe:
255
+ self.reframe = reframe
256
+ self.epoch = epoch
257
+
258
+ # def __matmul__(self, other): # PYCHOK Python 3.5+
259
+ # '''Return C{NotImplemented} for C{ll_ = ll @ datum} and C{ll_ = ll @ reframe}.
260
+ # '''
261
+ # RefFrame = _MODS.trf.RefFrame
262
+ # return NotImplemented if isinstance(other, (Datum, RefFrame)) else \
263
+ # _NotImplemented(self, other)
264
+
265
+ def antipode(self, height=None):
266
+ '''Return the antipode, the point diametrically opposite
267
+ to this point.
268
+
269
+ @kwarg height: Optional height of the antipode, height
270
+ of this point otherwise (C{meter}).
271
+
272
+ @return: The antipodal point (C{LatLon}).
273
+ '''
274
+ lla = LatLonBase.antipode(self, height=height)
275
+ if lla.datum != self.datum:
276
+ lla.datum = self.datum
277
+ return lla
278
+
279
+ @deprecated_property_RO
280
+ def convergence(self):
281
+ '''DEPRECATED, use property C{gamma}.'''
282
+ return self.gamma
283
+
284
+ @deprecated_method
285
+ def convertDatum(self, datum2):
286
+ '''DEPRECATED, use method L{toDatum}.'''
287
+ return self.toDatum(datum2)
288
+
289
+ @deprecated_method
290
+ def convertRefFrame(self, reframe2):
291
+ '''DEPRECATED, use method L{toRefFrame}.'''
292
+ return self.toRefFrame(reframe2)
293
+
294
+ @Property_RO
295
+ def _css(self):
296
+ '''(INTERNAL) Get this C{LatLon} point as a Cassini-Soldner location (L{Css}).
297
+ '''
298
+ css = _MODS.css
299
+ return css.toCss(self, height=self.height, Css=css.Css, name=self.name)
300
+
301
+ @property_doc_(''' this points's datum (L{Datum}).''')
302
+ def datum(self):
303
+ '''Get this point's datum (L{Datum}).
304
+ '''
305
+ return self._datum
306
+
307
+ @datum.setter # PYCHOK setter!
308
+ def datum(self, datum):
309
+ '''Set this point's datum I{without conversion} (L{Datum}).
310
+
311
+ @raise TypeError: The B{C{datum}} is not a L{Datum}
312
+ or not ellipsoidal.
313
+ '''
314
+ _xinstanceof(Datum, datum=datum)
315
+ if not datum.isEllipsoidal:
316
+ raise _IsnotError(_ellipsoidal_, datum=datum)
317
+ if self._datum != datum:
318
+ _update_all(self)
319
+ self._datum = datum
320
+
321
+ def distanceTo2(self, other, wrap=False):
322
+ '''I{Approximate} the distance and (initial) bearing between this
323
+ and an other (ellipsoidal) point based on the radii of curvature.
324
+
325
+ I{Suitable only for short distances up to a few hundred Km
326
+ or Miles and only between points not near-polar}.
327
+
328
+ @arg other: The other point (C{LatLon}).
329
+ @kwarg wrap: If C{True}, wrap or I{normalize} the B{C{other}}
330
+ point (C{bool}).
331
+
332
+ @return: An L{Distance2Tuple}C{(distance, initial)}.
333
+
334
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
335
+
336
+ @raise ValueError: Incompatible datum ellipsoids.
337
+
338
+ @see: Method L{Ellipsoid.distance2} and U{Local, flat earth
339
+ approximation<https://www.EdWilliams.org/avform.htm#flat>}
340
+ aka U{Hubeny<https://www.OVG.AT/de/vgi/files/pdf/3781/>}
341
+ formula.
342
+ '''
343
+ p = self.others(other)
344
+ if wrap:
345
+ p = _Wrap.point(p)
346
+ E = self.ellipsoids(other)
347
+ return E.distance2(*(self.latlon + p.latlon))
348
+
349
+ @Property_RO
350
+ def _elevation2(self):
351
+ '''(INTERNAL) Get elevation and data source.
352
+ '''
353
+ return _MODS.elevations.elevation2(self.lat, self.lon,
354
+ timeout=self._elevation2to)
355
+
356
+ def elevation2(self, adjust=True, datum=None, timeout=2):
357
+ '''Return elevation of this point for its or the given datum, ellipsoid
358
+ or sphere.
359
+
360
+ @kwarg adjust: Adjust the elevation for a B{C{datum}} other than
361
+ C{NAD83} (C{bool}).
362
+ @kwarg datum: Optional datum overriding this point's datum (L{Datum},
363
+ L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple} or C{scalar}
364
+ radius).
365
+ @kwarg timeout: Optional query timeout (C{seconds}).
366
+
367
+ @return: An L{Elevation2Tuple}C{(elevation, data_source)} or
368
+ C{(None, error)} in case of errors.
369
+
370
+ @note: The adjustment applied is the difference in geocentric earth
371
+ radius between the B{C{datum}} and C{NAV83} upon which the
372
+ L{elevations.elevation2} is based.
373
+
374
+ @note: NED elevation is only available for locations within the U{Conterminous
375
+ US (CONUS)<https://WikiPedia.org/wiki/Contiguous_United_States>}.
376
+
377
+ @see: Function L{elevations.elevation2} and method C{Ellipsoid.Rgeocentric}
378
+ for further details and possible C{error}s.
379
+ '''
380
+ if self._elevation2to != timeout:
381
+ self._elevation2to = timeout
382
+ LatLonEllipsoidalBase._elevation2._update(self)
383
+ return self._Radjust2(adjust, datum, self._elevation2)
384
+
385
+ def ellipsoid(self, datum=_WGS84):
386
+ '''Return the ellipsoid of this point's datum or the given datum.
387
+
388
+ @kwarg datum: Default datum (L{Datum}).
389
+
390
+ @return: The ellipsoid (L{Ellipsoid} or L{Ellipsoid2}).
391
+ '''
392
+ return _xattr(self, datum=datum).ellipsoid
393
+
394
+ @property_RO
395
+ def ellipsoidalLatLon(self):
396
+ '''Get this C{LatLon}'s ellipsoidal class.
397
+ '''
398
+ return type(self)
399
+
400
+ def ellipsoids(self, other):
401
+ '''Check the type and ellipsoid of this and an other point's datum.
402
+
403
+ @arg other: The other point (C{LatLon}).
404
+
405
+ @return: This point's datum ellipsoid (L{Ellipsoid} or L{Ellipsoid2}).
406
+
407
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
408
+
409
+ @raise ValueError: Incompatible datum ellipsoids.
410
+ '''
411
+ self.others(other, up=2) # ellipsoids' caller
412
+
413
+ E = self.ellipsoid()
414
+ try: # other may be Sphere, etc.
415
+ e = other.ellipsoid()
416
+ except AttributeError:
417
+ try: # no ellipsoid method, try datum
418
+ e = other.datum.ellipsoid
419
+ except AttributeError:
420
+ e = E # no datum, XXX assume equivalent?
421
+ if e != E:
422
+ raise _ValueError(e.named2, txt=_incompatible(E.named2))
423
+ return E
424
+
425
+ @property_doc_(''' this point's observed or C{reframe} epoch (C{float}).''')
426
+ def epoch(self):
427
+ '''Get this point's observed or C{reframe} epoch (L{Epoch}) or C{None}.
428
+ '''
429
+ return self._epoch or (self.reframe.epoch if self.reframe else None)
430
+
431
+ @epoch.setter # PYCHOK setter!
432
+ def epoch(self, epoch):
433
+ '''Set or clear this point's observed epoch, a fractional
434
+ calendar year (L{Epoch}, C{scalar} or C{str}) or C{None}.
435
+
436
+ @raise TRFError: Invalid B{C{epoch}}.
437
+ '''
438
+ self._epoch = None if epoch is None else Epoch(epoch)
439
+
440
+ @Property_RO
441
+ def Equidistant(self):
442
+ '''Get the prefered azimuthal equidistant projection I{class} (L{EquidistantKarney} or L{EquidistantExact}).
443
+ '''
444
+ try:
445
+ _ = self.datum.ellipsoid.geodesic
446
+ return _MODS.azimuthal.EquidistantKarney
447
+ except ImportError: # no geographiclib
448
+ return _MODS.azimuthal.EquidistantExact # XXX no longer L{azimuthal.Equidistant}
449
+
450
+ @Property_RO
451
+ def _etm(self):
452
+ '''(INTERNAL) Get this C{LatLon} point as an ETM coordinate (L{pygeodesy.toEtm8}).
453
+ '''
454
+ etm = _MODS.etm
455
+ return etm.toEtm8(self, datum=self.datum, Etm=etm.Etm)
456
+
457
+ @property_RO
458
+ def gamma(self):
459
+ '''Get this point's UTM or UPS meridian convergence (C{degrees}) or
460
+ C{None} if not available or not converted from L{Utm} or L{Ups}.
461
+ '''
462
+ return self._gamma
463
+
464
+ @Property_RO
465
+ def _geoidHeight2(self):
466
+ '''(INTERNAL) Get geoid height and model.
467
+ '''
468
+ return _MODS.elevations.geoidHeight2(self.lat, self.lon, model=0,
469
+ timeout=self._geoidHeight2to)
470
+
471
+ def geoidHeight2(self, adjust=False, datum=None, timeout=2):
472
+ '''Return geoid height of this point for its or the given datum, ellipsoid
473
+ or sphere.
474
+
475
+ @kwarg adjust: Adjust the geoid height for a B{C{datum}} other than
476
+ C{NAD83/NADV88} (C{bool}).
477
+ @kwarg datum: Optional datum overriding this point's datum (L{Datum},
478
+ L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple} or C{scalar}
479
+ radius).
480
+ @kwarg timeout: Optional query timeout (C{seconds}).
481
+
482
+ @return: A L{GeoidHeight2Tuple}C{(height, model_name)} or
483
+ C{(None, error)} in case of errors.
484
+
485
+ @note: The adjustment applied is the difference in geocentric earth
486
+ radius between the B{C{datum}} and C{NAV83/NADV88} upon which
487
+ the L{elevations.geoidHeight2} is based.
488
+
489
+ @note: The geoid height is only available for locations within the U{Conterminous
490
+ US (CONUS)<https://WikiPedia.org/wiki/Contiguous_United_States>}.
491
+
492
+ @see: Function L{elevations.geoidHeight2} and method C{Ellipsoid.Rgeocentric}
493
+ for further details and possible C{error}s.
494
+ '''
495
+ if self._geoidHeight2to != timeout:
496
+ self._geoidHeight2to = timeout
497
+ LatLonEllipsoidalBase._geoidHeight2._update(self)
498
+ return self._Radjust2(adjust, datum, self._geoidHeight2)
499
+
500
+ def intermediateTo(self, other, fraction, height=None, wrap=False): # PYCHOK no cover
501
+ '''I{Must be overloaded}.'''
502
+ _MODS.named.notOverloaded(self, other, fraction, height=height, wrap=wrap)
503
+
504
+ def intersection3(self, end1, other, end2, height=None, wrap=False, # was=True
505
+ equidistant=None, tol=_TOL_M):
506
+ '''I{Iteratively} compute the intersection point of two lines, each
507
+ defined by two points or a start point and bearing from North.
508
+
509
+ @arg end1: End point of this line (C{LatLon}) or the initial
510
+ bearing at this point (compass C{degrees360}).
511
+ @arg other: Start point of the other line (C{LatLon}).
512
+ @arg end2: End point of the other line (C{LatLon}) or the initial
513
+ bearing at the other point (compass C{degrees360}).
514
+ @kwarg height: Optional height at the intersection (C{meter},
515
+ conventionally) or C{None} for the mean height.
516
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
517
+ B{C{other}} and B{C{end*}} points (C{bool}).
518
+ @kwarg equidistant: An azimuthal equidistant projection (I{class} or
519
+ function L{pygeodesy.equidistant}), or C{None}
520
+ for this point's preferred C{.Equidistant}.
521
+ @kwarg tol: Tolerance for convergence and skew line distance and
522
+ length (C{meter}, conventionally).
523
+
524
+ @return: An L{Intersection3Tuple}C{(point, outside1, outside2)}
525
+ with C{point} a C{LatLon} instance.
526
+
527
+ @raise ImportError: Package U{geographiclib
528
+ <https://PyPI.org/project/geographiclib>}
529
+ not installed or not found, but only if
530
+ C{B{equidistant}=}L{EquidistantKarney}.
531
+
532
+ @raise IntersectionError: Skew, colinear, parallel or otherwise
533
+ non-intersecting lines or no convergence
534
+ for the given B{C{tol}}.
535
+
536
+ @raise TypeError: If B{C{end1}}, B{C{other}} or B{C{end2}} point
537
+ is not C{LatLon}.
538
+
539
+ @note: For each line specified with an initial bearing, a pseudo-end
540
+ point is computed as the C{destination} along that bearing at
541
+ about 1.5 times the distance from the start point to an initial
542
+ gu-/estimate of the intersection point (and between 1/8 and 3/8
543
+ of the authalic earth perimeter).
544
+
545
+ @see: I{Karney's} U{intersect.cpp<https://SourceForge.net/p/geographiclib/
546
+ discussion/1026621/thread/21aaff9f/>}, U{The B{ellipsoidal} case<https://
547
+ GIS.StackExchange.com/questions/48937/calculating-intersection-of-two-circles>}
548
+ and U{Karney's paper<https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section
549
+ B{14. MARITIME BOUNDARIES} for more details about the iteration algorithm.
550
+ '''
551
+ try:
552
+ s2 = self.others(other)
553
+ return _MODS.ellipsoidalBaseDI._intersect3(self, end1,
554
+ s2, end2,
555
+ height=height, wrap=wrap,
556
+ equidistant=equidistant, tol=tol,
557
+ LatLon=self.classof, datum=self.datum)
558
+ except (TypeError, ValueError) as x:
559
+ raise _xError(x, start1=self, end1=end1, other=other, end2=end2,
560
+ height=height, wrap=wrap, tol=tol)
561
+
562
+ def intersections2(self, radius1, other, radius2, height=None, wrap=False, # was=True
563
+ equidistant=None, tol=_TOL_M):
564
+ '''I{Iteratively} compute the intersection points of two circles,
565
+ each defined by a center point and a radius.
566
+
567
+ @arg radius1: Radius of this circle (C{meter}, conventionally).
568
+ @arg other: Center of the other circle (C{LatLon}).
569
+ @arg radius2: Radius of the other circle (C{meter}, same units as
570
+ B{C{radius1}}).
571
+ @kwarg height: Optional height for the intersection points (C{meter},
572
+ conventionally) or C{None} for the I{"radical height"}
573
+ at the I{radical line} between both centers.
574
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the B{C{other}}
575
+ center (C{bool}).
576
+ @kwarg equidistant: An azimuthal equidistant projection (I{class} or
577
+ function L{pygeodesy.equidistant}) or C{None}
578
+ for this point's preferred C{.Equidistant}.
579
+ @kwarg tol: Convergence tolerance (C{meter}, same units as
580
+ B{C{radius1}} and B{C{radius2}}).
581
+
582
+ @return: 2-Tuple of the intersection points, each a C{LatLon}
583
+ instance. For abutting circles, both intersection
584
+ points are the same instance, aka the I{radical center}.
585
+
586
+ @raise ImportError: Package U{geographiclib
587
+ <https://PyPI.org/project/geographiclib>}
588
+ not installed or not found, but only if
589
+ C{B{equidistant}=}L{EquidistantKarney}.
590
+
591
+ @raise IntersectionError: Concentric, antipodal, invalid or
592
+ non-intersecting circles or no
593
+ convergence for the given B{C{tol}}.
594
+
595
+ @raise TypeError: Invalid B{C{other}} or B{C{equidistant}}.
596
+
597
+ @raise UnitError: Invalid B{C{radius1}}, B{C{radius2}} or B{C{height}}.
598
+
599
+ @see: U{The B{ellipsoidal} case<https://GIS.StackExchange.com/questions/48937/
600
+ calculating-intersection-of-two-circles>}, U{Karney's paper
601
+ <https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section B{14. MARITIME BOUNDARIES},
602
+ U{circle-circle<https://MathWorld.Wolfram.com/Circle-CircleIntersection.html>} and
603
+ U{sphere-sphere<https://MathWorld.Wolfram.com/Sphere-SphereIntersection.html>}
604
+ intersections.
605
+ '''
606
+ try:
607
+ c2 = self.others(other)
608
+ return _MODS.ellipsoidalBaseDI._intersections2(self, radius1,
609
+ c2, radius2,
610
+ height=height, wrap=wrap,
611
+ equidistant=equidistant, tol=tol,
612
+ LatLon=self.classof, datum=self.datum)
613
+ except (AssertionError, TypeError, ValueError) as x:
614
+ raise _xError(x, center=self, radius1=radius1, other=other, radius2=radius2,
615
+ height=height, wrap=wrap, tol=tol)
616
+
617
+ def isenclosedBy(self, points, wrap=False):
618
+ '''Check whether a polygon or composite encloses this point.
619
+
620
+ @arg points: The polygon points or clips (C{LatLon}[],
621
+ L{BooleanFHP} or L{BooleanGH}).
622
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
623
+ B{C{points}} (C{bool}).
624
+
625
+ @return: C{True} if this point is inside the polygon or composite,
626
+ C{False} otherwise.
627
+
628
+ @raise PointsError: Insufficient number of B{C{points}}.
629
+
630
+ @raise TypeError: Some B{C{points}} are not C{LatLon}.
631
+
632
+ @raise ValueError: Invalid B{C{point}}, lat- or longitude.
633
+
634
+ @see: Functions L{pygeodesy.isconvex}, L{pygeodesy.isenclosedBy}
635
+ and L{pygeodesy.ispolar} especially if the B{C{points}} may
636
+ enclose a pole or wrap around the earth I{longitudinally}.
637
+ '''
638
+ return _MODS.points.isenclosedBy(self, points, wrap=wrap)
639
+
640
+ @property_RO
641
+ def iteration(self):
642
+ '''Get the most recent C{intersections2} or C{nearestOn} iteration
643
+ number (C{int}) or C{None} if not available/applicable.
644
+ '''
645
+ return self._iteration
646
+
647
+ @Property_RO
648
+ def _lcc(self):
649
+ '''(INTERNAL) Get this C{LatLon} point as a Lambert location (L{Lcc}).
650
+ '''
651
+ lcc = _MODS.lcc
652
+ return lcc.toLcc(self, height=self.height, Lcc=lcc.Lcc, name=self.name)
653
+
654
+ def midpointTo(self, other, height=None, fraction=_0_5, wrap=False):
655
+ '''Find the midpoint on a geodesic between this and an other point.
656
+
657
+ @arg other: The other point (C{LatLon}).
658
+ @kwarg height: Optional height for midpoint, overriding the
659
+ mean height (C{meter}).
660
+ @kwarg fraction: Midpoint location from this point (C{scalar}),
661
+ may be negative or greater than 1.0.
662
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
663
+ B{C{other}} point (C{bool}).
664
+
665
+ @return: Midpoint (C{LatLon}).
666
+
667
+ @raise TypeError: The B{C{other}} point is not C{LatLon}.
668
+
669
+ @raise ValueError: Invalid B{C{height}}.
670
+
671
+ @see: Methods C{intermediateTo} and C{rhumbMidpointTo}.
672
+ '''
673
+ return self.intermediateTo(other, fraction, height=height, wrap=wrap)
674
+
675
+ def nearestOn(self, point1, point2, within=True, height=None, wrap=False, # was=True
676
+ equidistant=None, tol=_TOL_M):
677
+ '''I{Iteratively} locate the closest point on the geodesic between
678
+ two other (ellipsoidal) points.
679
+
680
+ @arg point1: Start point (C{LatLon}).
681
+ @arg point2: End point (C{LatLon}).
682
+ @kwarg within: If C{True} return the closest point I{between}
683
+ B{C{point1}} and B{C{point2}}, otherwise the
684
+ closest point elsewhere on the geodesic (C{bool}).
685
+ @kwarg height: Optional height for the closest point (C{meter},
686
+ conventionally) or C{None} or C{False} for the
687
+ interpolated height. If C{False}, the closest
688
+ takes the heights of the points into account.
689
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll both
690
+ B{C{point1}} and B{C{point2}} (C{bool}).
691
+ @kwarg equidistant: An azimuthal equidistant projection (I{class} or
692
+ function L{pygeodesy.equidistant}) or C{None}
693
+ for this point's preferred C{.Equidistant}.
694
+ @kwarg tol: Convergence tolerance (C{meter}, conventionally).
695
+
696
+ @return: Closest point (C{LatLon}).
697
+
698
+ @raise ImportError: Package U{geographiclib
699
+ <https://PyPI.org/project/geographiclib>}
700
+ not installed or not found, but only if
701
+ C{B{equidistant}=}L{EquidistantKarney}.
702
+
703
+ @raise TypeError: Invalid B{C{point1}}, B{C{point2}} or
704
+ B{C{equidistant}}.
705
+
706
+ @raise ValueError: Datum or ellipsoid of B{C{point1}} or B{C{point2}} is
707
+ incompatible or no convergence for the given B{C{tol}}.
708
+
709
+ @see: I{Karney}'s U{intercept.cpp<https://SourceForge.net/p/geographiclib/
710
+ discussion/1026621/thread/21aaff9f/>}, U{The B{ellipsoidal} case<https://
711
+ GIS.StackExchange.com/questions/48937/calculating-intersection-of-two-circles>}
712
+ and U{Karney's paper<https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section
713
+ B{14. MARITIME BOUNDARIES} for details about the iteration algorithm.
714
+ '''
715
+ try:
716
+ t = _MODS.ellipsoidalBaseDI._nearestOn2(self, point1, point2, within=within,
717
+ height=height, wrap=wrap,
718
+ equidistant=equidistant,
719
+ tol=tol, LatLon=self.classof)
720
+ except (TypeError, ValueError) as x:
721
+ raise _xError(x, point=self, point1=point1, point2=point2, within=within,
722
+ height=height, wrap=wrap, tol=tol)
723
+ return t.closest
724
+
725
+ @Property_RO
726
+ def _osgr(self):
727
+ '''(INTERNAL) Get this C{LatLon} point as an OSGR coordinate (L{Osgr}),
728
+ based on the OS recommendation.
729
+ '''
730
+ return _MODS.osgr.toOsgr(self, kTM=False, name=self.name) # datum=self.datum
731
+
732
+ @Property_RO
733
+ def _osgrTM(self):
734
+ '''(INTERNAL) Get this C{LatLon} point as an OSGR coordinate (L{Osgr})
735
+ based on I{Karney}'s Krüger implementation.
736
+ '''
737
+ return _MODS.osgr.toOsgr(self, kTM=True, name=self.name) # datum=self.datum
738
+
739
+ def parse(self, strllh, height=0, datum=None, epoch=None, reframe=None,
740
+ sep=_COMMA_, wrap=False, name=NN):
741
+ '''Parse a string consisting of C{"lat, lon[, height]"},
742
+ representing a similar, ellipsoidal C{LatLon} point.
743
+
744
+ @arg strllh: Lat, lon and optional height (C{str}),
745
+ see function L{pygeodesy.parse3llh}.
746
+ @kwarg height: Optional, default height (C{meter} or
747
+ C{None}).
748
+ @kwarg datum: Optional datum (L{Datum}), overriding this
749
+ datum I{without conversion}.
750
+ @kwarg epoch: Optional datum (L{Epoch}), overriding this
751
+ epoch I{without conversion}.
752
+ @kwarg reframe: Optional datum (L{RefFrame}), overriding
753
+ this reframe I{without conversion}.
754
+ @kwarg sep: Optional separator (C{str}).
755
+ @kwarg wrap: If C{True}, wrap or I{normalize} the lat-
756
+ and longitude (C{bool}).
757
+ @kwarg name: Optional instance name (C{str}), overriding
758
+ this name.
759
+
760
+ @return: The similar point (ellipsoidal C{LatLon}).
761
+
762
+ @raise ParseError: Invalid B{C{strllh}}.
763
+ '''
764
+ a, b, h = _MODS.dms.parse3llh(strllh, height=height, sep=sep, wrap=wrap)
765
+ r = self.classof(a, b, height=h, datum=self.datum)
766
+ if datum not in (None, self.datum):
767
+ r.datum = datum
768
+ if epoch not in (None, self.epoch):
769
+ r.epoch = epoch
770
+ if reframe not in (None, self.reframe):
771
+ r.reframe = reframe
772
+ return self._xnamed(r, name=name, force=True) if name else r
773
+
774
+ def _Radjust2(self, adjust, datum, meter_text2):
775
+ '''(INTERNAL) Adjust an C{elevation} or C{geoidHeight} with
776
+ difference in Gaussian radii of curvature of the given
777
+ datum and NAD83 ellipsoids at this point's latitude.
778
+
779
+ @note: This is an arbitrary, possibly incorrect adjustment.
780
+ '''
781
+ if adjust: # Elevation2Tuple or GeoidHeight2Tuple
782
+ m, t = meter_text2
783
+ if isinstance(m, float) and fabs(m) > EPS:
784
+ n = Datums.NAD83.ellipsoid.rocGauss(self.lat)
785
+ if n > EPS0:
786
+ # use ratio, datum and NAD83 units may differ
787
+ E = self.ellipsoid() if datum in (None, self.datum) else \
788
+ _earth_ellipsoid(datum)
789
+ r = E.rocGauss(self.lat)
790
+ if r > EPS0 and fabs(r - n) > EPS: # EPS1
791
+ m *= r / n
792
+ meter_text2 = meter_text2.classof(m, t)
793
+ return self._xnamed(meter_text2)
794
+
795
+ @property_doc_(''' this point's reference frame (L{RefFrame}).''')
796
+ def reframe(self):
797
+ '''Get this point's reference frame (L{RefFrame}) or C{None}.
798
+ '''
799
+ return self._reframe
800
+
801
+ @reframe.setter # PYCHOK setter!
802
+ def reframe(self, reframe):
803
+ '''Set or clear this point's reference frame (L{RefFrame}) or C{None}.
804
+
805
+ @raise TypeError: The B{C{reframe}} is not a L{RefFrame}.
806
+ '''
807
+ _set_reframe(self, reframe)
808
+
809
+ @Property_RO
810
+ def scale(self):
811
+ '''Get this point's UTM grid or UPS point scale factor (C{float})
812
+ or C{None} if not converted from L{Utm} or L{Ups}.
813
+ '''
814
+ return self._scale
815
+
816
+ def toCartesian(self, height=None, **Cartesian_and_kwds): # PYCHOK signature
817
+ '''Convert this point to cartesian, I{geocentric} coordinates,
818
+ also known as I{Earth-Centered, Earth-Fixed} (ECEF).
819
+
820
+ @see: Method L{toCartesian<latlonBase.LatLonBase.toCartesian>}
821
+ for further details.
822
+ '''
823
+ kwds = Cartesian_and_kwds
824
+ if kwds:
825
+ kwds = _xkwds(kwds, reframe=self.reframe, epoch=self.epoch)
826
+ return LatLonBase.toCartesian(self, height=height, **kwds)
827
+
828
+ def toCss(self, **toCss_kwds):
829
+ '''Convert this C{LatLon} point to a Cassini-Soldner location.
830
+
831
+ @kwarg toCss_kwds: Optional L{pygeodesy.toCss} keyword arguments.
832
+
833
+ @return: The Cassini-Soldner location (L{Css}).
834
+
835
+ @see: Function L{pygeodesy.toCss}.
836
+ '''
837
+ return self._css if not toCss_kwds else _MODS.css.toCss(
838
+ self, **_xkwds(toCss_kwds, name=self.name))
839
+
840
+ def toDatum(self, datum2, height=None, name=NN):
841
+ '''Convert this point to an other datum.
842
+
843
+ @arg datum2: Datum to convert I{to} (L{Datum}).
844
+ @kwarg height: Optional height, overriding the
845
+ converted height (C{meter}).
846
+ @kwarg name: Optional name (C{str}), iff converted.
847
+
848
+ @return: The converted point (ellipsoidal C{LatLon})
849
+ or a copy of this point if B{C{datum2}}
850
+ matches this point's C{datum}.
851
+
852
+ @raise TypeError: Invalid B{C{datum2}}.
853
+ '''
854
+ n = name or self.name
855
+ d2 = _ellipsoidal_datum(datum2, name=n)
856
+ if self.datum == d2:
857
+ r = self.copy(name=name)
858
+ else:
859
+ kwds = _xkwds_not(None, LatLon=self.classof, name=n,
860
+ epoch=self.epoch, reframe=self.reframe)
861
+ c = self.toCartesian().toDatum(d2)
862
+ r = c.toLatLon(datum=d2, height=height, **kwds)
863
+ return r
864
+
865
+ def toEtm(self, **toEtm8_kwds):
866
+ '''Convert this C{LatLon} point to an ETM coordinate.
867
+
868
+ @kwarg toEtm8_kwds: Optional L{pygeodesy.toEtm8} keyword arguments.
869
+
870
+ @return: The ETM coordinate (L{Etm}).
871
+
872
+ @see: Function L{pygeodesy.toEtm8}.
873
+ '''
874
+ return self._etm if not toEtm8_kwds else _MODS.etm.toEtm8(
875
+ self, **_xkwds(toEtm8_kwds, name=self.name))
876
+
877
+ def toLcc(self, **toLcc_kwds):
878
+ '''Convert this C{LatLon} point to a Lambert location.
879
+
880
+ @kwarg toLcc_kwds: Optional L{pygeodesy.toLcc} keyword arguments.
881
+
882
+ @return: The Lambert location (L{Lcc}).
883
+
884
+ @see: Function L{pygeodesy.toLcc}.
885
+ '''
886
+ return self._lcc if not toLcc_kwds else _MODS.lcc.toLcc(
887
+ self, **_xkwds(toLcc_kwds, name=self.name))
888
+
889
+ def toMgrs(self, center=False, pole=NN):
890
+ '''Convert this C{LatLon} point to an MGRS coordinate.
891
+
892
+ @kwarg center: If C{True}, try to I{un}-center MGRS
893
+ to its C{lowerleft} (C{bool}) or by
894
+ C{B{center} meter} (C{scalar}).
895
+ @kwarg pole: Optional top/center for the MGRS UPS
896
+ projection (C{str}, 'N[orth]' or 'S[outh]').
897
+
898
+ @return: The MGRS coordinate (L{Mgrs}).
899
+
900
+ @see: Method L{toUtmUps} and L{Mgrs.toLatLon}.
901
+ '''
902
+ return self.toUtmUps(center=center, pole=pole).toMgrs(center=False)
903
+
904
+ def toOsgr(self, kTM=False, **toOsgr_kwds):
905
+ '''Convert this C{LatLon} point to an OSGR coordinate.
906
+
907
+ @kwarg kTM: If C{True} use I{Karney}'s Krüger method from module
908
+ L{ktm}, otherwise I{Ordinance Survery}'s recommended
909
+ formulation (C{bool}).
910
+ @kwarg toOsgr_kwds: Optional L{pygeodesy.toOsgr} keyword arguments.
911
+
912
+ @return: The OSGR coordinate (L{Osgr}).
913
+
914
+ @see: Function L{pygeodesy.toOsgr}.
915
+ '''
916
+ if toOsgr_kwds:
917
+ r = _MODS.osgr.toOsgr(self, kTM=kTM, **_xkwds(toOsgr_kwds, name=self.name))
918
+ else:
919
+ r = self._osgrTM if kTM else self._osgr
920
+ return r
921
+
922
+ def toRefFrame(self, reframe2, reframe=None, epoch=None, epoch2=None, height=None, name=NN):
923
+ '''Convert this point to an other reference frame and epoch.
924
+
925
+ @arg reframe2: Reference frame to convert I{to} (L{RefFrame}).
926
+ @kwarg reframe: Optional reference frame to convert I{from} (L{RefFrame}),
927
+ overriding this point's reference frame.
928
+ @kwarg epoch: Optional epoch (L{Epoch}, C{scalar} or C{str}), overriding
929
+ this point's C{epoch or B{reframe}.epoch}.
930
+ @kwarg epoch2: Optional epoch to observe for the converted point (L{Epoch},
931
+ C{scalar} or C{str}), otherwise B{C{epoch}}.
932
+ @kwarg height: Optional height, overriding the converted height (C{meter}).
933
+ @kwarg name: Optional name (C{str}), C{B{reframe2}.name} iff converted.
934
+
935
+ @return: The converted point (ellipsoidal C{LatLon}) or if conversion
936
+ C{isunity}, this point or a copy of this point if the B{C{name}}
937
+ is non-empty.
938
+
939
+ @raise TRFError: This point's C{reframe} is not defined, invalid B{C{epoch}}
940
+ or B{C{epoch2}} or conversion from this point's C{reframe}
941
+ to B{C{reframe2}} is not available.
942
+
943
+ @raise TypeError: B{C{reframe2}} or B{C{reframe}} not a L{RefFrame}.
944
+ '''
945
+ return _MODS.trf._toRefFrame(self, reframe2, reframe=reframe, epoch=epoch,
946
+ epoch2=epoch2, name=name, height=height)
947
+
948
+ def toTransform(self, transform, inverse=False, datum=None, **LatLon_kwds):
949
+ '''Apply a Helmert transform to this geodetic point.
950
+
951
+ @arg transform: Transform to apply (L{Transform} or L{TransformXform}).
952
+ @kwarg inverse: Apply the inverse of the Helmert transform (C{bool}).
953
+ @kwarg datum: Datum for the transformed point (L{Datum}), overriding
954
+ this point's datum but I{not} taken it into account.
955
+ @kwarg LatLon_kwds: Optional keyword arguments for the transformed
956
+ point, like C{B{height}=...}.
957
+
958
+ @return: A transformed point (C{LatLon}) or a copy of this point if
959
+ C{B{transform}.isunity}.
960
+
961
+ @raise TypeError: Invalid B{C{transform}}.
962
+ '''
963
+ _xinstanceof(Transform, transform=transform)
964
+ d = datum or self.datum
965
+ if transform.isunity:
966
+ r = self.dup(datum=d, **LatLon_kwds)
967
+ else:
968
+ c = self.toCartesian()
969
+ c = c.toTransform(transform, inverse=inverse, datum=d)
970
+ r = c.toLatLon(LatLon=self.classof, **_xkwds(LatLon_kwds, height=self.height))
971
+ return r
972
+
973
+ def toUps(self, pole=NN, falsed=True, center=False):
974
+ '''Convert this C{LatLon} point to a UPS coordinate.
975
+
976
+ @kwarg pole: Optional top/center of (stereographic)
977
+ projection (C{str}, 'N[orth]' or 'S[outh]').
978
+ @kwarg falsed: False easting and northing (C{bool}).
979
+ @kwarg center: If C{True}, I{un}-center the UPS
980
+ to its C{lowerleft} (C{bool}) or
981
+ by C{B{center} meter} (C{scalar}).
982
+
983
+ @return: The UPS coordinate (L{Ups}).
984
+
985
+ @see: Function L{pygeodesy.toUps8}.
986
+ '''
987
+ if self._upsOK(pole, falsed):
988
+ u = self._ups
989
+ else:
990
+ ups = _MODS.ups
991
+ u = ups.toUps8(self, datum=self.datum, Ups=ups.Ups,
992
+ pole=pole, falsed=falsed)
993
+ return _lowerleft(u, center)
994
+
995
+ def toUtm(self, center=False):
996
+ '''Convert this C{LatLon} point to a UTM coordinate.
997
+
998
+ @kwarg center: If C{True}, I{un}-center the UTM
999
+ to its C{lowerleft} (C{bool}) or
1000
+ by C{B{center} meter} (C{scalar}).
1001
+
1002
+ @return: The UTM coordinate (L{Utm}).
1003
+
1004
+ @see: Method L{Mgrs.toUtm} and function L{pygeodesy.toUtm8}.
1005
+ '''
1006
+ return _lowerleft(self._utm, center)
1007
+
1008
+ def toUtmUps(self, pole=NN, center=False):
1009
+ '''Convert this C{LatLon} point to a UTM or UPS coordinate.
1010
+
1011
+ @kwarg pole: Optional top/center of UPS (stereographic)
1012
+ projection (C{str}, 'N[orth]' or 'S[outh]').
1013
+ @kwarg center: If C{True}, I{un}-center the UTM or UPS to
1014
+ its C{lowerleft} (C{bool}) or by C{B{center}
1015
+ meter} (C{scalar}).
1016
+
1017
+ @return: The UTM or UPS coordinate (L{Utm} or L{Ups}).
1018
+
1019
+ @see: Function L{pygeodesy.toUtmUps8}.
1020
+ '''
1021
+ if self._utmOK():
1022
+ u = self._utm
1023
+ elif self._upsOK(pole):
1024
+ u = self._ups
1025
+ else: # no cover
1026
+ utmups = _MODS.utmups
1027
+ u = utmups.toUtmUps8(self, datum=self.datum, pole=pole, name=self.name,
1028
+ Utm=utmups.Utm, Ups=utmups.Ups)
1029
+ if isinstance(u, utmups.Utm):
1030
+ self._update(False, _utm=u) # PYCHOK kwds
1031
+ elif isinstance(u, utmups.Ups):
1032
+ self._update(False, _ups=u) # PYCHOK kwds
1033
+ else:
1034
+ _xinstanceof(utmups.Utm, utmups.Ups, toUtmUps8=u)
1035
+ return _lowerleft(u, center)
1036
+
1037
+ @deprecated_method
1038
+ def to3xyz(self): # PYCHOK no cover
1039
+ '''DEPRECATED, use method C{toEcef}.
1040
+
1041
+ @return: A L{Vector3Tuple}C{(x, y, z)}.
1042
+
1043
+ @note: Overloads C{LatLonBase.to3xyz}
1044
+ '''
1045
+ r = self.toEcef()
1046
+ return _MODS.namedTuples.Vector3Tuple(r.x, r.y, r.z, name=self.name)
1047
+
1048
+ def triangulate(self, bearing1, other, bearing2, **height_wrap_tol):
1049
+ '''I{Iteratively} locate a point given this, an other point and the (initial)
1050
+ bearing at this and at the other point.
1051
+
1052
+ @arg bearing1: Bearing at this point (compass C{degrees360}).
1053
+ @arg other: Start point of the other line (C{LatLon}).
1054
+ @arg bearing2: Bearing at the other point (compass C{degrees360}).
1055
+ @kwarg height_wrap_tol: Optional keyword arguments C{B{height}=None},
1056
+ C{B{wrap}=False} and C{B{tol}}, see method L{intersection3}.
1057
+
1058
+ @return: Triangulated point (C{LatLon}).
1059
+
1060
+ @see: Method L{intersection3} for further details.
1061
+ '''
1062
+ if _isDegrees(bearing1) and _isDegrees(bearing2):
1063
+ r = self.intersection3(bearing1, other, bearing2, **height_wrap_tol)
1064
+ return r.point
1065
+ raise _TypeError(bearing1=bearing1, bearing2=bearing2 **height_wrap_tol)
1066
+
1067
+ def trilaterate5(self, distance1, point2, distance2, point3, distance3,
1068
+ area=True, eps=EPS1, wrap=False):
1069
+ '''Trilaterate three points by I{area overlap} or I{perimeter
1070
+ intersection} of three intersecting circles.
1071
+
1072
+ @arg distance1: Distance to this point (C{meter}), same units
1073
+ as B{C{eps}}).
1074
+ @arg point2: Second center point (C{LatLon}).
1075
+ @arg distance2: Distance to point2 (C{meter}, same units as
1076
+ B{C{eps}}).
1077
+ @arg point3: Third center point (C{LatLon}).
1078
+ @arg distance3: Distance to point3 (C{meter}, same units as
1079
+ B{C{eps}}).
1080
+ @kwarg area: If C{True} compute the area overlap, otherwise the
1081
+ perimeter intersection of the circles (C{bool}).
1082
+ @kwarg eps: The required I{minimal overlap} for C{B{area}=True}
1083
+ or the I{intersection margin} for C{B{area}=False}
1084
+ (C{meter}, conventionally).
1085
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
1086
+ B{C{point2}} and B{C{point3}} (C{bool}).
1087
+
1088
+ @return: A L{Trilaterate5Tuple}C{(min, minPoint, max, maxPoint, n)}
1089
+ with C{min} and C{max} in C{meter}, same units as B{C{eps}},
1090
+ the corresponding trilaterated points C{minPoint} and
1091
+ C{maxPoint} as I{ellipsoidal} C{LatLon} and C{n}, the number
1092
+ of trilatered points found for the given B{C{eps}}.
1093
+
1094
+ If only a single trilaterated point is found, C{min I{is}
1095
+ max}, C{minPoint I{is} maxPoint} and C{n = 1}.
1096
+
1097
+ For C{B{area}=True}, C{min} and C{max} are the smallest
1098
+ respectively largest I{radial} overlap found.
1099
+
1100
+ For C{B{area}=False}, C{min} and C{max} represent the
1101
+ nearest respectively farthest intersection margin.
1102
+
1103
+ If C{B{area}=True} and all 3 circles are concentric, C{n=0}
1104
+ and C{minPoint} and C{maxPoint} are the B{C{point#}} with
1105
+ the smallest B{C{distance#}} C{min} respectively C{max} the
1106
+ largest B{C{distance#}}.
1107
+
1108
+ @raise IntersectionError: Trilateration failed for the given B{C{eps}},
1109
+ insufficient overlap for C{B{area}=True}, no
1110
+ circle intersections for C{B{area}=False} or
1111
+ all circles are (near-)concentric.
1112
+
1113
+ @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.
1114
+
1115
+ @raise ValueError: Coincident B{C{points}} or invalid B{C{distance1}},
1116
+ B{C{distance2}} or B{C{distance3}}.
1117
+
1118
+ @note: Ellipsoidal trilateration invokes methods C{LatLon.intersections2}
1119
+ and C{LatLon.nearestOn} based on I{Karney}'s Python U{geographiclib
1120
+ <https://PyPI.org/project/geographiclib>} if installed, otherwise
1121
+ the accurate (but slower) C{ellipsoidalExact.LatLon} methods.
1122
+ '''
1123
+ return _trilaterate5(self, distance1,
1124
+ self.others(point2=point2), distance2,
1125
+ self.others(point3=point3), distance3,
1126
+ area=area, eps=eps, wrap=wrap)
1127
+
1128
+ @Property_RO
1129
+ def _ups(self): # __dict__ value overwritten by method C{toUtmUps}
1130
+ '''(INTERNAL) Get this C{LatLon} point as UPS coordinate (L{Ups}),
1131
+ see L{pygeodesy.toUps8}.
1132
+ '''
1133
+ ups = _MODS.ups
1134
+ return ups.toUps8(self, datum=self.datum, Ups=ups.Ups,
1135
+ pole=NN, falsed=True, name=self.name)
1136
+
1137
+ def _upsOK(self, pole=NN, falsed=True):
1138
+ '''(INTERNAL) Check matching C{Ups}.
1139
+ '''
1140
+ try:
1141
+ u = self._ups
1142
+ except RangeError:
1143
+ return False
1144
+ return falsed and (u.pole == pole[:1].upper() or not pole)
1145
+
1146
+ @Property_RO
1147
+ def _utm(self): # __dict__ value overwritten by method C{toUtmUps}
1148
+ '''(INTERNAL) Get this C{LatLon} point as UTM coordinate (L{Utm}),
1149
+ see L{pygeodesy.toUtm8}.
1150
+ '''
1151
+ utm = _MODS.utm
1152
+ return utm.toUtm8(self, datum=self.datum, Utm=utm.Utm, name=self.name)
1153
+
1154
+ def _utmOK(self):
1155
+ '''(INTERNAL) Check C{Utm}.
1156
+ '''
1157
+ try:
1158
+ _ = self._utm
1159
+ except RangeError:
1160
+ return False
1161
+ return True
1162
+
1163
+
1164
+ def _lowerleft(utmups, center):
1165
+ '''(INTERNAL) Optionally I{un}-center C{utmups}.
1166
+ '''
1167
+ if center in (False, 0, _0_0):
1168
+ u = utmups
1169
+ elif center in (True,):
1170
+ u = utmups._lowerleft
1171
+ else:
1172
+ u = _MODS.utmupsBase._lowerleft(utmups, center)
1173
+ return u
1174
+
1175
+
1176
+ def _nearestOn(point, point1, point2, within=True, height=None, wrap=False, # was=True
1177
+ equidistant=None, tol=_TOL_M, **LatLon_and_kwds):
1178
+ '''(INTERNAL) Get closest point, imported by .ellipsoidalExact,
1179
+ -GeodSolve, -Karney and -Vincenty to embellish exceptions.
1180
+ '''
1181
+ try:
1182
+ p = _xellipsoidal(point=point)
1183
+ t = _MODS.ellipsoidalBaseDI._nearestOn2(p, point1, point2, within=within,
1184
+ height=height, wrap=wrap,
1185
+ equidistant=equidistant,
1186
+ tol=tol, **LatLon_and_kwds)
1187
+ except (TypeError, ValueError) as x:
1188
+ raise _xError(x, point=point, point1=point1, point2=point2)
1189
+ return t.closest
1190
+
1191
+
1192
+ def _set_reframe(inst, reframe):
1193
+ '''(INTERNAL) Set or clear an instance's reference frame.
1194
+ '''
1195
+ if reframe is not None:
1196
+ _xinstanceof(_MODS.trf.RefFrame, reframe=reframe)
1197
+ inst._reframe = reframe
1198
+ elif inst.reframe is not None:
1199
+ inst._reframe = None
1200
+
1201
+
1202
+ __all__ += _ALL_DOCS(CartesianEllipsoidalBase, LatLonEllipsoidalBase)
1203
+
1204
+ # **) MIT License
1205
+ #
1206
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1207
+ #
1208
+ # Permission is hereby granted, free of charge, to any person obtaining a
1209
+ # copy of this software and associated documentation files (the "Software"),
1210
+ # to deal in the Software without restriction, including without limitation
1211
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1212
+ # and/or sell copies of the Software, and to permit persons to whom the
1213
+ # Software is furnished to do so, subject to the following conditions:
1214
+ #
1215
+ # The above copyright notice and this permission notice shall be included
1216
+ # in all copies or substantial portions of the Software.
1217
+ #
1218
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1219
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1220
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1221
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1222
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1223
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1224
+ # OTHER DEALINGS IN THE SOFTWARE.