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,685 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Ellipsoidal, C{N-vector}-based geodesy.
5
+
6
+ Ellipsoidal classes geodetic L{LatLon}, geocentric (ECEF) L{Cartesian}
7
+ and C{Nvector} and DEPRECATED L{Ned} and functions L{meanOf}, L{sumOf}
8
+ and DEPRECATED L{toNed}.
9
+
10
+ Pure Python implementation of n-vector-based geodetic (lat-/longitude)
11
+ methods by I{(C) Chris Veness 2011-2016} published under the same MIT
12
+ Licence**, see U{Vector-based geodesy
13
+ <https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
14
+
15
+ These classes and functions work with: (a) geodetic lat-/longitude points on
16
+ the earth's surface and (b) 3-D vectors used as n-vectors representing points
17
+ on the earth's surface or vectors normal to the plane of a great circle.
18
+
19
+ See also I{Kenneth Gade} U{'A Non-singular Horizontal Position Representation'
20
+ <https://www.NavLab.net/Publications/A_Nonsingular_Horizontal_Position_Representation.pdf>},
21
+ The Journal of Navigation (2010), vol 63, nr 3, pp 395-417.
22
+ '''
23
+ # make sure int/int division yields float quotient, see .basics
24
+ from __future__ import division as _; del _ # PYCHOK semicolon
25
+
26
+ from pygeodesy.basics import issubclassof, map2, _xinstanceof
27
+ from pygeodesy.datums import _earth_ellipsoid, _ellipsoidal_datum, _WGS84
28
+ # from pygeodesy.dms import toDMS # _MODS
29
+ from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, \
30
+ _nearestOn, LatLonEllipsoidalBase, \
31
+ _TOL_M, _Wrap
32
+ from pygeodesy.errors import _IsnotError, _xkwds, _xkwds_pop2
33
+ # from pygeodesy.fmath import fdot # from .nvectorBase
34
+ from pygeodesy.interns import NN, _Nv00_, _COMMASPACE_
35
+ from pygeodesy.interns import _down_, _east_, _north_, _pole_ # PYCHOK used!
36
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _ALL_OTHER
37
+ # from pygeodesy.ltp import Ltp # _MODS
38
+ from pygeodesy.ltpTuples import Aer as _Aer, Ned as _Ned, Ned4Tuple, \
39
+ sincos2d_, _xnamed
40
+ # from pygeodesy.named import _xnamed # from .ltpTuples
41
+ from pygeodesy.nvectorBase import fabs, fdot, NorthPole, LatLonNvectorBase, \
42
+ NvectorBase, sumOf as _sumOf
43
+ from pygeodesy.props import deprecated_class, deprecated_function, \
44
+ deprecated_method, Property_RO, property_RO
45
+ from pygeodesy.streprs import Fmt, fstr, _xzipairs
46
+ from pygeodesy.units import Bearing, Distance, Height, Scalar
47
+ # from pygeodesy.utily import sincos2d_, _Wrap # from .ltpTuples, .ellipsoidalBase
48
+
49
+ # from math import fabs # from .nvectorBase
50
+
51
+ __all__ = _ALL_LAZY.ellipsoidalNvector
52
+ __version__ = '24.02.18'
53
+
54
+
55
+ class Ned(_Ned):
56
+ '''DEPRECATED on 2024.02.04, use class L{pygeodesy.Ned}.'''
57
+
58
+ def __init__(self, north, east, down, name=NN):
59
+ deprecated_class(self.__class__)
60
+ _Ned.__init__(self, north, east, down, name=name)
61
+
62
+ @deprecated_method # PYCHOK expected
63
+ def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused):
64
+ '''DEPRECATED, use class L{pygeodesy.Ned}.
65
+
66
+ @kwarg prec: Number of (decimal) digits, unstripped (C{int}).
67
+ @kwarg fmt: Enclosing backets format (C{str}).
68
+ @kwarg sep: Separator between NEDs (C{str}).
69
+
70
+ @return: This Ned as "[L:f, B:degrees360, E:degrees90]" (C{str})
71
+ with length or slantrange C{L}, bearing or azimuth C{B}
72
+ and elevation C{E}.
73
+ '''
74
+ dms = _MODS.dms
75
+ t = (fstr(self.slantrange, prec=prec),
76
+ dms.toDMS(self.azimuth, form=dms.F_D, prec=prec, ddd=0),
77
+ dms.toDMS(self.elevation, form=dms.F_D, prec=prec, ddd=0))
78
+ return _xzipairs('LBE', t, sep=sep, fmt=fmt)
79
+
80
+
81
+ class Cartesian(CartesianEllipsoidalBase):
82
+ '''Extended to convert geocentric, L{Cartesian} points to
83
+ C{Nvector} and n-vector-based, geodetic L{LatLon}.
84
+ '''
85
+ @property_RO
86
+ def Ecef(self):
87
+ '''Get the ECEF I{class} (L{EcefVeness}), I{once}.
88
+ '''
89
+ return _Ecef()
90
+
91
+ def toLatLon(self, **LatLon_and_kwds): # PYCHOK LatLon=LatLon, datum=None
92
+ '''Convert this cartesian to an C{Nvector}-based geodetic point.
93
+
94
+ @kwarg LatLon_and_kwds: Optional L{LatLon}, B{C{datum}} and other
95
+ keyword arguments. Use C{B{LatLon}=...} to
96
+ override this L{LatLon} class or specify
97
+ C{B{LatLon} is None}.
98
+
99
+ @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is set
100
+ to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
101
+ C, M, datum)} with C{C} and C{M} if available.
102
+
103
+ @raise TypeError: Invalid B{C{LatLon_and_kwds}}.
104
+ '''
105
+ kwds = _xkwds(LatLon_and_kwds, LatLon=LatLon, datum=self.datum)
106
+ return CartesianEllipsoidalBase.toLatLon(self, **kwds)
107
+
108
+ def toNvector(self, **Nvector_and_kwds): # PYCHOK Datums.WGS84
109
+ '''Convert this cartesian to C{Nvector} components, I{including height}.
110
+
111
+ @kwarg Nvector_and_kwds: Optional C{Nvector}, B{C{datum}} and other
112
+ keyword arguments. Use C{B{Nvector}=...} to
113
+ override this C{Nvector} class or specify
114
+ C{B{Nvector} is None}.
115
+
116
+ @return: The C{n-vector} components (C{Nvector}) or if B{C{Nvector}}
117
+ is set to C{None}, a L{Vector4Tuple}C{(x, y, z, h)}
118
+
119
+ @raise TypeError: Invalid B{C{Nvector_and_kwds}}.
120
+ '''
121
+ kwds = _xkwds(Nvector_and_kwds, Nvector=Nvector, datum=self.datum)
122
+ return CartesianEllipsoidalBase.toNvector(self, **kwds)
123
+
124
+
125
+ class LatLon(LatLonNvectorBase, LatLonEllipsoidalBase):
126
+ '''An n-vector-based, ellipsoidal L{LatLon} point.
127
+ '''
128
+ _Nv = None # cached toNvector (C{Nvector})
129
+
130
+ def _update(self, updated, *attrs, **setters): # PYCHOK args
131
+ '''(INTERNAL) Zap cached attributes if updated.
132
+ '''
133
+ if updated:
134
+ LatLonNvectorBase._update(self, updated, _Nv=self._Nv) # special case
135
+ LatLonEllipsoidalBase._update(self, updated, *attrs, **setters)
136
+
137
+ # def crossTrackDistanceTo(self, start, end, radius=R_M):
138
+ # '''Return the (signed) distance from this point to the great
139
+ # circle defined by a start point and an end point or bearing.
140
+ #
141
+ # @arg start: Start point of great circle line (L{LatLon}).
142
+ # @arg end: End point of great circle line (L{LatLon}) or
143
+ # initial bearing (compass C{degrees360}) at the
144
+ # start point.
145
+ # @kwarg radius: Mean earth radius (C{meter}).
146
+ #
147
+ # @return: Distance to great circle, negative if to left or
148
+ # positive if to right of line (C{meter}, same units
149
+ # as B{C{radius}}).
150
+ #
151
+ # @raise TypeError: If B{C{start}} or B{C{end}} point is not L{LatLon}.
152
+ # '''
153
+ # self.others(start=start)
154
+ #
155
+ # if _isDegrees(end): # gc from point and bearing
156
+ # gc = start.greatCircle(end)
157
+ # else: # gc by two points
158
+ # gc = start.toNvector().cross(end.toNvector())
159
+ #
160
+ # # (signed) angle between point and gc normal vector
161
+ # v = self.toNvector()
162
+ # a = gc.angleTo(v, vSign=v.cross(gc))
163
+ # a = _copysign(PI_2, a) - a
164
+ # return a * float(radius)
165
+
166
+ def deltaTo(self, other, wrap=False, **Ned_and_kwds):
167
+ '''Calculate the local delta from this to an other point.
168
+
169
+ @note: This is a linear delta, I{unrelated} to a geodesic on the
170
+ ellipsoid.
171
+
172
+ @arg other: The other point (L{LatLon}).
173
+ @kwarg wrap: If C{True}, wrap or I{normalize} the B{C{other}}
174
+ point (C{bool}).
175
+ @kwarg Ned_and_kwds: Optional C{B{Ned}=L{Ned} class and B{name}=NN}
176
+ to return delta and other B{C{Ned}} keyword arguments.
177
+
178
+ @return: Delta from this to the other point (B{C{Ned}}).
179
+
180
+ @raise TypeError: The B{C{other}} point is not L{LatLon} or
181
+ B{C{Ned}} is not L{pygeodesy.Ned} nor
182
+ L{pygeodesy.Ned4Tuple} nor DEPRECATED L{Ned}.
183
+
184
+ @raise ValueError: If ellipsoids are incompatible.
185
+ '''
186
+ self.ellipsoids(other) # throws TypeError and ValueError
187
+
188
+ p = self.others(other)
189
+ if wrap:
190
+ p = _Wrap.point(p)
191
+ # get delta in cartesian frame
192
+ dc = p.toCartesian().minus(self.toCartesian())
193
+ # rotate dc to get delta in n-vector reference
194
+ # frame using the rotation matrix row vectors
195
+ ned_ = map2(dc.dot, self._rotation3)
196
+
197
+ N, kwds = _xkwds_pop2(Ned_and_kwds, Ned=Ned)
198
+ if issubclassof(N, Ned4Tuple):
199
+ ned_ += _MODS.ltp.Ltp(self, ecef=self.Ecef(self.datum)),
200
+ elif not issubclassof(N, _Ned):
201
+ raise _IsnotError(Fmt.sub_class(_Ned, Ned4Tuple), Ned=N)
202
+ return N(*ned_, **_xkwds(kwds, name=self.name))
203
+
204
+ # def destination(self, distance, bearing, radius=R_M, height=None):
205
+ # '''Return the destination point after traveling from this
206
+ # point the given distance on the given initial bearing.
207
+ #
208
+ # @arg distance: Distance traveled (C{meter}, same units as
209
+ # given earth B{C{radius}}).
210
+ # @arg bearing: Initial bearing (compass C{degrees360}).
211
+ # @kwarg radius: Mean earth radius (C{meter}).
212
+ # @kwarg height: Optional height at destination point,
213
+ # overriding default (C{meter}, same units
214
+ # as B{C{radius}}).
215
+ #
216
+ # @return: Destination point (L{LatLon}).
217
+ # '''
218
+ # r = _m2radians(distance, radius) # angular distance in radians
219
+ # # great circle by starting from this point on given bearing
220
+ # gc = self.greatCircle(bearing)
221
+ #
222
+ # v1 = self.toNvector()
223
+ # x = v1.times(cos(r)) # component of v2 parallel to v1
224
+ # y = gc.cross(v1).times(sin(r)) # component of v2 perpendicular to v1
225
+ #
226
+ # v2 = x.plus(y).unit()
227
+ # return v2.toLatLon(height=self.height if height is C{None} else height)
228
+
229
+ def destinationNed(self, delta):
230
+ '''Calculate the destination point using the supplied NED delta
231
+ from this point.
232
+
233
+ @arg delta: Delta from this to the other point in the local
234
+ tangent plane (LTP) of this point (L{Ned}).
235
+
236
+ @return: Destination point (L{LatLon}).
237
+
238
+ @raise TypeError: If B{C{delta}} is not L{pygeodesy.Ned} or
239
+ DEPRECATED L{Ned}.
240
+ '''
241
+ _xinstanceof(_Ned, delta=delta)
242
+
243
+ nv, ev, dv = self._rotation3
244
+ # convert NED delta to standard coordinate frame of n-vector
245
+ dn = delta.ned[:3] # XXX Ned4Tuple.to3Tuple
246
+ # rotate dn to get delta in cartesian (ECEF) coordinate
247
+ # reference frame using the rotation matrix column vectors
248
+ dc = Cartesian(fdot(dn, nv.x, ev.x, dv.x),
249
+ fdot(dn, nv.y, ev.y, dv.y),
250
+ fdot(dn, nv.z, ev.z, dv.z))
251
+
252
+ # apply (cartesian) delta to this Cartesian to obtain destination as cartesian
253
+ v = self.toCartesian().plus(dc)
254
+ return v.toLatLon(datum=self.datum, LatLon=self.classof) # Cartesian(v.x, v.y, v.z).toLatLon(...)
255
+
256
+ def distanceTo(self, other, radius=None, wrap=False):
257
+ '''I{Approximate} the distance from this to an other point.
258
+
259
+ @arg other: The other point (L{LatLon}).
260
+ @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
261
+ L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or
262
+ L{a_f2Tuple}), overriding the mean radius C{R1}
263
+ of this point's datum..
264
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
265
+ B{C{other}} and angular distance (C{bool}).
266
+
267
+ @return: Distance (C{meter}, same units as B{C{radius}}).
268
+
269
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
270
+
271
+ @raise ValueError: Invalid B{C{radius}}.
272
+ '''
273
+ p = self.others(other)
274
+ if wrap:
275
+ p = _Wrap.point(p)
276
+ a = self._N_vector.angleTo(p._N_vector, wrap=wrap)
277
+ E = self.datum.ellipsoid if radius is None else _earth_ellipsoid(radius)
278
+ return fabs(a) * E.R1 # see .utily.radians2m
279
+
280
+ @property_RO
281
+ def Ecef(self):
282
+ '''Get the ECEF I{class} (L{EcefVeness}), I{once}.
283
+ '''
284
+ return _Ecef()
285
+
286
+ @deprecated_method
287
+ def equals(self, other, eps=None): # PYCHOK no cover
288
+ '''DEPRECATED, use method L{isequalTo}.
289
+ '''
290
+ return self.isequalTo(other, eps=eps)
291
+
292
+ def isequalTo(self, other, eps=None):
293
+ '''Compare this point with an other point.
294
+
295
+ @arg other: The other point (L{LatLon}).
296
+ @kwarg eps: Optional margin (C{float}).
297
+
298
+ @return: C{True} if points are identical, including
299
+ datum, I{ignoring height}, C{False} otherwise.
300
+
301
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
302
+
303
+ @raise ValueError: Invalid B{C{eps}}.
304
+
305
+ @see: Method C{isequalTo3} to include I{height}.
306
+ '''
307
+ return self.datum == self.others(other).datum and \
308
+ _MODS.formy._isequalTo(self, other, eps=eps)
309
+
310
+ # def greatCircle(self, bearing):
311
+ # '''Return the great circle heading on the given bearing
312
+ # from this point.
313
+ #
314
+ # Direction of vector is such that initial bearing vector
315
+ # b = c × p, where p is representing this point.
316
+ #
317
+ # @arg bearing: Bearing from this point (compass C{degrees360}).
318
+ #
319
+ # @return: N-vector representing great circle (C{Nvector}).
320
+ # '''
321
+ # a, b, _ = self.philamheight
322
+ # t = radians(bearing)
323
+ #
324
+ # sa, ca, sb, cb, st, ct = sincos2_(a, b, t)
325
+ # return self._xnamed(Nvector(sb * ct - sa * cb * st,
326
+ # -cb * ct - sa * sb * st,
327
+ # ca * st)
328
+
329
+ # def initialBearingTo(self, other, wrap=False):
330
+ # '''Return the initial bearing (forward azimuth) from
331
+ # this to an other point.
332
+ #
333
+ # @arg other: The other point (L{LatLon}).
334
+ # @kwarg wrap: If C{True}, wrap or I{normalize}
335
+ # and unroll the B{C{other}} (C{bool}).
336
+ #
337
+ # @return: Initial bearing (compass C{degrees360}).
338
+ #
339
+ # @raise TypeError: The B{C{other}} point is not L{LatLon}.
340
+ # '''
341
+ # p = self.others(other)
342
+ # if wrap:
343
+ # p = _Wrap.point(p)
344
+ # v1 = self.toNvector()
345
+ #
346
+ # gc1 = v1.cross(p.toNvector()) # gc through v1 & v2
347
+ # gc2 = v1.cross(_NP3) # gc through v1 & North pole
348
+ #
349
+ # # bearing is (signed) angle between gc1 & gc2
350
+ # return degrees360(gc1.angleTo(gc2, vSign=v1))
351
+
352
+ def intermediateTo(self, other, fraction, height=None, wrap=False):
353
+ '''Return the point at given fraction between this and
354
+ an other point.
355
+
356
+ @arg other: The other point (L{LatLon}).
357
+ @arg fraction: Fraction between both points (C{scalar},
358
+ 0.0 at this to 1.0 at the other point.
359
+ @kwarg height: Optional height, overriding the fractional
360
+ height (C{meter}).
361
+ @kwarg wrap: If C{True}, wrap or I{normalize} the
362
+ B{C{other}} point (C{bool}).
363
+
364
+ @return: Intermediate point (L{LatLon}).
365
+
366
+ @raise TypeError: The B{C{other}} point is not L{LatLon}.
367
+ '''
368
+ p = self.others(other)
369
+ if wrap:
370
+ p = _Wrap.point(p)
371
+ f = Scalar(fraction=fraction)
372
+ h = self._havg(other, f=f, h=height)
373
+ i = self.toNvector().intermediateTo(p.toNvector(), f)
374
+ return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)
375
+
376
+ @Property_RO
377
+ def _rotation3(self):
378
+ '''(INTERNAL) Get the rotation matrix from n-vector coordinate frame axes.
379
+ '''
380
+ nv = self.toNvector() # local (n-vector) coordinate frame
381
+
382
+ dv = nv.negate() # down, opposite to n-vector
383
+ ev = NorthPole.cross(nv, raiser=_pole_).unit() # east, pointing perpendicular to the plane
384
+ nv = ev.cross(dv) # north, by right hand rule
385
+ return nv, ev, dv # matrix rows
386
+
387
+ def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian, datum=None
388
+ '''Convert this point to an C{Nvector}-based geodetic point.
389
+
390
+ @kwarg Cartesian_and_kwds: Optional L{Cartesian}, B{C{datum}} and other
391
+ keyword arguments. Use C{B{Cartesian}=...}
392
+ to override this L{Cartesian} class or specify
393
+ C{B{Cartesian}=None}.
394
+
395
+ @return: The geodetic point (L{Cartesian}) or if B{C{Cartesian}} is set
396
+ to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
397
+ datum)} with C{C} and C{M} if available.
398
+
399
+ @raise TypeError: Invalid B{C{Cartesian}} or other B{C{Cartesian_and_kwds}}.
400
+ '''
401
+ kwds = _xkwds(Cartesian_and_kwds, Cartesian=Cartesian, datum=self.datum)
402
+ return LatLonEllipsoidalBase.toCartesian(self, **kwds)
403
+
404
+ def toNvector(self, **Nvector_and_kwds): # PYCHOK signature
405
+ '''Convert this point to C{Nvector} components, I{including height}.
406
+
407
+ @kwarg Nvector_and_kwds: Optional C{Nvector}, B{C{datum}} and other
408
+ keyword arguments. Use C{B{Nvector}=...}
409
+ to override this C{Nvector} class or specify
410
+ C{B{Nvector}=None}.
411
+
412
+ @return: The C{n-vector} components (C{Nvector}) or if B{C{Nvector}}
413
+ is set to C{None}, a L{Vector4Tuple}C{(x, y, z, h)}.
414
+
415
+ @raise TypeError: Invalid B{C{Nvector}} or other B{C{Nvector_and_kwds}}.
416
+ '''
417
+ kwds = _xkwds(Nvector_and_kwds, Nvector=Nvector, datum=self.datum)
418
+ return LatLonNvectorBase.toNvector(self, **kwds)
419
+
420
+
421
+ _Nvll = LatLon(0, 0, name=_Nv00_) # reference instance (L{LatLon})
422
+
423
+
424
+ class Nvector(NvectorBase):
425
+ '''An n-vector is a position representation using a (unit) vector
426
+ normal to the earth ellipsoid. Unlike lat-/longitude points,
427
+ n-vectors have no singularities or discontinuities.
428
+
429
+ For many applications, n-vectors are more convenient to work
430
+ with than other position representations like lat-/longitude,
431
+ earth-centred earth-fixed (ECEF) vectors, UTM coordinates, etc.
432
+
433
+ Note commonality with L{pygeodesy.sphericalNvector.Nvector}.
434
+ '''
435
+ _datum = _WGS84 # default datum (L{Datum})
436
+
437
+ def __init__(self, x_xyz, y=None, z=None, h=0, datum=None, ll=None, name=NN):
438
+ '''New n-vector normal to the earth's surface.
439
+
440
+ @arg x_xyz: X component of vector (C{scalar}) or (3-D) vector
441
+ (C{Nvector}, L{Vector3d}, L{Vector3Tuple} or
442
+ L{Vector4Tuple}).
443
+ @kwarg y: Y component of vector (C{scalar}), ignored if B{C{x_xyz}}
444
+ is not C{scalar}, otherwise same units as B{C{x_xyz}}.
445
+ @kwarg z: Z component of vector (C{scalar}), ignored if B{C{x_xyz}}
446
+ is not C{scalar}, otherwise same units as B{C{x_xyz}}.
447
+ @kwarg h: Optional height above model surface (C{meter}).
448
+ @kwarg datum: Optional datum this n-vector is defined in
449
+ (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or
450
+ L{a_f2Tuple}).
451
+ @kwarg ll: Optional, original latlon (C{LatLon}).
452
+ @kwarg name: Optional name (C{str}).
453
+
454
+ @raise TypeError: If B{C{datum}} is not a L{Datum}.
455
+ '''
456
+ NvectorBase.__init__(self, x_xyz, y=y, z=z, h=h, ll=ll, name=name)
457
+ if datum not in (None, self._datum):
458
+ self._datum = _ellipsoidal_datum(datum, name=name)
459
+
460
+ @Property_RO
461
+ def datum(self):
462
+ '''Get this n-vector's datum (L{Datum}).
463
+ '''
464
+ return self._datum
465
+
466
+ @property_RO
467
+ def ellipsoidalNvector(self):
468
+ '''Get this C{Nvector}'s ellipsoidal class.
469
+ '''
470
+ return type(self)
471
+
472
+ def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian
473
+ '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates.
474
+
475
+ @kwarg Cartesian_and_kwds: Optional L{Cartesian}, B{C{h}}, B{C{datum}} and
476
+ other keyword arguments. Use C{B{Cartesian}=...}
477
+ to override this L{Cartesian} class or specify
478
+ C{B{Cartesian} is None}.
479
+
480
+ @return: The cartesian point (L{Cartesian}) or if B{C{Cartesian}} is set
481
+ to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,
482
+ datum)} with C{C} and C{M} if available.
483
+
484
+ @raise TypeError: Invalid B{C{Cartesian_and_kwds}}.
485
+ '''
486
+ kwds = _xkwds(Cartesian_and_kwds, h=self.h, Cartesian=Cartesian,
487
+ datum=self.datum)
488
+ return NvectorBase.toCartesian(self, **kwds) # class or .classof
489
+
490
+ def toLatLon(self, **LatLon_and_kwds): # PYCHOK height=None, LatLon=LatLon
491
+ '''Convert this n-vector to an C{Nvector}-based geodetic point.
492
+
493
+ @kwarg LatLon_and_kwds: Optional L{LatLon}, B{C{height}}, B{C{datum}}
494
+ and other keyword arguments. Use C{B{LatLon}=...}
495
+ to override this L{LatLon} class or specify
496
+ C{B{LatLon} is None}.
497
+
498
+ @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is set
499
+ to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,
500
+ C, M, datum)} with C{C} and C{M} if available.
501
+
502
+ @raise TypeError: Invalid B{C{LatLon_and_kwds}}.
503
+ '''
504
+ kwds = _xkwds(LatLon_and_kwds, height=self.h, datum=self.datum, LatLon=LatLon)
505
+ return NvectorBase.toLatLon(self, **kwds) # class or .classof
506
+
507
+ def unit(self, ll=None):
508
+ '''Normalize this vector to unit length.
509
+
510
+ @kwarg ll: Optional, original latlon (C{LatLon}).
511
+
512
+ @return: Normalised vector (C{Nvector}).
513
+ '''
514
+ u = NvectorBase.unit(self, ll=ll)
515
+ if u.datum != self.datum:
516
+ u._update(False, datum=self.datum)
517
+ return u
518
+
519
+
520
+ def _Ecef():
521
+ # return the Ecef class and overwrite property_RO
522
+ Cartesian.Ecef = LatLon.Ecef = E = _MODS.ecef.EcefVeness
523
+ return E
524
+
525
+
526
+ def meanOf(points, datum=_WGS84, height=None, wrap=False,
527
+ **LatLon_and_kwds):
528
+ '''Compute the geographic mean of several points.
529
+
530
+ @arg points: Points to be averaged (L{LatLon}[]).
531
+ @kwarg datum: Optional datum to use (L{Datum}).
532
+ @kwarg height: Optional height at mean point, overriding
533
+ the mean height (C{meter}).
534
+ @kwarg wrap: If C{True}, wrap or I{normalize} B{C{points}}
535
+ (C{bool}).
536
+ @kwarg LatLon_and_kwds: Optional B{C{LatLon}} class to return
537
+ the mean points and overriding this L{LatLon}
538
+ (or C{None}) and additional B{C{LatLon}}
539
+ keyword arguments, ignored if C{B{LatLon}
540
+ is None}.
541
+
542
+ @return: Geographic mean point and mean height (B{C{LatLon}})
543
+ or if B{C{LatLon}} is C{None}, an L{Ecef9Tuple}C{(x,
544
+ y, z, lat, lon, height, C, M, datum)} with C{C} and
545
+ C{M} if available.
546
+
547
+ @raise ValueError: Insufficient number of B{C{points}}.
548
+ '''
549
+ Ps = _Nvll.PointsIter(points, wrap=wrap)
550
+ # geographic mean
551
+ m = sumOf(p._N_vector for p in Ps.iterate(closed=False))
552
+ kwds = _xkwds(LatLon_and_kwds, height=height, datum=datum,
553
+ LatLon=LatLon, name=meanOf.__name__)
554
+ return m.toLatLon(**kwds)
555
+
556
+
557
+ def nearestOn(point, point1, point2, within=True, height=None, wrap=False,
558
+ equidistant=None, tol=_TOL_M, LatLon=LatLon, **LatLon_kwds):
559
+ '''I{Iteratively} locate the closest point on the geodesic between
560
+ two other (ellipsoidal) points.
561
+
562
+ @arg point: Reference point (C{LatLon}).
563
+ @arg point1: Start point of the geodesic (C{LatLon}).
564
+ @arg point2: End point of the geodesic (C{LatLon}).
565
+ @kwarg within: If C{True} return the closest point I{between}
566
+ B{C{point1}} and B{C{point2}}, otherwise the
567
+ closest point elsewhere on the geodesic (C{bool}).
568
+ @kwarg height: Optional height for the closest point (C{meter},
569
+ conventionally) or C{None} or C{False} for the
570
+ interpolated height. If C{False}, the closest
571
+ takes the heights of the points into account.
572
+ @kwarg wrap: If C{True}, wrap or I{normalize} and unroll I{only}
573
+ B{C{point1}} and B{C{point2}} (C{bool}).
574
+ @kwarg equidistant: An azimuthal equidistant projection (I{class}
575
+ or function L{pygeodesy.equidistant}) or C{None}
576
+ for the preferred C{B{point}.Equidistant}.
577
+ @kwarg tol: Convergence tolerance (C{meter}).
578
+ @kwarg LatLon: Optional class to return the closest point
579
+ (L{LatLon}) or C{None}.
580
+ @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword
581
+ arguments, ignored if C{B{LatLon} is None}.
582
+
583
+ @return: Closest point, a B{C{LatLon}} instance or if C{B{LatLon}
584
+ is None}, a L{LatLon4Tuple}C{(lat, lon, height, datum)}.
585
+
586
+ @raise ImportError: Package U{geographiclib
587
+ <https://PyPI.org/project/geographiclib>}
588
+ not installed or not found.
589
+
590
+ @raise TypeError: Invalid or non-ellipsoidal B{C{point}}, B{C{point1}}
591
+ or B{C{point2}} or invalid B{C{equidistant}}.
592
+
593
+ @raise ValueError: No convergence for the B{C{tol}}.
594
+
595
+ @see: U{The B{ellipsoidal} case<https://GIS.StackExchange.com/questions/48937/
596
+ calculating-intersection-of-two-circles>} and U{Karney's paper
597
+ <https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section B{14. MARITIME
598
+ BOUNDARIES} for more details about the iteration algorithm.
599
+ '''
600
+ return _nearestOn(point, point1, point2, within=within, height=height, wrap=wrap,
601
+ equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds)
602
+
603
+
604
+ def sumOf(nvectors, Vector=Nvector, h=None, **Vector_kwds):
605
+ '''Return the vectorial sum of two or more n-vectors.
606
+
607
+ @arg nvectors: Vectors to be added (C{Nvector}[]).
608
+ @kwarg Vector: Optional class for the vectorial sum (C{Nvector}).
609
+ @kwarg h: Optional height, overriding the mean height (C{meter}).
610
+ @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword
611
+ arguments, ignored if C{B{Vector} is None}.
612
+
613
+ @return: Vectorial sum (B{C{Vector}}).
614
+
615
+ @raise VectorError: No B{C{nvectors}}.
616
+ '''
617
+ return _sumOf(nvectors, Vector=Vector, h=h, **Vector_kwds)
618
+
619
+
620
+ @deprecated_function
621
+ def toNed(distance, bearing, elevation, Ned=Ned, name=NN):
622
+ '''DEPRECATED, use L{pygeodesy.Aer}C{(bearing, elevation,
623
+ distance).xyzLocal.toNed(B{Ned}, name=B{name})} or
624
+ L{XyzLocal}C{(pygeodesy.Aer(bearing, elevation,
625
+ distance)).toNed(B{Ned}, name=B{name})}.
626
+
627
+ Create an NED vector from distance, bearing and elevation
628
+ (in local coordinate system).
629
+
630
+ @arg distance: NED vector length (C{meter}).
631
+ @arg bearing: NED vector bearing (compass C{degrees360}).
632
+ @arg elevation: NED vector elevation from local coordinate
633
+ frame horizontal (C{degrees}).
634
+ @kwarg Ned: Optional class to return the NED (C{Ned}) or
635
+ C{None}.
636
+ @kwarg name: Optional name (C{str}).
637
+
638
+ @return: An NED vector equivalent to this B{C{distance}},
639
+ B{C{bearing}} and B{C{elevation}} (DEPRECATED L{Ned})
640
+ or a DEPRECATED L{Ned3Tuple}C{(north, east, down)}
641
+ if C{B{Ned} is None}.
642
+
643
+ @raise ValueError: Invalid B{C{distance}}, B{C{bearing}}
644
+ or B{C{elevation}}.
645
+ '''
646
+ if True: # use new Aer class
647
+ n, e, d, _ = _Aer(bearing, elevation, distance).xyz4
648
+ else: # DEPRECATED
649
+ d = Distance(distance)
650
+
651
+ sb, cb, se, ce = sincos2d_(Bearing(bearing),
652
+ Height(elevation=elevation))
653
+ n = cb * d * ce
654
+ e = sb * d * ce
655
+ d *= se
656
+
657
+ r = _MODS.deprecated.classes.Ned3Tuple(n, e, -d) if Ned is None else \
658
+ Ned(n, e, -d)
659
+ return _xnamed(r, name)
660
+
661
+
662
+ __all__ += _ALL_OTHER(Cartesian, LatLon, Ned, Nvector, # classes
663
+ meanOf, sumOf, toNed)
664
+
665
+ # **) MIT License
666
+ #
667
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
668
+ #
669
+ # Permission is hereby granted, free of charge, to any person obtaining a
670
+ # copy of this software and associated documentation files (the "Software"),
671
+ # to deal in the Software without restriction, including without limitation
672
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
673
+ # and/or sell copies of the Software, and to permit persons to whom the
674
+ # Software is furnished to do so, subject to the following conditions:
675
+ #
676
+ # The above copyright notice and this permission notice shall be included
677
+ # in all copies or substantial portions of the Software.
678
+ #
679
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
680
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
681
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
682
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
683
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
684
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
685
+ # OTHER DEALINGS IN THE SOFTWARE.