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,1264 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Spherical, C{N-vector}-based geodesy.
5
+
6
+ N-vector-based classes geodetic (lat-/longitude) L{LatLon}, geocentric
7
+ (ECEF) L{Cartesian} and C{Nvector} and functions L{areaOf}, L{intersection},
8
+ L{meanOf}, L{nearestOn3}, L{perimeterOf}, L{sumOf}, L{triangulate} and
9
+ L{trilaterate}, I{all spherical}.
10
+
11
+ Pure Python implementation of n-vector-based spherical geodetic (lat-/longitude)
12
+ methods, transcoded from JavaScript originals by I{(C) Chris Veness 2011-2016},
13
+ published under the same MIT Licence**. See U{Vector-based geodesy
14
+ <https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>} and
15
+ U{Module latlon-nvector-spherical
16
+ <https://www.Movable-Type.co.UK/scripts/geodesy/docs/module-latlon-nvector-spherical.html>}.
17
+
18
+ Tools for working with points and lines on (a spherical model of) the
19
+ earth’s surface using using n-vectors rather than the more common
20
+ spherical trigonometry. N-vectors make many calculations much simpler,
21
+ and easier to follow, compared with the trigonometric equivalents.
22
+
23
+ Based on Kenneth Gade’s U{‘Non-singular Horizontal Position Representation’
24
+ <https://www.NavLab.net/Publications/A_Nonsingular_Horizontal_Position_Representation.pdf>},
25
+ The Journal of Navigation (2010), vol 63, nr 3, pp 395-417.
26
+
27
+ Note that the formulations below take x => 0°N,0°E, y => 0°N,90°E and
28
+ z => 90°N while Gade uses x => 90°N, y => 0°N,90°E, z => 0°N,0°E.
29
+
30
+ Also note that on a spherical earth model, an n-vector is equivalent
31
+ to a normalised version of an (ECEF) cartesian coordinate.
32
+ '''
33
+ # make sure int/int division yields float quosient, see .basics
34
+ from __future__ import division as _; del _ # PYCHOK semicolon
35
+
36
+ # from pygeodesy.basics import _xinstanceof # _MODS
37
+ from pygeodesy.constants import EPS, EPS0, PI, PI2, PI_2, R_M, \
38
+ _0_0, _0_5, _1_0
39
+ # from pygeodesy.datums import Datums # from .sphericalBase
40
+ from pygeodesy.errors import PointsError, VectorError, _xError, _xkwds
41
+ from pygeodesy.fmath import fmean, fsum
42
+ # from pygeodesy.fsums import fsum # from .fmath
43
+ from pygeodesy.interns import _composite_, _end_, _Nv00_, _other_, \
44
+ _point_, _pole_
45
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _ALL_OTHER
46
+ # from pygeodesy.named import notImplemented # from .nvectorBase
47
+ # from pygeodesy.namedTuples import NearestOn3Tuple # from .points
48
+ from pygeodesy.nvectorBase import LatLonNvectorBase, NorthPole, \
49
+ notImplemented, NvectorBase, _nsumOf, \
50
+ _triangulate, _trilaterate
51
+ from pygeodesy.points import NearestOn3Tuple, ispolar # PYCHOK exported
52
+ from pygeodesy.props import deprecated_function, deprecated_method, \
53
+ property_RO
54
+ from pygeodesy.sphericalBase import _m2radians, CartesianSphericalBase, \
55
+ _intersecant2, LatLonSphericalBase, \
56
+ _radians2m, Datums
57
+ from pygeodesy.units import Bearing, Bearing_, _isDegrees, Radius, Scalar
58
+ from pygeodesy.utily import atan2, degrees360, fabs, sincos2, sincos2_, \
59
+ sincos2d, _unrollon, _Wrap
60
+
61
+ # from math import atan2, fabs # from utily
62
+
63
+ __all__ = _ALL_LAZY.sphericalNvector
64
+ __version__ = '23.12.18'
65
+
66
+ _lines_ = 'lines'
67
+
68
+
69
+ class Cartesian(CartesianSphericalBase):
70
+ '''Extended to convert geocentric, L{Cartesian} points to
71
+ C{Nvector} and n-vector-based, spherical L{LatLon}.
72
+ '''
73
+
74
+ def toLatLon(self, **LatLon_and_kwds): # PYCHOK LatLon=LatLon
75
+ '''Convert this cartesian to an C{Nvector}-based geodetic point.
76
+
77
+ @kwarg LatLon_and_kwds: Optional C{LatLon} class and C{LatLon} keyword
78
+ arguments, like C{datum}. Use C{B{LatLon}=...}
79
+ to override this L{LatLon} class or specify
80
+ C{B{LatLon}=None}.
81
+
82
+ @return: A C{LatLon} or if C{LatLon is None}, an L{Ecef9Tuple}C{(x, y, z,
83
+ lat, lon, height, C, M, datum)} with C{C} and C{M} if available.
84
+
85
+ @raise TypeError: Invalid C{LatLon} or other B{C{LatLon_and_kwds}} item.
86
+ '''
87
+ kwds = _xkwds(LatLon_and_kwds, LatLon=LatLon, datum=self.datum)
88
+ return CartesianSphericalBase.toLatLon(self, **kwds)
89
+
90
+ def toNvector(self, **Nvector_and_kwds): # PYCHOK Datums.WGS84
91
+ '''Convert this cartesian to C{Nvector} components, I{including height}.
92
+
93
+ @kwarg Nvector_and_kwds: Optional C{Nvector} class and C{Nvector} keyword
94
+ arguments, like C{datum}. Use C{B{Nvector}=...}
95
+ to override this C{Nvector} class or specify
96
+ C{B{Nvector}=None}.
97
+
98
+ @return: An C{Nvector}) or if C{Nvector is None}, a L{Vector4Tuple}C{(x, y, z, h)}.
99
+
100
+ @raise TypeError: Invalid C{Nvector} or other B{C{Nvector_and_kwds}} item.
101
+ '''
102
+ # ll = CartesianBase.toLatLon(self, LatLon=LatLon,
103
+ # datum=datum or self.datum)
104
+ # kwds = _xkwds(kwds, Nvector=Nvector)
105
+ # return ll.toNvector(**kwds)
106
+ kwds = _xkwds(Nvector_and_kwds, Nvector=Nvector, datum=self.datum)
107
+ return CartesianSphericalBase.toNvector(self, **kwds)
108
+
109
+
110
+ class LatLon(LatLonNvectorBase, LatLonSphericalBase):
111
+ '''New n-vector-based point on a spherical earth model.
112
+
113
+ Tools for working with points, lines and paths on (a spherical
114
+ model of) the earth's surface using vector-based methods.
115
+ '''
116
+ _Nv = None # cached_toNvector C{Nvector})
117
+
118
+ def _update(self, updated, *attrs, **setters): # PYCHOK args
119
+ '''(INTERNAL) Zap cached attributes if updated.
120
+ '''
121
+ if updated: # reset caches
122
+ LatLonNvectorBase._update(self, updated, _Nv=self._Nv) # special case
123
+ LatLonSphericalBase._update(self, updated, *attrs, **setters)
124
+
125
+ def alongTrackDistanceTo(self, start, end, radius=R_M, wrap=False):
126
+ '''Compute the (signed) distance from the start to the closest
127
+ point on the great circle line defined by a start and an
128
+ end point.
129
+
130
+ That is, if a perpendicular is drawn from this point to the
131
+ great circle line, the along-track distance is the distance
132
+ from the start point to the point where the perpendicular
133
+ crosses the line.
134
+
135
+ @arg start: Start point of great circle line (L{LatLon}).
136
+ @arg end: End point of great circle line (L{LatLon}) or
137
+ initial bearing from start point (compass
138
+ C{degrees360}).
139
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
140
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
141
+ the B{C{start}} and B{C{end}} points (C{bool}).
142
+
143
+ @return: Distance along the great circle line (C{radians}
144
+ if C{B{radius} is None} else C{meter}, same units
145
+ as B{C{radius}}), positive if "after" the start
146
+ toward the end point of the line or negative if
147
+ "before" the start point.
148
+
149
+ @raise TypeError: If B{C{start}} or B{C{end}} point is not L{LatLon}.
150
+
151
+ @raise Valuerror: Some points coincide.
152
+ '''
153
+ p = self.others(start=start)
154
+ n = self.toNvector()
155
+
156
+ gc, _, _ = self._gc3(p, end, _end_, wrap=wrap)
157
+ a = gc.cross(n).cross(gc) # along-track point gc × p × gc
158
+ return _radians2m(start.toNvector().angleTo(a, vSign=gc), radius)
159
+
160
+ @deprecated_method
161
+ def bearingTo(self, other, **unused): # PYCHOK no cover
162
+ '''DEPRECATED, use method L{initialBearingTo}.
163
+ '''
164
+ return self.initialBearingTo(other)
165
+
166
+ def crossTrackDistanceTo(self, start, end, radius=R_M, wrap=False):
167
+ '''Compute the (signed) distance from this point to great circle
168
+ defined by a start and end point.
169
+
170
+ @arg start: Start point of great circle line (L{LatLon}).
171
+ @arg end: End point of great circle line (L{LatLon}) or initial
172
+ bearing from start point (compass C{degrees360}).
173
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
174
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
175
+ B{C{start}} and B{C{end}} points (C{bool}).
176
+
177
+ @return: Distance to great circle (C{radians} if C{B{radius}
178
+ is None} else C{meter}, same units as B{C{radius}}),
179
+ negative if to the left or positive if to the right
180
+ of the line .
181
+
182
+ @raise TypeError: If B{C{start}} or B{C{end}} point is not L{LatLon}.
183
+
184
+ @raise Valuerror: Some points coincide.
185
+ '''
186
+ p = self.others(start=start)
187
+ n = self.toNvector()
188
+
189
+ gc, _, _ = self._gc3(p, end, _end_, wrap=wrap)
190
+ return _radians2m(gc.angleTo(n) - PI_2, radius)
191
+
192
+ def destination(self, distance, bearing, radius=R_M, height=None):
193
+ '''Locate the destination from this point after having travelled
194
+ the given distance on the given bearing.
195
+
196
+ @arg distance: Distance travelled (C{meter}, same units as
197
+ B{C{radius}}).
198
+ @arg bearing: Bearing from this point (compass C{degrees360}).
199
+ @kwarg radius: Mean earth radius (C{meter}).
200
+ @kwarg height: Optional height at destination, overriding the
201
+ default height (C{meter}, same units as B{C{radius}}).
202
+
203
+ @return: Destination point (L{LatLon}).
204
+
205
+ @raise Valuerror: Polar coincidence or invalid B{C{distance}},
206
+ B{C{bearing}}, B{C{radius}} or B{C{height}}.
207
+ '''
208
+ b = Bearing_(bearing)
209
+ a = _m2radians(distance, radius, low=None)
210
+ sa, ca, sb, cb = sincos2_(a, b)
211
+
212
+ n = self.toNvector()
213
+ e = NorthPole.cross(n, raiser=_pole_).unit() # east vector at n
214
+ x = n.cross(e) # north vector at n
215
+ d = x.times(cb).plus(e.times(sb)) # direction vector @ n
216
+ n = n.times(ca).plus(d.times(sa))
217
+ return n.toLatLon(height=height, LatLon=self.classof) # Nvector(n.x, n.y, n.z).toLatLon(...)
218
+
219
+ def distanceTo(self, other, radius=R_M, wrap=False):
220
+ '''Compute the distance from this to an other point.
221
+
222
+ @arg other: The other point (L{LatLon}).
223
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
224
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
225
+ the B{C{other}} point (C{bool}).
226
+
227
+ @return: Distance between this and the B{C{other}} point
228
+ (C{meter}, same units as B{C{radius}} or C{radians}
229
+ if B{C{radius}} is C{None}).
230
+
231
+ @raise TypeError: Invalid B{C{other}} point.
232
+ '''
233
+ p = self.others(other)
234
+ if wrap:
235
+ p = _unrollon(self, p)
236
+ n = p.toNvector()
237
+ r = fabs(self.toNvector().angleTo(n, wrap=wrap))
238
+ return r if radius is None else (Radius(radius) * r)
239
+
240
+ # @Property_RO
241
+ # def Ecef(self):
242
+ # '''Get the ECEF I{class} (L{EcefVeness}), I{lazily}.
243
+ # '''
244
+ # return _ALL_MODS.ecef.EcefKarney
245
+
246
+ def _gc3(self, start, end, namend, raiser=_point_, wrap=False):
247
+ '''(INTERNAL) Return great circle, start and end Nvectors.
248
+ '''
249
+ s = start.toNvector()
250
+ if _isDegrees(end): # bearing
251
+ gc = s.greatCircle(end)
252
+ e = None
253
+ else: # point
254
+ p = self.others(end, name=namend)
255
+ if wrap:
256
+ p = _unrollon(start, p, wrap=wrap)
257
+ e = p.toNvector()
258
+ gc = s.cross(e, raiser=raiser) # XXX .unit()?
259
+ return gc, s, e
260
+
261
+ def greatCircle(self, bearing):
262
+ '''Compute the vector normal to great circle obtained by
263
+ heading on the given bearing from this point.
264
+
265
+ Direction of vector is such that initial bearing vector
266
+ b = c × n, where n is an n-vector representing this point.
267
+
268
+ @arg bearing: Bearing from this point (compass C{degrees360}).
269
+
270
+ @return: N-vector representing the great circle (C{Nvector}).
271
+ '''
272
+ t = Bearing_(bearing)
273
+ a, b = self.philam
274
+
275
+ sa, ca, sb, cb, st, ct = sincos2_(a, b, t)
276
+ return Nvector(sb * ct - sa * cb * st,
277
+ -cb * ct - sa * sb * st,
278
+ ca * st, name=self.name) # XXX .unit()
279
+
280
+ def greatCircleTo(self, other, wrap=False):
281
+ '''Compute the vector normal to great circle obtained by
282
+ heading from this to an other point or on a given bearing.
283
+
284
+ Direction of vector is such that initial bearing vector
285
+ b = c × n, where n is an n-vector representing this point.
286
+
287
+ @arg other: The other point (L{LatLon}) or the bearing from
288
+ this point (compass C{degrees360}).
289
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
290
+ the B{C{other}} point (C{bool}).
291
+
292
+ @return: N-vector representing the great circle (C{Nvector}).
293
+
294
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
295
+
296
+ @raise Valuerror: Points coincide.
297
+ '''
298
+ gc, _, _ = self._gc3(self, other, _other_, wrap=wrap)
299
+ return gc.unit()
300
+
301
+ def initialBearingTo(self, other, wrap=False, **unused): # raiser=...
302
+ '''Compute the initial bearing (forward azimuth) from this
303
+ to an other point.
304
+
305
+ @arg other: The other point (L{LatLon}).
306
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
307
+ the B{C{other}} point (C{bool}).
308
+
309
+ @return: Initial bearing (compass C{degrees360}).
310
+
311
+ @raise Crosserror: This point coincides with the B{C{other}}
312
+ point or the C{NorthPole}, provided
313
+ L{pygeodesy.crosserrors} is C{True}.
314
+
315
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
316
+ '''
317
+ n = self.toNvector()
318
+ p = self.others(other)
319
+ if wrap:
320
+ p = _unrollon(self, p, wrap=wrap)
321
+ p = p.toNvector()
322
+ # see <https://MathForum.org/library/drmath/view/55417.html>
323
+ # gc1 = self.greatCircleTo(other)
324
+ gc1 = n.cross(p, raiser=_point_) # .unit()
325
+ # gc2 = self.greatCircleTo(NorthPole)
326
+ gc2 = n.cross(NorthPole, raiser=_pole_) # .unit()
327
+ return degrees360(gc1.angleTo(gc2, vSign=n))
328
+
329
+ def intermediateChordTo(self, other, fraction, height=None, wrap=False):
330
+ '''Locate the point projected from the point at given fraction
331
+ on a straight line (chord) between this and an other point.
332
+
333
+ @arg other: The other point (L{LatLon}).
334
+ @arg fraction: Fraction between both points (float, between
335
+ 0.0 for this and 1.0 for the other point).
336
+ @kwarg height: Optional height at the intermediate point,
337
+ overriding the fractional height (C{meter}).
338
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
339
+ the B{C{other}} point (C{bool}).
340
+
341
+ @return: Intermediate point (L{LatLon}).
342
+
343
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
344
+ '''
345
+ n = self.toNvector()
346
+ p = self.others(other)
347
+ if wrap:
348
+ p = _unrollon(self, p, wrap=wrap)
349
+
350
+ f = Scalar(fraction=fraction)
351
+ i = p.toNvector().times(f).plus(n.times(1 - f))
352
+ # i = p.toNvector() * f + self.toNvector() * (1 - f))
353
+
354
+ h = self._havg(other, f=f, h=height)
355
+ return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
356
+
357
+ def intermediateTo(self, other, fraction, height=None, wrap=False):
358
+ '''Locate the point at a given fraction between this and an
359
+ other point.
360
+
361
+ @arg other: The other point (L{LatLon}).
362
+ @arg fraction: Fraction between both points (C{float}, between
363
+ 0.0 for this and 1.0 for the other point).
364
+ @kwarg height: Optional height at the intermediate point,
365
+ overriding the fractional height (C{meter}).
366
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
367
+ the B{C{other}} point (C{bool}).
368
+
369
+ @return: Intermediate point (L{LatLon}).
370
+
371
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
372
+
373
+ @raise Valuerror: Points coincide or invalid B{C{height}}.
374
+
375
+ @see: Methods C{midpointTo} and C{rhumbMidpointTo}.
376
+ '''
377
+ n = self.toNvector()
378
+ p = self.others(other)
379
+ if wrap:
380
+ p = _unrollon(self, p, wrap=wrap)
381
+ p = p.toNvector()
382
+ f = Scalar(fraction=fraction)
383
+
384
+ x = n.cross(p, raiser=_point_)
385
+ d = x.unit().cross(n) # unit(n × p) × n
386
+ # angular distance α, tan(α) = |n × p| / n ⋅ p
387
+ s, c = sincos2(atan2(x.length, n.dot(p)) * f) # interpolated
388
+ i = n.times(c).plus(d.times(s)) # n * cosα + d * sinα
389
+
390
+ h = self._havg(other, f=f, h=height)
391
+ return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
392
+
393
+ def intersection(self, end1, start2, end2, height=None, wrap=False):
394
+ '''Locate the intersection point of two lines each defined
395
+ by two points or a start point and bearing from North.
396
+
397
+ @arg end1: End point of the first line (L{LatLon}) or the
398
+ initial bearing at this point (compass C{degrees360}).
399
+ @arg start2: Start point of the second line (L{LatLon}).
400
+ @arg end2: End point of the second line (L{LatLon}) or the
401
+ initial bearing at the second point (compass
402
+ C{degrees}).
403
+ @kwarg height: Optional height at the intersection point,
404
+ overriding the mean height (C{meter}).
405
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll all
406
+ start and end points (C{bool}).
407
+
408
+ @return: The intersection point (L{LatLon}).
409
+
410
+ @raise TypeError: If B{C{start2}}, B{C{end1}} or B{C{end2}}
411
+ point is not L{LatLon}.
412
+
413
+ @raise ValueError: Intersection is ambiguous or infinite or
414
+ the lines are parallel, coincident or null.
415
+
416
+ @see: Function L{sphericalNvector.intersection} and method
417
+ L{intersection2}.
418
+ '''
419
+ return intersection(self, end1, start2, end2, height=height,
420
+ wrap=wrap, LatLon=self.classof)
421
+
422
+ def intersection2(self, end1, start2, end2, height=None, wrap=False):
423
+ '''Locate the intersections of two (great circle) lines each defined
424
+ by two points or by a start point and an (initial) bearing.
425
+
426
+ @arg end1: End point of the first line (L{LatLon}) or the
427
+ initial bearing at this point (compass C{degrees360}).
428
+ @arg start2: Start point of the second line (L{LatLon}).
429
+ @arg end2: End point of the second line (L{LatLon}) or the
430
+ initial bearing at the second start point (compass
431
+ C{degrees360}).
432
+ @kwarg height: Optional height at the intersection and antipodal
433
+ point, overriding the mean height (C{meter}).
434
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
435
+ B{C{start2}} and both B{C{end*}} points (C{bool}).
436
+
437
+ @return: 2-Tuple C{(intersection, antipode)}, each a B{C{LatLon}}.
438
+
439
+ @raise TypeError: If B{C{start2}}, B{C{end1}} or B{C{end2}}
440
+ point is not L{LatLon}.
441
+
442
+ @raise ValueError: Intersection is ambiguous or infinite or
443
+ the lines are parallel, coincident or null.
444
+
445
+ @see: Function L{sphericalNvector.intersection2} and method
446
+ L{intersection}.
447
+ '''
448
+ return intersection2(self, end1, start2, end2, height=height,
449
+ wrap=wrap, LatLon=self.classof)
450
+
451
+ def isenclosedBy(self, points, wrap=False):
452
+ '''Check whether a (convex) polygon or composite encloses this point.
453
+
454
+ @arg points: The polygon points or composite (L{LatLon}[],
455
+ L{BooleanFHP} or L{BooleanGH}).
456
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
457
+ B{C{points}} (C{bool}).
458
+
459
+ @return: C{True} if this point is inside the polygon or composite,
460
+ C{False} otherwise.
461
+
462
+ @raise PointsError: Insufficient number of B{C{points}}.
463
+
464
+ @raise TypeError: Some B{C{points}} are not L{LatLon}.
465
+
466
+ @see: Functions L{pygeodesy.isconvex}, L{pygeodesy.isenclosedBy}
467
+ and L{pygeodesy.ispolar} especially if the B{C{points}} may
468
+ enclose a pole or wrap around the earth I{longitudinally}.
469
+ '''
470
+ if _MODS.booleans.isBoolean(points):
471
+ return points._encloses(self.lat, self.lon, wrap=wrap)
472
+
473
+ # sum subtended angles of each edge (using n0, the
474
+ # normal vector to this point for sign of α)
475
+ def _subtangles(ps, w):
476
+ Ps = self.PointsIter(ps, loop=1, wrap=w)
477
+ n0 = self.toNvector()
478
+ _m0 = n0.minus
479
+ p1 = Ps[0]
480
+ vs1 = _m0(p1.toNvector())
481
+ for p2 in Ps.iterate(closed=True):
482
+ if w and not Ps.looped:
483
+ p2 = _unrollon(p1, p2)
484
+ p1 = p2
485
+ vs2 = _m0(p2.toNvector())
486
+ yield vs1.angleTo(vs2, vSign=n0) # PYCHOK false
487
+ vs1 = vs2
488
+
489
+ # Note, this method uses angle summation test: on a plane,
490
+ # angles for an enclosed point will sum to 360°, angles for
491
+ # an exterior point will sum to 0°. On a sphere, enclosed
492
+ # point angles will sum to less than 360° (due to spherical
493
+ # excess), exterior point angles will be small but non-zero.
494
+ s = fsum(_subtangles(points, wrap), floats=True) # normal vector
495
+ # XXX are winding number optimisations equally applicable to
496
+ # spherical surface?
497
+ return fabs(s) > PI
498
+
499
+ @deprecated_method
500
+ def isEnclosedBy(self, points): # PYCHOK no cover
501
+ '''DEPRECATED, use method C{isenclosedBy}.'''
502
+ return self.isenclosedBy(points)
503
+
504
+ def iswithin(self, point1, point2, wrap=False):
505
+ '''Check whether this point is between two other points.
506
+
507
+ If this point is not on the great circle arc defined by
508
+ both points, return whether it is within the area bound
509
+ by perpendiculars to the great circle at each point (in
510
+ the same hemispere).
511
+
512
+ @arg point1: Start point of the arc (L{LatLon}).
513
+ @arg point2: End point of the arc (L{LatLon}).
514
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
515
+ B{C{point1}} and B{C{point2}} (C{bool}).
516
+
517
+ @return: C{True} if this point is within the (great circle)
518
+ arc, C{False} otherwise.
519
+
520
+ @raise TypeError: If B{C{point1}} or B{C{point2}} is not
521
+ L{LatLon}.
522
+ '''
523
+ p1 = self.others(point1=point1)
524
+ p2 = self.others(point2=point2)
525
+ if wrap:
526
+ p1 = _Wrap.point(p1)
527
+ p2 = _unrollon(p1, p2, wrap=wrap)
528
+ n, n1, n2 = (_.toNvector() for _ in (self, p1, p2))
529
+
530
+ # corner case, null arc
531
+ if n1.isequalTo(n2):
532
+ return n.isequalTo(n1) or n.isequalTo(n2) # PYCHOK returns
533
+
534
+ if n.dot(n1) < 0 or n.dot(n2) < 0: # different hemisphere
535
+ return False # PYCHOK returns
536
+
537
+ # get vectors representing d0=p0->p1 and d2=p2->p1 and the
538
+ # dot product d0⋅d2 tells us if p0 is on the p2 side of p1 or
539
+ # on the other side (similarly for d0=p0->p2 and d1=p1->p2
540
+ # and dot product d0⋅d1 and p0 on the p1 side of p2 or not)
541
+ return n.minus(n1).dot(n2.minus(n1)) >= 0 and \
542
+ n.minus(n2).dot(n1.minus(n2)) >= 0
543
+
544
+ @deprecated_method
545
+ def isWithin(self, point1, point2): # PYCHOK no cover
546
+ '''DEPRECATED, use method C{iswithin}.'''
547
+ return self.iswithin(point1, point2)
548
+
549
+ def midpointTo(self, other, height=None, fraction=_0_5, wrap=False):
550
+ '''Find the midpoint between this and an other point.
551
+
552
+ @arg other: The other point (L{LatLon}).
553
+ @kwarg height: Optional height at the midpoint, overriding
554
+ the mean height (C{meter}).
555
+ @kwarg fraction: Midpoint location from this point (C{scalar}),
556
+ may be negative or greater than 1.0.
557
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
558
+ B{C{other}} point (C{bool}).
559
+
560
+ @return: Midpoint (L{LatLon}).
561
+
562
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
563
+
564
+ @see: Methods C{intermediateTo} and C{rhumbMidpointTo}.
565
+ '''
566
+ if fraction is _0_5:
567
+ p = self.others(other)
568
+ if wrap:
569
+ p = _unrollon(self, p, wrap=wrap)
570
+ m = self.toNvector().plus(p.toNvector())
571
+ h = self._havg(other, f=fraction, h=height)
572
+ r = m.toLatLon(height=h, LatLon=self.classof)
573
+ else:
574
+ r = self.intermediateTo(other, fraction, height=height, wrap=wrap)
575
+ return r
576
+
577
+ def nearestOn(self, point1, point2, height=None, within=True, wrap=False):
578
+ '''Locate the point on the great circle arc between two points
579
+ closest to this point.
580
+
581
+ @arg point1: Start point of the arc (L{LatLon}).
582
+ @arg point2: End point of the arc (L{LatLon}).
583
+ @kwarg height: Optional height, overriding the mean height for
584
+ the point within the arc (C{meter}), or C{None}
585
+ to interpolate the height.
586
+ @kwarg within: If C{True}, return the closest point between both
587
+ given points, otherwise the closest point
588
+ elsewhere on the great circle arc (C{bool}).
589
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
590
+ B{C{point1}} and B{C{point2}} (C{bool}).
591
+
592
+ @return: Closest point on the arc (L{LatLon}).
593
+
594
+ @raise NotImplementedError: Keyword argument C{B{wrap}=True}
595
+ not supported.
596
+
597
+ @raise TypeError: Invalid B{C{point1}} or B{C{point2}}.
598
+ '''
599
+ p1 = self.others(point1=point1)
600
+ p2 = self.others(point2=point2)
601
+ if wrap:
602
+ p1 = _Wrap.point(p1)
603
+ p2 = _unrollon(p1, p2, wrap=wrap)
604
+ p0 = self
605
+
606
+ if p0.iswithin(p1, p2) and not p1.isequalTo(p2, EPS):
607
+ # closer to arc than to its endpoints,
608
+ # find the closest point on the arc
609
+ gc1 = p1.toNvector().cross(p2.toNvector())
610
+ gc2 = p0.toNvector().cross(gc1)
611
+ n = gc1.cross(gc2)
612
+
613
+ elif within: # for backward compatibility, XXX unwrapped
614
+ return point1 if (self.distanceTo(point1) <
615
+ self.distanceTo(point2)) else point2
616
+
617
+ else: # handle beyond arc extent by .vector3d.nearestOn
618
+ n1 = p1.toNvector()
619
+ n2 = p2.toNvector()
620
+ n = p0.toNvector().nearestOn(n1, n2, within=False)
621
+ if n is n1:
622
+ return p1 # is point1
623
+ elif n is n2:
624
+ return p2 # is point2 if not wrap
625
+
626
+ p = n.toLatLon(height=height or 0, LatLon=self.classof)
627
+ if height in (None, False): # interpolate height within extent
628
+ d = p1.distanceTo(p2)
629
+ f = (p1.distanceTo(p) / d) if d > EPS0 else _0_5
630
+ p.height = p1._havg(p2, f=max(_0_0, min(f, _1_0)))
631
+ return p
632
+
633
+ # @deprecated_method
634
+ def nearestOn2(self, points, **closed_radius_height): # PYCHOK no cover
635
+ '''DEPRECATED, use method L{sphericalNvector.LatLon.nearestOn3}.
636
+
637
+ @return: ... 2-Tuple C{(closest, distance)} of the C{closest}
638
+ point (L{LatLon}) on the polygon and the C{distance}
639
+ to that point from this point ...
640
+ '''
641
+ r = self.nearestOn3(points, **closed_radius_height)
642
+ return r.closest, r.distance
643
+
644
+ def nearestOn3(self, points, closed=False, radius=R_M, height=None, wrap=False):
645
+ '''Locate the point on a path or polygon (with great circle
646
+ arcs joining consecutive points) closest to this point.
647
+
648
+ The closest point is either on within the extent of any great
649
+ circle arc or the nearest of the arc's end points.
650
+
651
+ @arg points: The path or polygon points (L{LatLon}[]).
652
+ @kwarg closed: Optionally, close the polygon (C{bool}).
653
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
654
+ @kwarg height: Optional height, overriding the mean height
655
+ for a point within the arc (C{meter}).
656
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll
657
+ the B{C{points}} (C{bool}).
658
+
659
+ @return: A L{NearestOn3Tuple}C{(closest, distance, angle)} of
660
+ the C{closest} point (L{LatLon}), the C{distance}
661
+ between this and the C{closest} point in C{meter},
662
+ same units as B{C{radius}} or in C{radians} if
663
+ B{C{radius}} is C{None} and the C{angle} from this
664
+ to the C{closest} point in compass C{degrees360}.
665
+
666
+ @raise TypeError: Some B{C{points}} are not C{LatLon}.
667
+
668
+ @raise ValueError: No B{C{points}}.
669
+ '''
670
+ Ps = self.PointsIter(points, loop=1, wrap=wrap)
671
+ _r = self.distanceTo
672
+ _n = self.nearestOn
673
+
674
+ c = p1 = Ps[0]
675
+ r = _r(c, radius=None) # radians
676
+ for p2 in Ps.iterate(closed=closed):
677
+ if wrap and not Ps.looped:
678
+ p2 = _unrollon(p1, p2)
679
+ p = _n(p1, p2, height=height)
680
+ d = _r(p, radius=None) # radians
681
+ if d < r:
682
+ c, r = p, d
683
+ p1 = p2
684
+ d = r if radius is None else (Radius(radius) * r)
685
+ return NearestOn3Tuple(c, d, degrees360(r))
686
+
687
+ def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian, datum=None
688
+ '''Convert this point to C{Nvector}-based cartesian (ECEF) coordinates.
689
+
690
+ @kwarg Cartesian_and_kwds: Optional L{Cartesian} and L{Cartesian} keyword
691
+ arguments, like C{datum}. Use C{B{Cartesian}=...}
692
+ to override this L{Cartesian} class or specify
693
+ C{B{Cartesian}=None}.
694
+
695
+ @return: A L{Cartesian} or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,
696
+ z, lat, lon, height, C, M, datum)} with C{C} and C{M} if available.
697
+
698
+ @raise TypeError: Invalid L{Cartesian} or other B{C{Cartesian_and_kwds}} item.
699
+ '''
700
+ kwds = _xkwds(Cartesian_and_kwds, Cartesian=Cartesian, datum=self.datum)
701
+ return LatLonSphericalBase.toCartesian(self, **kwds)
702
+
703
+ def toNvector(self, **Nvector_and_kwds): # PYCHOK signature
704
+ '''Convert this point to C{Nvector} components, I{including height}.
705
+
706
+ @kwarg Nvector_and_kwds: Optional C{Nvector} and C{Nvector} keyword arguments.
707
+ Specify C{B{Nvector}=...} to override this C{Nvector}
708
+ class or use C{B{Nvector}=None}.
709
+
710
+ @return: An C{Nvector} or if B{C{Nvector}} is C{None}, a L{Vector4Tuple}C{(x, y, z, h)}.
711
+
712
+ @raise TypeError: Invalid C{Nvector} or other B{C{Nvector_and_kwds}} item.
713
+ '''
714
+ return LatLonNvectorBase.toNvector(self, **_xkwds(Nvector_and_kwds, Nvector=Nvector))
715
+
716
+
717
+ class Nvector(NvectorBase):
718
+ '''An n-vector is a position representation using a (unit) vector
719
+ normal to the earth's surface. Unlike lat-/longitude points,
720
+ n-vectors have no singularities or discontinuities.
721
+
722
+ For many applications, n-vectors are more convenient to work
723
+ with than other position representations like lat-/longitude,
724
+ earth-centred earth-fixed (ECEF) vectors, UTM coordinates, etc.
725
+
726
+ On a spherical model earth, an n-vector is equivalent to an
727
+ earth-centred earth-fixed (ECEF) vector.
728
+
729
+ Note commonality with L{pygeodesy.ellipsoidalNvector.Nvector}.
730
+ '''
731
+ _datum = Datums.Sphere # default datum (L{Datum})
732
+
733
+ @property_RO
734
+ def sphericalNvector(self):
735
+ '''Get this C{Nvector}'s spherical class.
736
+ '''
737
+ return type(self)
738
+
739
+ def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian
740
+ '''Convert this n-vector to C{Nvector}-based cartesian
741
+ (ECEF) coordinates.
742
+
743
+ @kwarg Cartesian_and_kwds: Optional L{Cartesian} and L{Cartesian} keyword
744
+ arguments, like C{h}. Use C{B{Cartesian}=...}
745
+ to override this L{Cartesian} class or specify
746
+ C{B{Cartesian}=None}.
747
+
748
+ @return: The cartesian point (L{Cartesian}) or if B{C{Cartesian}} is
749
+ set to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
750
+ C, M, datum)} with C{C} and C{M} if available.
751
+
752
+ @raise TypeError: Invalid B{C{Cartesian_and_kwds}} argument.
753
+ '''
754
+ kwds = _xkwds(Cartesian_and_kwds, h=self.h, Cartesian=Cartesian)
755
+ return NvectorBase.toCartesian(self, **kwds) # class or .classof
756
+
757
+ def toLatLon(self, **LatLon_and_kwds): # PYCHOK height=None, LatLon=LatLon
758
+ '''Convert this n-vector to an C{Nvector}-based geodetic point.
759
+
760
+ @kwarg LatLon_and_kwds: Optional L{LatLon} and L{LatLon} keyword
761
+ arguments, like C{height}. Use C{B{LatLon}=...}
762
+ to override this L{LatLon} class or specify
763
+ C{B{LatLon}=None}.
764
+
765
+ @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is set
766
+ to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
767
+ C, M, datum)} with C{C} and C{M} if available.
768
+
769
+ @raise TypeError: Invalid B{C{LatLon_and_kwds}} argument.
770
+
771
+ @raise ValueError: Invalid B{C{height}}.
772
+ '''
773
+ kwds = _xkwds(LatLon_and_kwds, height=self.h, LatLon=LatLon)
774
+ return NvectorBase.toLatLon(self, **kwds) # class or .classof
775
+
776
+ def greatCircle(self, bearing):
777
+ '''Compute the n-vector normal to great circle obtained by
778
+ heading on given compass bearing from this point as its
779
+ n-vector.
780
+
781
+ Direction of vector is such that initial bearing vector
782
+ b = c × p.
783
+
784
+ @arg bearing: Initial compass bearing (C{degrees}).
785
+
786
+ @return: N-vector representing great circle (C{Nvector}).
787
+
788
+ @raise Valuerror: Polar coincidence.
789
+ '''
790
+ s, c = sincos2d(Bearing(bearing))
791
+
792
+ e = NorthPole.cross(self, raiser=_pole_) # easting
793
+ n = self.cross(e, raiser=_point_) # northing
794
+
795
+ e = e.times(c / e.length)
796
+ n = n.times(s / n.length)
797
+ return n.minus(e)
798
+
799
+
800
+ _Nvll = LatLon(_0_0, _0_0, name=_Nv00_) # reference instance (L{LatLon})
801
+
802
+
803
+ def areaOf(points, radius=R_M, wrap=False):
804
+ '''Calculate the area of a (spherical) polygon or composite (with
805
+ great circle arcs joining consecutive points).
806
+
807
+ @arg points: The polygon points or clips (C{LatLon}[],
808
+ L{BooleanFHP} or L{BooleanGH}).
809
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
810
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
811
+ B{C{points}} (C{bool}).
812
+
813
+ @return: Polygon area (C{meter} I{squared} , same units as
814
+ B{C{radius}}, or C{radians} if B{C{radius}} is C{None}).
815
+
816
+ @raise PointsError: Insufficient number of B{C{points}}.
817
+
818
+ @raise TypeError: Some B{C{points}} are not L{LatLon}.
819
+
820
+ @see: Functions L{pygeodesy.areaOf}, L{sphericalTrigonometry.areaOf}
821
+ and L{ellipsoidalKarney.areaOf}.
822
+ '''
823
+ def _interangles(ps, w): # like .karney._polygon
824
+ Ps = _Nvll.PointsIter(ps, loop=2, wrap=w)
825
+ # use vector to 1st point as plane normal for sign of α
826
+ n0 = Ps[0].toNvector()
827
+
828
+ v2 = Ps[0]._N_vector # XXX v2 == no?
829
+ p1 = Ps[1]
830
+ v1 = p1._N_vector
831
+ gc = v2.cross(v1)
832
+ for p2 in Ps.iterate(closed=True):
833
+ if w and not Ps.looped:
834
+ p2 = _unrollon(p1, p2)
835
+ p1 = p2
836
+ v2 = p2._N_vector
837
+ gc1 = v1.cross(v2)
838
+ v1 = v2
839
+ yield gc.angleTo(gc1, vSign=n0)
840
+ gc = gc1
841
+
842
+ if _MODS.booleans.isBoolean(points):
843
+ r = points._sum2(LatLon, areaOf, radius=None, wrap=wrap)
844
+ else:
845
+ # sum interior angles: depending on whether polygon is cw or ccw,
846
+ # angle between edges is π−α or π+α, where α is angle between
847
+ # great-circle vectors; so sum α, then take n·π − |Σα| (cannot
848
+ # use Σ(π−|α|) as concave polygons would fail)
849
+ s = fsum(_interangles(points, wrap), floats=True)
850
+ # using Girard’s theorem: A = [Σθᵢ − (n−2)·π]·R²
851
+ # (PI2 - abs(s) == (n*PI - abs(s)) - (n-2)*PI)
852
+ r = fabs(PI2 - fabs(s))
853
+ return r if radius is None else (r * Radius(radius)**2)
854
+
855
+
856
+ def intersecant2(center, circle, point, other, **radius_exact_height_wrap):
857
+ '''Compute the intersections of a circle and a (great circle) line given as
858
+ two points or as a point and bearing.
859
+
860
+ @arg center: Center of the circle (L{LatLon}).
861
+ @arg circle: Radius of the circle (C{meter}, same units as B{C{radius}})
862
+ or a point on the circle (L{LatLon}).
863
+ @arg point: A point on the (great circle) line (L{LatLon}).
864
+ @arg other: An other point on the (great circle) line (L{LatLon}) or
865
+ the bearing at the B{C{point}} (compass C{degrees360}).
866
+ @kwarg radius_exact_height_wrap: Optional keyword arguments, see
867
+ method L{LatLon.intersecant2} for further details.
868
+
869
+ @return: 2-Tuple of the intersection points (representing a chord), each
870
+ an instance of the B{C{point}} class. Both points are the same
871
+ instance if the (great circle) line is tangent to the circle.
872
+
873
+ @raise IntersectionError: The circle and line do not intersect.
874
+
875
+ @raise TypeError: If B{C{center}} or B{C{point}} not L{LatLon} or
876
+ B{C{circle}} or B{C{other}} invalid.
877
+
878
+ @raise UnitError: Invalid B{C{circle}}, B{C{other}}, B{C{radius}},
879
+ B{C{exact}}, B{C{height}} or B{C{napieradius}}.
880
+ '''
881
+ c = _Nvll.others(center=center)
882
+ p = _Nvll.others(point=point)
883
+ try:
884
+ return _intersecant2(c, circle, p, other, **radius_exact_height_wrap)
885
+ except (TypeError, ValueError) as x:
886
+ raise _xError(x, center=center, circle=circle, point=point, other=other,
887
+ **radius_exact_height_wrap)
888
+
889
+
890
+ def intersection(start1, end1, start2, end2, height=None, wrap=False,
891
+ LatLon=LatLon, **LatLon_kwds):
892
+ '''Locate the intersections of two (great circle) lines each defined
893
+ by two points or by a start point and an (initial) bearing.
894
+
895
+ @arg start1: Start point of the first line (L{LatLon}).
896
+ @arg end1: End point of the first line (L{LatLon}) or the initial
897
+ bearing at the first start point (compass C{degrees360}).
898
+ @arg start2: Start point of the second line (L{LatLon}).
899
+ @arg end2: End point of the second line (L{LatLon}) or the initial
900
+ bearing at the second start point (compass C{degrees360}).
901
+ @kwarg height: Optional height at the intersection point,
902
+ overriding the mean height (C{meter}).
903
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{start2}}
904
+ and both B{C{end*}} points (C{bool}).
905
+ @kwarg LatLon: Optional class to return the intersection point
906
+ (L{LatLon}).
907
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
908
+ arguments, ignored if C{B{LatLon} is None}.
909
+
910
+ @return: The intersection point (B{C{LatLon}}) or if C{B{LatLon}
911
+ is None}, a cartesian L{Ecef9Tuple}C{(x, y, z, lat, lon,
912
+ height, C, M, datum)} with C{C} and C{M} if available.
913
+
914
+ @raise TypeError: If B{C{start*}} or B{C{end*}} is not L{LatLon}.
915
+
916
+ @raise ValueError: Intersection is ambiguous or infinite or
917
+ the lines are parallel, coincident or null.
918
+
919
+ @see: Function L{sphericalNvector.intersection2}.
920
+ '''
921
+ i, _, h = _intersect3(start1, end1, start2, end2, height, wrap)
922
+ kwds = _xkwds(LatLon_kwds, height=h, LatLon=LatLon)
923
+ return i.toLatLon(**kwds)
924
+
925
+
926
+ def intersection2(start1, end1, start2, end2, height=None, wrap=False,
927
+ LatLon=LatLon, **LatLon_kwds):
928
+ '''Locate the intersections of two (great circle) lines each defined
929
+ by two points or by a start point and an (initial) bearing.
930
+
931
+ @arg start1: Start point of the first line (L{LatLon}).
932
+ @arg end1: End point of the first line (L{LatLon}) or the
933
+ initial bearing at the first start point
934
+ (compass C{degrees360}).
935
+ @arg start2: Start point of the second line (L{LatLon}).
936
+ @arg end2: End point of the second line (L{LatLon}) or the
937
+ initial bearing at the second start point
938
+ (compass C{degrees360}).
939
+ @kwarg height: Optional height at the intersection and antipodal
940
+ point, overriding the mean height (C{meter}).
941
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{start2}}
942
+ and both B{C{end*}} points (C{bool}).
943
+ @kwarg LatLon: Optional class to return the intersection and
944
+ antipodal points (L{LatLon}).
945
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
946
+ arguments, ignored if C{B{LatLon} is None}.
947
+
948
+ @return: 2-Tuple C{(intersection, antipode)}, each a (B{C{LatLon}})
949
+ or if C{B{LatLon} is None}, a cartesian L{Ecef9Tuple}C{(x,
950
+ y, z, lat, lon, height, C, M, datum)} with C{C} and C{M}
951
+ if available.
952
+
953
+ @raise TypeError: If B{C{start*}} or B{C{end*}} is not L{LatLon}.
954
+
955
+ @raise ValueError: Intersection is ambiguous or infinite or
956
+ the lines are parallel, coincident or null.
957
+
958
+ @see: Function L{sphericalNvector.intersection}.
959
+ '''
960
+ i, a, h = _intersect3(start1, end1, start2, end2, height, wrap)
961
+ kwds = _xkwds(LatLon_kwds, height=h, LatLon=LatLon)
962
+ return i.toLatLon(**kwds), a.toLatLon(**kwds)
963
+
964
+
965
+ def _intersect3(start1, end1, start2, end2, height, wrap):
966
+ '''(INTERNAL) Return the intersection and antipodal points for
967
+ functions C{intersection} and C{intersection2}.
968
+ '''
969
+ p1 = _Nvll.others(start1=start1)
970
+ p2 = _Nvll.others(start2=start2)
971
+ if wrap:
972
+ p2 = _unrollon(p1, p2, wrap=wrap)
973
+ # If gc1 and gc2 are great circles through start and end points
974
+ # (or defined by start point and bearing), then the candidate
975
+ # intersections are simply gc1 × gc2 and gc2 × gc1. Most of the
976
+ # work is deciding the correct intersection point to select! If
977
+ # bearing is given, that determines the intersection, but if both
978
+ # lines are defined by start/end points, take closer intersection.
979
+ gc1, s1, e1 = _Nvll._gc3(p1, end1, 'end1', wrap=wrap)
980
+ gc2, s2, e2 = _Nvll._gc3(p2, end2, 'end2', wrap=wrap)
981
+
982
+ hs = start1.height, start2.height
983
+ # there are two (antipodal) candidate intersection
984
+ # points ... we have to choose the one to return
985
+ i1 = gc1.cross(gc2, raiser=_lines_)
986
+ i2 = gc2.cross(gc1, raiser=_lines_)
987
+
988
+ # selection of intersection point depends on how
989
+ # lines are defined (by bearings or endpoints)
990
+ if e1 and e2: # endpoint+endpoint
991
+ d = sumOf((s1, s2, e1, e2)).dot(i1)
992
+ hs += end1.height, end2.height
993
+ elif e1 and not e2: # endpoint+bearing
994
+ # gc2 x v2 . i1 +ve means v2 bearing points to i1
995
+ d = gc2.cross(s2).dot(i1)
996
+ hs += end1.height,
997
+ elif e2 and not e1: # bearing+endpoint
998
+ # gc1 x v1 . i1 +ve means v1 bearing points to i1
999
+ d = gc1.cross(s1).dot(i1)
1000
+ hs += end2.height,
1001
+ else: # bearing+bearing
1002
+ # if gc x v . i1 is +ve, initial bearing is
1003
+ # towards i1, otherwise towards antipodal i2
1004
+ d1 = gc1.cross(s1).dot(i1) # +ve means p1 bearing points to i1
1005
+ d2 = gc2.cross(s2).dot(i1) # +ve means p2 bearing points to i1
1006
+ if d1 > 0 and d2 > 0:
1007
+ d = 1 # both point to i1
1008
+ elif d1 < 0 and d2 < 0:
1009
+ d = -1 # both point to i2
1010
+ else: # d1, d2 opposite signs
1011
+ # intersection is at further-away intersection point,
1012
+ # take opposite intersection from mid- point of v1
1013
+ # and v2 [is this always true?] XXX changed to always
1014
+ # get intersection p1 bearing points to, aka being
1015
+ # located "after" p1 along the bearing at p1, like
1016
+ # function .sphericalTrigonometry._intersect and
1017
+ # .ellipsoidalBaseDI._intersect3
1018
+ d = d1 # neg(s1.plus(s2).dot(i1))
1019
+
1020
+ h = fmean(hs) if height is None else height
1021
+ return (i1, i2, h) if d > 0 else (i2, i1, h)
1022
+
1023
+
1024
+ def meanOf(points, height=None, wrap=False, LatLon=LatLon, **LatLon_kwds):
1025
+ '''Compute the I{geographic} mean of the supplied points.
1026
+
1027
+ @arg points: Array of points to be averaged (L{LatLon}[]).
1028
+ @kwarg height: Optional height, overriding the mean height
1029
+ (C{meter}).
1030
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{points}} (C{bool}).
1031
+ @kwarg LatLon: Optional class to return the mean point (L{LatLon}).
1032
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1033
+ arguments, ignored if C{B{LatLon} is None}.
1034
+
1035
+ @return: Point at geographic mean and mean height (B{C{LatLon}}).
1036
+
1037
+ @raise PointsError: Insufficient number of B{C{points}} or
1038
+ some B{C{points}} are not C{LatLon}.
1039
+ '''
1040
+ def _N_vs(ps, w):
1041
+ Ps = _Nvll.PointsIter(ps, wrap=w)
1042
+ for p in Ps.iterate(closed=False):
1043
+ yield p._N_vector
1044
+
1045
+ try:
1046
+ # geographic mean
1047
+ n = _nsumOf(_N_vs(points, wrap), height, Nvector, {})
1048
+ except (TypeError, ValueError) as x:
1049
+ raise PointsError(points=points, wrap=wrap, LatLon=LatLon, cause=x)
1050
+ return n.toLatLon(**_xkwds(LatLon_kwds, LatLon=LatLon, height=n.h,
1051
+ name=meanOf.__name__))
1052
+
1053
+
1054
+ @deprecated_function
1055
+ def nearestOn2(point, points, **closed_radius_height): # PYCHOK no cover
1056
+ '''DEPRECATED, use method L{sphericalNvector.nearestOn3}.
1057
+
1058
+ @return: ... 2-Tuple C{(closest, distance)} of the C{closest}
1059
+ point (L{LatLon}) on the polygon and the C{distance}
1060
+ between the C{closest} and the given B{C{point}} ...
1061
+ '''
1062
+ r = nearestOn3(point, points, **closed_radius_height)
1063
+ return r.closest, r.distance
1064
+
1065
+
1066
+ def nearestOn3(point, points, closed=False, radius=R_M, height=None, wrap=False):
1067
+ '''Locate the point on a polygon (with great circle arcs joining
1068
+ consecutive points) closest to an other point.
1069
+
1070
+ If the given point is between the end points of a great circle
1071
+ arc, the closest point is on that arc. Otherwise, the closest
1072
+ point is the nearest of the arc's end points.
1073
+
1074
+ @arg point: The other, reference point (L{LatLon}).
1075
+ @arg points: The polygon points (L{LatLon}[]).
1076
+ @kwarg closed: Optionally, close the polygon (C{bool}).
1077
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
1078
+ @kwarg height: Optional height, overriding the mean height for
1079
+ a point within the (great circle) arc (C{meter}).
1080
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
1081
+ B{C{points}} (C{bool}).
1082
+
1083
+ @return: A L{NearestOn3Tuple}C{(closest, distance, angle)} of
1084
+ the C{closest} point (L{LatLon}) on the polygon, the
1085
+ C{distance} and the C{angle} between the C{closest}
1086
+ and the given B{C{point}}. The C{distance} is in
1087
+ C{meter}, same units as B{C{radius}} or in C{radians}
1088
+ if B{C{radius}} is C{None}, the C{angle} is in compass
1089
+ C{degrees360}.
1090
+
1091
+ @raise PointsError: Insufficient number of B{C{points}}.
1092
+
1093
+ @raise TypeError: Some B{C{points}} or B{C{point}} not C{LatLon}.
1094
+ '''
1095
+ _MODS.basics._xinstanceof(LatLon, point=point)
1096
+
1097
+ return point.nearestOn3(points, closed=closed, radius=radius,
1098
+ height=height, wrap=wrap)
1099
+
1100
+
1101
+ def perimeterOf(points, closed=False, radius=R_M, wrap=False):
1102
+ '''Compute the perimeter of a (spherical) polygon or composite (with
1103
+ great circle arcs joining consecutive points).
1104
+
1105
+ @arg points: The polygon points (L{LatLon}[]).
1106
+ @kwarg closed: Optionally, close the polygon (C{bool}).
1107
+ @kwarg radius: Mean earth radius (C{meter}) or C{None}.
1108
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
1109
+ B{C{points}} (C{bool}).
1110
+
1111
+ @return: Polygon perimeter (C{meter}, same units as B{C{radius}}
1112
+ or C{radians} if B{C{radius}} is C{None}).
1113
+
1114
+ @raise PointsError: Insufficient number of B{C{points}}.
1115
+
1116
+ @raise TypeError: Some B{C{points}} are not L{LatLon}.
1117
+
1118
+ @raise ValueError: Invalid B{C{radius}} or C{B{closed}=False} with
1119
+ C{B{points}} a composite.
1120
+
1121
+ @see: Functions L{pygeodesy.perimeterOf}, L{ellipsoidalKarney.perimeterOf}
1122
+ and L{sphericalTrigonometry.perimeterOf}.
1123
+ '''
1124
+ def _rads(ps, c, w): # angular edge lengths in radians
1125
+ Ps = _Nvll.PointsIter(ps, loop=1, wrap=w)
1126
+ p1 = Ps[0]
1127
+ v1 = p1._N_vector
1128
+ for p2 in Ps.iterate(closed=c):
1129
+ if w and not (c and Ps.looped):
1130
+ p2 = _unrollon(p1, p2)
1131
+ p1 = p2
1132
+ v2 = p2._N_vector
1133
+ yield v1.angleTo(v2)
1134
+ v1 = v2
1135
+
1136
+ if _MODS.booleans.isBoolean(points):
1137
+ if not closed:
1138
+ notImplemented(None, closed=closed, points=_composite_)
1139
+ r = points._sum2(LatLon, perimeterOf, closed=True, radius=None, wrap=wrap)
1140
+ else:
1141
+ r = fsum(_rads(points, closed, wrap), floats=True)
1142
+ return r if radius is None else (Radius(radius) * r)
1143
+
1144
+
1145
+ def sumOf(nvectors, Vector=Nvector, h=None, **Vector_kwds):
1146
+ '''Return the I{vectorial} sum of two or more n-vectors.
1147
+
1148
+ @arg nvectors: Vectors to be added (C{Nvector}[]).
1149
+ @kwarg Vector: Optional class for the vectorial sum (C{Nvector}).
1150
+ @kwarg h: Optional height, overriding the mean height (C{meter}).
1151
+ @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword arguments.
1152
+
1153
+ @return: Vectorial sum (B{C{Vector}}).
1154
+
1155
+ @raise VectorError: No B{C{nvectors}}.
1156
+ '''
1157
+ try:
1158
+ return _nsumOf(nvectors, h, Vector, Vector_kwds)
1159
+ except (TypeError, ValueError) as x:
1160
+ raise VectorError(nvectors=nvectors, Vector=Vector, cause=x)
1161
+
1162
+
1163
+ def triangulate(point1, bearing1, point2, bearing2,
1164
+ height=None, wrap=False,
1165
+ LatLon=LatLon, **LatLon_kwds):
1166
+ '''Locate a point given two known points and the (initial) bearing
1167
+ from those points.
1168
+
1169
+ @arg point1: First reference point (L{LatLon}).
1170
+ @arg bearing1: Bearing at the first point (compass C{degrees360}).
1171
+ @arg point2: Second reference point (L{LatLon}).
1172
+ @arg bearing2: Bearing at the second point (compass C{degrees360}).
1173
+ @kwarg height: Optional height at the triangulated point, overriding
1174
+ the mean height (C{meter}).
1175
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{point2}}
1176
+ (C{bool}).
1177
+ @kwarg LatLon: Optional class to return the triangulated point (L{LatLon}).
1178
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
1179
+ arguments, ignored if C{B{LatLon} is None}.
1180
+
1181
+ @return: Triangulated point (B{C{LatLon}}).
1182
+
1183
+ @raise TypeError: If B{C{point1}} or B{C{point2}} is not L{LatLon}.
1184
+
1185
+ @raise Valuerror: Points coincide.
1186
+ '''
1187
+ return _triangulate(_Nvll.others(point1=point1), bearing1,
1188
+ _Nvll.others(point2=point2), bearing2,
1189
+ height=height, wrap=wrap,
1190
+ LatLon=LatLon, **LatLon_kwds)
1191
+
1192
+
1193
+ def trilaterate(point1, distance1, point2, distance2, point3, distance3, # PYCHOK args
1194
+ radius=R_M, height=None, useZ=False, wrap=False,
1195
+ LatLon=LatLon, **LatLon_kwds):
1196
+ '''Locate a point at given distances from three other points.
1197
+
1198
+ @arg point1: First point (L{LatLon}).
1199
+ @arg distance1: Distance to the first point (C{meter}, same units
1200
+ as B{C{radius}}).
1201
+ @arg point2: Second point (L{LatLon}).
1202
+ @arg distance2: Distance to the second point (C{meter}, same units
1203
+ as B{C{radius}}).
1204
+ @arg point3: Third point (L{LatLon}).
1205
+ @arg distance3: Distance to the third point (C{meter}, same units
1206
+ as B{C{radius}}).
1207
+ @kwarg radius: Mean earth radius (C{meter}).
1208
+ @kwarg height: Optional height at the trilaterated point, overriding
1209
+ the IDW height (C{meter}, same units as B{C{radius}}).
1210
+ @kwarg useZ: Include Z component iff non-NaN, non-zero (C{bool}).
1211
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll B{C{point2}}
1212
+ and B{C{point3}} (C{bool}).
1213
+ @kwarg LatLon: Optional class to return the trilaterated point (L{LatLon}).
1214
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments,
1215
+ ignored if C{B{LatLon} is None}.
1216
+
1217
+ @return: Trilaterated point (B{C{LatLon}}).
1218
+
1219
+ @raise IntersectionError: No intersection, trilateration failed.
1220
+
1221
+ @raise TypeError: Invalid B{C{point1}}, B{C{point2}} or B{C{point3}}.
1222
+
1223
+ @raise ValueError: Coincident B{C{points}} or invalid B{C{distance1}},
1224
+ B{C{distance2}}, B{C{distance3}} or B{C{radius}}.
1225
+
1226
+ @see: U{Trilateration<https://WikiPedia.org/wiki/Trilateration>}.
1227
+ '''
1228
+ return _trilaterate(_Nvll.others(point1=point1), distance1,
1229
+ _Nvll.others(point2=point2), distance2,
1230
+ _Nvll.others(point3=point3), distance3,
1231
+ radius=radius, height=height, useZ=useZ,
1232
+ wrap=wrap, LatLon=LatLon, **LatLon_kwds)
1233
+
1234
+
1235
+ __all__ += _ALL_OTHER(Cartesian, LatLon, Nvector, # classes
1236
+ areaOf, # functions
1237
+ intersecant2, intersection, intersection2, ispolar,
1238
+ meanOf,
1239
+ nearestOn2, nearestOn3,
1240
+ perimeterOf,
1241
+ sumOf,
1242
+ triangulate, trilaterate)
1243
+
1244
+ # **) MIT License
1245
+ #
1246
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1247
+ #
1248
+ # Permission is hereby granted, free of charge, to any person obtaining a
1249
+ # copy of this software and associated documentation files (the "Software"),
1250
+ # to deal in the Software without restriction, including without limitation
1251
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
1252
+ # and/or sell copies of the Software, and to permit persons to whom the
1253
+ # Software is furnished to do so, subject to the following conditions:
1254
+ #
1255
+ # The above copyright notice and this permission notice shall be included
1256
+ # in all copies or substantial portions of the Software.
1257
+ #
1258
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1259
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1260
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
1261
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1262
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1263
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1264
+ # OTHER DEALINGS IN THE SOFTWARE.