Coverage for pygeodesy/ellipsoidalNvector.py: 96%
137 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-06 16:50 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-05-06 16:50 -0400
2# -*- coding: utf-8 -*-
4u'''Ellipsoidal, C{N-vector}-based geodesy.
6Ellipsoidal classes geodetic L{LatLon}, geocentric (ECEF) L{Cartesian}
7and C{Nvector} and DEPRECATED L{Ned} and functions L{meanOf}, L{sumOf}
8and DEPRECATED L{toNed}.
10Pure Python implementation of n-vector-based geodetic (lat-/longitude)
11methods by I{(C) Chris Veness 2011-2016} published under the same MIT
12Licence**, see U{Vector-based geodesy
13<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
15These classes and functions work with: (a) geodetic lat-/longitude points on
16the earth's surface and (b) 3-D vectors used as n-vectors representing points
17on the earth's surface or vectors normal to the plane of a great circle.
19See also I{Kenneth Gade} U{'A Non-singular Horizontal Position Representation'
20<https://www.NavLab.net/Publications/A_Nonsingular_Horizontal_Position_Representation.pdf>},
21The Journal of Navigation (2010), vol 63, nr 3, pp 395-417.
22'''
23# make sure int/int division yields float quotient, see .basics
24from __future__ import division as _; del _ # PYCHOK semicolon
26from pygeodesy.basics import issubclassof, map2, _xinstanceof
27from pygeodesy.datums import _earth_ellipsoid, _ellipsoidal_datum, _WGS84
28# from pygeodesy.dms import toDMS # _MODS
29from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, \
30 _nearestOn, LatLonEllipsoidalBase, \
31 _TOL_M, _Wrap
32from pygeodesy.errors import _IsnotError, _xkwds, _xkwds_pop2
33# from pygeodesy.fmath import fdot # from .nvectorBase
34from pygeodesy.interns import NN, _Nv00_, _COMMASPACE_
35from pygeodesy.interns import _down_, _east_, _north_, _pole_ # PYCHOK used!
36from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _ALL_OTHER
37# from pygeodesy.ltp import Ltp # _MODS
38from pygeodesy.ltpTuples import Aer as _Aer, Ned as _Ned, Ned4Tuple, \
39 sincos2d_, _xnamed
40# from pygeodesy.named import _xnamed # from .ltpTuples
41from pygeodesy.nvectorBase import fabs, fdot, NorthPole, LatLonNvectorBase, \
42 NvectorBase, sumOf as _sumOf
43from pygeodesy.props import deprecated_class, deprecated_function, \
44 deprecated_method, Property_RO, property_RO
45from pygeodesy.streprs import Fmt, fstr, _xzipairs
46from pygeodesy.units import Bearing, Distance, Height, Scalar
47# from pygeodesy.utily import sincos2d_, _Wrap # from .ltpTuples, .ellipsoidalBase
49# from math import fabs # from .nvectorBase
51__all__ = _ALL_LAZY.ellipsoidalNvector
52__version__ = '24.02.18'
55class Ned(_Ned):
56 '''DEPRECATED on 2024.02.04, use class L{pygeodesy.Ned}.'''
58 def __init__(self, north, east, down, name=NN):
59 deprecated_class(self.__class__)
60 _Ned.__init__(self, north, east, down, name=name)
62 @deprecated_method # PYCHOK expected
63 def toRepr(self, prec=None, fmt=Fmt.SQUARE, sep=_COMMASPACE_, **unused):
64 '''DEPRECATED, use class L{pygeodesy.Ned}.
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}).
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)
81class 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()
91 def toLatLon(self, **LatLon_and_kwds): # PYCHOK LatLon=LatLon, datum=None
92 '''Convert this cartesian to an C{Nvector}-based geodetic point.
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}.
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.
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)
108 def toNvector(self, **Nvector_and_kwds): # PYCHOK Datums.WGS84
109 '''Convert this cartesian to C{Nvector} components, I{including height}.
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}.
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)}
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)
125class LatLon(LatLonNvectorBase, LatLonEllipsoidalBase):
126 '''An n-vector-based, ellipsoidal L{LatLon} point.
127 '''
128 _Nv = None # cached toNvector (C{Nvector})
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)
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)
166 def deltaTo(self, other, wrap=False, **Ned_and_kwds):
167 '''Calculate the local delta from this to an other point.
169 @note: This is a linear delta, I{unrelated} to a geodesic on the
170 ellipsoid.
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.
178 @return: Delta from this to the other point (B{C{Ned}}).
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}.
184 @raise ValueError: If ellipsoids are incompatible.
185 '''
186 self.ellipsoids(other) # throws TypeError and ValueError
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)
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))
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)
229 def destinationNed(self, delta):
230 '''Calculate the destination point using the supplied NED delta
231 from this point.
233 @arg delta: Delta from this to the other point in the local
234 tangent plane (LTP) of this point (L{Ned}).
236 @return: Destination point (L{LatLon}).
238 @raise TypeError: If B{C{delta}} is not L{pygeodesy.Ned} or
239 DEPRECATED L{Ned}.
240 '''
241 _xinstanceof(_Ned, delta=delta)
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))
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(...)
256 def distanceTo(self, other, radius=None, wrap=False):
257 '''I{Approximate} the distance from this to an other point.
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}).
267 @return: Distance (C{meter}, same units as B{C{radius}}).
269 @raise TypeError: The B{C{other}} point is not L{LatLon}.
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
280 @property_RO
281 def Ecef(self):
282 '''Get the ECEF I{class} (L{EcefVeness}), I{once}.
283 '''
284 return _Ecef()
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)
292 def isequalTo(self, other, eps=None):
293 '''Compare this point with an other point.
295 @arg other: The other point (L{LatLon}).
296 @kwarg eps: Optional margin (C{float}).
298 @return: C{True} if points are identical, including
299 datum, I{ignoring height}, C{False} otherwise.
301 @raise TypeError: The B{C{other}} point is not L{LatLon}.
303 @raise ValueError: Invalid B{C{eps}}.
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)
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)
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))
352 def intermediateTo(self, other, fraction, height=None, wrap=False):
353 '''Return the point at given fraction between this and
354 an other point.
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}).
364 @return: Intermediate point (L{LatLon}).
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(...)
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
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
387 def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian, datum=None
388 '''Convert this point to an C{Nvector}-based geodetic point.
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}.
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.
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)
404 def toNvector(self, **Nvector_and_kwds): # PYCHOK signature
405 '''Convert this point to C{Nvector} components, I{including height}.
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}.
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)}.
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)
421_Nvll = LatLon(0, 0, name=_Nv00_) # reference instance (L{LatLon})
424class 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.
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.
433 Note commonality with L{pygeodesy.sphericalNvector.Nvector}.
434 '''
435 _datum = _WGS84 # default datum (L{Datum})
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.
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}).
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)
460 @Property_RO
461 def datum(self):
462 '''Get this n-vector's datum (L{Datum}).
463 '''
464 return self._datum
466 @property_RO
467 def ellipsoidalNvector(self):
468 '''Get this C{Nvector}'s ellipsoidal class.
469 '''
470 return type(self)
472 def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian
473 '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates.
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}.
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.
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
490 def toLatLon(self, **LatLon_and_kwds): # PYCHOK height=None, LatLon=LatLon
491 '''Convert this n-vector to an C{Nvector}-based geodetic point.
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}.
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.
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
507 def unit(self, ll=None):
508 '''Normalize this vector to unit length.
510 @kwarg ll: Optional, original latlon (C{LatLon}).
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
520def _Ecef():
521 # return the Ecef class and overwrite property_RO
522 Cartesian.Ecef = LatLon.Ecef = E = _MODS.ecef.EcefVeness
523 return E
526def meanOf(points, datum=_WGS84, height=None, wrap=False,
527 **LatLon_and_kwds):
528 '''Compute the geographic mean of several points.
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}.
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.
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)
557def 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.
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}.
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)}.
586 @raise ImportError: Package U{geographiclib
587 <https://PyPI.org/project/geographiclib>}
588 not installed or not found.
590 @raise TypeError: Invalid or non-ellipsoidal B{C{point}}, B{C{point1}}
591 or B{C{point2}} or invalid B{C{equidistant}}.
593 @raise ValueError: No convergence for the B{C{tol}}.
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)
604def sumOf(nvectors, Vector=Nvector, h=None, **Vector_kwds):
605 '''Return the vectorial sum of two or more n-vectors.
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}.
613 @return: Vectorial sum (B{C{Vector}}).
615 @raise VectorError: No B{C{nvectors}}.
616 '''
617 return _sumOf(nvectors, Vector=Vector, h=h, **Vector_kwds)
620@deprecated_function
621def 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})}.
627 Create an NED vector from distance, bearing and elevation
628 (in local coordinate system).
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}).
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}.
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)
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
657 r = _MODS.deprecated.classes.Ned3Tuple(n, e, -d) if Ned is None else \
658 Ned(n, e, -d)
659 return _xnamed(r, name)
662__all__ += _ALL_OTHER(Cartesian, LatLon, Ned, Nvector, # classes
663 meanOf, sumOf, toNed)
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.