1

2# -*- coding: utf-8 -*-

3

4 u'''Ellipsoidal, C{N-vector}-based geodesy.

5

6 Ellipsoidal classes geodetic (lat-/longitude) L{LatLon}, geocentric (ECEF)

7 L{Cartesian}, C{Nvector} and DEPRECATED L{Ned} and functions L{meanOf},

8 L{sumOf} 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

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__ = '23.12.14'

53

54

55 class Ned(_Ned):

56 '''DEPRECATED, 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=3 if prec is None else 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, Ned=Ned, wrap=False):

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 Ned: Class to use (L{pygeodesy.Ned} or L{pygeodesy.Ned4Tuple}),

174 overriding the default DEPRECATED L{Ned}.

175 @kwarg wrap: If C{True}, wrap or I{normalize} the B{C{other}}

176 point (C{bool}).

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 if issubclassof(Ned, Ned4Tuple):

197 ned_ += (_MODS.ltp.Ltp(self, ecef=self.Ecef(self.datum)),)

198 elif not issubclassof(Ned, _Ned):

199 raise _IsnotError(Fmt.sub_class(_Ned, Ned4Tuple), Ned=Ned)

200 return Ned(*ned_, name=self.name)

201

202# def destination(self, distance, bearing, radius=R_M, height=None):

203# '''Return the destination point after traveling from this

204# point the given distance on the given initial bearing.

205#

206# @arg distance: Distance traveled (C{meter}, same units as

207# given earth B{C{radius}}).

208# @arg bearing: Initial bearing (compass C{degrees360}).

209# @kwarg radius: Mean earth radius (C{meter}).

210# @kwarg height: Optional height at destination point,

211# overriding default (C{meter}, same units

212# as B{C{radius}}).

213#

214# @return: Destination point (L{LatLon}).

215# '''

216# r = _m2radians(distance, radius) # angular distance in radians

217# # great circle by starting from this point on given bearing

218# gc = self.greatCircle(bearing)

219#

220# v1 = self.toNvector()

221# x = v1.times(cos(r)) # component of v2 parallel to v1

222# y = gc.cross(v1).times(sin(r)) # component of v2 perpendicular to v1

223#

224# v2 = x.plus(y).unit()

225# return v2.toLatLon(height=self.height if height is C{None} else height)

226

227 def destinationNed(self, delta):

228 '''Calculate the destination point using the supplied NED delta

229 from this point.

230

231 @arg delta: Delta from this to the other point in the local

232 tangent plane (LTP) of this point (L{Ned}).

233

234 @return: Destination point (L{LatLon}).

235

236 @raise TypeError: If B{C{delta}} is not L{pygeodesy.Ned} or

237 DEPRECATED L{Ned}.

238 '''

239 _xinstanceof(_Ned, delta=delta)

240

241 nv, ev, dv = self._rotation3

242 # convert NED delta to standard coordinate frame of n-vector

243 dn = delta.ned

244 # rotate dn to get delta in cartesian (ECEF) coordinate

245 # reference frame using the rotation matrix column vectors

246 dc = Cartesian(fdot(dn, nv.x, ev.x, dv.x),

247 fdot(dn, nv.y, ev.y, dv.y),

248 fdot(dn, nv.z, ev.z, dv.z))

249

250 # apply (cartesian) delta to this Cartesian to obtain destination as cartesian

251 v = self.toCartesian().plus(dc)

252 return v.toLatLon(datum=self.datum, LatLon=self.classof) # Cartesian(v.x, v.y, v.z).toLatLon(...)

253

254 def distanceTo(self, other, radius=None, wrap=False):

255 '''I{Approximate} the distance from this to an other point.

256

257 @arg other: The other point (L{LatLon}).

258 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},

259 L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or

260 L{a_f2Tuple}), overriding the mean radius C{R1}

261 of this point's datum..

262 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the

263 B{C{other}} and angular distance (C{bool}).

264

265 @return: Distance (C{meter}, same units as B{C{radius}}).

266

267 @raise TypeError: The B{C{other}} point is not L{LatLon}.

268

269 @raise ValueError: Invalid B{C{radius}}.

270 '''

271 p = self.others(other)

272 if wrap:

273 p = _Wrap.point(p)

274 a = self._N_vector.angleTo(p._N_vector, wrap=wrap)

275 E = self.datum.ellipsoid if radius is None else _earth_ellipsoid(radius)

276 return fabs(a) * E.R1 # see .utily.radians2m

277

278 @property_RO

279 def Ecef(self):

280 '''Get the ECEF I{class} (L{EcefVeness}), I{once}.

281 '''

282 return _Ecef()

283

284 @deprecated_method

285 def equals(self, other, eps=None): # PYCHOK no cover

286 '''DEPRECATED, use method L{isequalTo}.

287 '''

288 return self.isequalTo(other, eps=eps)

289

290 def isequalTo(self, other, eps=None):

291 '''Compare this point with an other point.

292

293 @arg other: The other point (L{LatLon}).

294 @kwarg eps: Optional margin (C{float}).

295

296 @return: C{True} if points are identical, including

297 datum, I{ignoring height}, C{False} otherwise.

298

299 @raise TypeError: The B{C{other}} point is not L{LatLon}.

300

301 @raise ValueError: Invalid B{C{eps}}.

302

303 @see: Method C{isequalTo3} to include I{height}.

304 '''

305 return self.datum == self.others(other).datum and \

306 _MODS.formy._isequalTo(self, other, eps=eps)

307

308# def greatCircle(self, bearing):

309# '''Return the great circle heading on the given bearing

310# from this point.

311#

312# Direction of vector is such that initial bearing vector

313# b = c × p, where p is representing this point.

314#

315# @arg bearing: Bearing from this point (compass C{degrees360}).

316#

317# @return: N-vector representing great circle (C{Nvector}).

318# '''

319# a, b, _ = self.philamheight

320# t = radians(bearing)

321#

322# sa, ca, sb, cb, st, ct = sincos2_(a, b, t)

323# return self._xnamed(Nvector(sb * ct - sa * cb * st,

324# -cb * ct - sa * sb * st,

325# ca * st)

326

327# def initialBearingTo(self, other, wrap=False):

328# '''Return the initial bearing (forward azimuth) from

329# this to an other point.

330#

331# @arg other: The other point (L{LatLon}).

332# @kwarg wrap: If C{True}, wrap or I{normalize}

333# and unroll the B{C{other}} (C{bool}).

334#

335# @return: Initial bearing (compass C{degrees360}).

336#

337# @raise TypeError: The B{C{other}} point is not L{LatLon}.

338# '''

339# p = self.others(other)

340# if wrap:

341# p = _Wrap.point(p)

342# v1 = self.toNvector()

343#

344# gc1 = v1.cross(p.toNvector()) # gc through v1 & v2

345# gc2 = v1.cross(_NP3) # gc through v1 & North pole

346#

347# # bearing is (signed) angle between gc1 & gc2

348# return degrees360(gc1.angleTo(gc2, vSign=v1))

349

350 def intermediateTo(self, other, fraction, height=None, wrap=False):

351 '''Return the point at given fraction between this and

352 an other point.

353

354 @arg other: The other point (L{LatLon}).

355 @arg fraction: Fraction between both points (C{scalar},

356 0.0 at this to 1.0 at the other point.

357 @kwarg height: Optional height, overriding the fractional

358 height (C{meter}).

359 @kwarg wrap: If C{True}, wrap or I{normalize} the

360 B{C{other}} point (C{bool}).

361

362 @return: Intermediate point (L{LatLon}).

363

364 @raise TypeError: The B{C{other}} point is not L{LatLon}.

365 '''

366 p = self.others(other)

367 if wrap:

368 p = _Wrap.point(p)

369 f = Scalar(fraction=fraction)

370 h = self._havg(other, f=f, h=height)

371 i = self.toNvector().intermediateTo(p.toNvector(), f)

372 return i.toLatLon(height=h, LatLon=self.classof) # Nvector(i.x, i.y, i.z).toLatLon(...)

373

374 @Property_RO

375 def _rotation3(self):

376 '''(INTERNAL) Get the rotation matrix from n-vector coordinate frame axes.

377 '''

378 nv = self.toNvector() # local (n-vector) coordinate frame

379

380 dv = nv.negate() # down, opposite to n-vector

381 ev = NorthPole.cross(nv, raiser=_pole_).unit() # east, pointing perpendicular to the plane

382 nv = ev.cross(dv) # north, by right hand rule

383 return nv, ev, dv # matrix rows

384

385 def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian, datum=None

386 '''Convert this point to an C{Nvector}-based geodetic point.

387

388 @kwarg Cartesian_and_kwds: Optional L{Cartesian}, B{C{datum}} and other

389 keyword arguments. Use C{B{Cartesian}=...}

390 to override this L{Cartesian} class or specify

391 C{B{Cartesian}=None}.

392

393 @return: The geodetic point (L{Cartesian}) or if B{C{Cartesian}} is set

394 to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,

395 datum)} with C{C} and C{M} if available.

396

397 @raise TypeError: Invalid B{C{Cartesian}} or other B{C{Cartesian_and_kwds}}.

398 '''

399 kwds = _xkwds(Cartesian_and_kwds, Cartesian=Cartesian, datum=self.datum)

400 return LatLonEllipsoidalBase.toCartesian(self, **kwds)

401

402 def toNvector(self, **Nvector_and_kwds): # PYCHOK signature

403 '''Convert this point to C{Nvector} components, I{including height}.

404

405 @kwarg Nvector_and_kwds: Optional C{Nvector}, B{C{datum}} and other

406 keyword arguments. Use C{B{Nvector}=...}

407 to override this C{Nvector} class or specify

408 C{B{Nvector}=None}.

409

410 @return: The C{n-vector} components (C{Nvector}) or if B{C{Nvector}}

411 is set to C{None}, a L{Vector4Tuple}C{(x, y, z, h)}.

412

413 @raise TypeError: Invalid B{C{Nvector}} or other B{C{Nvector_and_kwds}}.

414 '''

415 kwds = _xkwds(Nvector_and_kwds, Nvector=Nvector, datum=self.datum)

416 return LatLonNvectorBase.toNvector(self, **kwds)

417

418

419_Nvll = LatLon(0, 0, name=_Nv00_) # reference instance (L{LatLon})

420

421

422 class Nvector(NvectorBase):

423 '''An n-vector is a position representation using a (unit) vector

424 normal to the earth ellipsoid. Unlike lat-/longitude points,

425 n-vectors have no singularities or discontinuities.

426

427 For many applications, n-vectors are more convenient to work

428 with than other position representations like lat-/longitude,

429 earth-centred earth-fixed (ECEF) vectors, UTM coordinates, etc.

430

431 Note commonality with L{pygeodesy.sphericalNvector.Nvector}.

432 '''

433 _datum = _WGS84 # default datum (L{Datum})

434

435 def __init__(self, x_xyz, y=None, z=None, h=0, datum=None, ll=None, name=NN):

436 '''New n-vector normal to the earth's surface.

437

438 @arg x_xyz: X component of vector (C{scalar}) or (3-D) vector

439 (C{Nvector}, L{Vector3d}, L{Vector3Tuple} or

440 L{Vector4Tuple}).

441 @kwarg y: Y component of vector (C{scalar}), ignored if B{C{x_xyz}}

442 is not C{scalar}, otherwise same units as B{C{x_xyz}}.

443 @kwarg z: Z 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 h: Optional height above model surface (C{meter}).

446 @kwarg datum: Optional datum this n-vector is defined in

447 (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or

448 L{a_f2Tuple}).

449 @kwarg ll: Optional, original latlon (C{LatLon}).

450 @kwarg name: Optional name (C{str}).

451

452 @raise TypeError: If B{C{datum}} is not a L{Datum}.

453 '''

454 NvectorBase.__init__(self, x_xyz, y=y, z=z, h=h, ll=ll, name=name)

455 if datum not in (None, self._datum):

456 self._datum = _ellipsoidal_datum(datum, name=name)

457

458 @Property_RO

459 def datum(self):

460 '''Get this n-vector's datum (L{Datum}).

461 '''

462 return self._datum

463

464 @property_RO

465 def ellipsoidalNvector(self):

466 '''Get this C{Nvector}'s ellipsoidal class.

467 '''

468 return type(self)

469

470 def toCartesian(self, **Cartesian_and_kwds): # PYCHOK Cartesian=Cartesian

471 '''Convert this n-vector to C{Nvector}-based cartesian (ECEF) coordinates.

472

473 @kwarg Cartesian_and_kwds: Optional L{Cartesian}, B{C{h}}, B{C{datum}} and

474 other keyword arguments. Use C{B{Cartesian}=...}

475 to override this L{Cartesian} class or specify

476 C{B{Cartesian} is None}.

477

478 @return: The cartesian point (L{Cartesian}) or if B{C{Cartesian}} is set

479 to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M,

480 datum)} with C{C} and C{M} if available.

481

482 @raise TypeError: Invalid B{C{Cartesian_and_kwds}}.

483 '''

484 kwds = _xkwds(Cartesian_and_kwds, h=self.h, Cartesian=Cartesian,

485 datum=self.datum)

486 return NvectorBase.toCartesian(self, **kwds) # class or .classof

487

488 def toLatLon(self, **LatLon_and_kwds): # PYCHOK height=None, LatLon=LatLon

489 '''Convert this n-vector to an C{Nvector}-based geodetic point.

490

491 @kwarg LatLon_and_kwds: Optional L{LatLon}, B{C{height}}, B{C{datum}}

492 and other keyword arguments. Use C{B{LatLon}=...}

493 to override this L{LatLon} class or specify

494 C{B{LatLon} is None}.

495

496 @return: The geodetic point (L{LatLon}) or if B{C{LatLon}} is set

497 to C{None}, an L{Ecef9Tuple}C{(x, y, z, lat, lon, height,

498 C, M, datum)} with C{C} and C{M} if available.

499

500 @raise TypeError: Invalid B{C{LatLon_and_kwds}}.

501 '''

502 kwds = _xkwds(LatLon_and_kwds, height=self.h, datum=self.datum, LatLon=LatLon)

503 return NvectorBase.toLatLon(self, **kwds) # class or .classof

504

505 def unit(self, ll=None):

506 '''Normalize this vector to unit length.

507

508 @kwarg ll: Optional, original latlon (C{LatLon}).

509

510 @return: Normalised vector (C{Nvector}).

511 '''

512 u = NvectorBase.unit(self, ll=ll)

513 if u.datum != self.datum:

514 u._update(False, datum=self.datum)

515 return u

516

517

518 def _Ecef():

519 # return the Ecef class and overwrite property_RO

520 Cartesian.Ecef = LatLon.Ecef = E = _MODS.ecef.EcefVeness

521 return E

522

523

524 def meanOf(points, datum=_WGS84, height=None, wrap=False,

525 **LatLon_and_kwds):

526 '''Compute the geographic mean of several points.

527

528 @arg points: Points to be averaged (L{LatLon}[]).

529 @kwarg datum: Optional datum to use (L{Datum}).

530 @kwarg height: Optional height at mean point, overriding

531 the mean height (C{meter}).

532 @kwarg wrap: If C{True}, wrap or I{normalize} B{C{points}}

533 (C{bool}).

534 @kwarg LatLon_and_kwds: Optional B{C{LatLon}} class to return

535 the mean points and overriding this L{LatLon}

536 (or C{None}) and additional B{C{LatLon}}

537 keyword arguments, ignored if C{B{LatLon}

538 is None}.

539

540 @return: Geographic mean point and mean height (B{C{LatLon}})

541 or if B{C{LatLon}} is C{None}, an L{Ecef9Tuple}C{(x,

542 y, z, lat, lon, height, C, M, datum)} with C{C} and

543 C{M} if available.

544

545 @raise ValueError: Insufficient number of B{C{points}}.

546 '''

547 Ps = _Nvll.PointsIter(points, wrap=wrap)

548 # geographic mean

549 m = sumOf(p._N_vector for p in Ps.iterate(closed=False))

550 kwds = _xkwds(LatLon_and_kwds, height=height, datum=datum,

551 LatLon=LatLon, name=meanOf.__name__)

552 return m.toLatLon(**kwds)

553

554

555 def nearestOn(point, point1, point2, within=True, height=None, wrap=False,

556 equidistant=None, tol=_TOL_M, LatLon=LatLon, **LatLon_kwds):

557 '''I{Iteratively} locate the closest point on the geodesic between

558 two other (ellipsoidal) points.

559

560 @arg point: Reference point (C{LatLon}).

561 @arg point1: Start point of the geodesic (C{LatLon}).

562 @arg point2: End point of the geodesic (C{LatLon}).

563 @kwarg within: If C{True} return the closest point I{between}

564 B{C{point1}} and B{C{point2}}, otherwise the

565 closest point elsewhere on the geodesic (C{bool}).

566 @kwarg height: Optional height for the closest point (C{meter},

567 conventionally) or C{None} or C{False} for the

568 interpolated height. If C{False}, the closest

569 takes the heights of the points into account.

570 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll I{only}

571 B{C{point1}} and B{C{point2}} (C{bool}).

572 @kwarg equidistant: An azimuthal equidistant projection (I{class}

573 or function L{pygeodesy.equidistant}) or C{None}

574 for the preferred C{B{point}.Equidistant}.

575 @kwarg tol: Convergence tolerance (C{meter}).

576 @kwarg LatLon: Optional class to return the closest point

577 (L{LatLon}) or C{None}.

578 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword

579 arguments, ignored if C{B{LatLon} is None}.

580

581 @return: Closest point, a B{C{LatLon}} instance or if C{B{LatLon}

582 is None}, a L{LatLon4Tuple}C{(lat, lon, height, datum)}.

583

584 @raise ImportError: Package U{geographiclib

585 <https://PyPI.org/project/geographiclib>}

586 not installed or not found.

587

588 @raise TypeError: Invalid or non-ellipsoidal B{C{point}}, B{C{point1}}

589 or B{C{point2}} or invalid B{C{equidistant}}.

590

591 @raise ValueError: No convergence for the B{C{tol}}.

592

593 @see: U{The B{ellipsoidal} case<https://GIS.StackExchange.com/questions/48937/

594 calculating-intersection-of-two-circles>} and U{Karney's paper

595 <https://ArXiv.org/pdf/1102.1215.pdf>}, pp 20-21, section B{14. MARITIME

596 BOUNDARIES} for more details about the iteration algorithm.

597 '''

598 return _nearestOn(point, point1, point2, within=within, height=height, wrap=wrap,

599 equidistant=equidistant, tol=tol, LatLon=LatLon, **LatLon_kwds)

600

601

602 def sumOf(nvectors, Vector=Nvector, h=None, **Vector_kwds):

603 '''Return the vectorial sum of two or more n-vectors.

604

605 @arg nvectors: Vectors to be added (C{Nvector}[]).

606 @kwarg Vector: Optional class for the vectorial sum (C{Nvector}).

607 @kwarg h: Optional height, overriding the mean height (C{meter}).

608 @kwarg Vector_kwds: Optional, additional B{C{Vector}} keyword

609 arguments, ignored if C{B{Vector} is None}.

610

611 @return: Vectorial sum (B{C{Vector}}).

612

613 @raise VectorError: No B{C{nvectors}}.

614 '''

615 return _sumOf(nvectors, Vector=Vector, h=h, **Vector_kwds)

616

617

618@deprecated_function

619 def toNed(distance, bearing, elevation, Ned=Ned, name=NN):

620 '''DEPRECATED, use L{pygeodesy.Aer}C{(bearing, elevation,

621 distance).xyzLocal.toNed(B{Ned}, name=B{name})} or

622 L{XyzLocal}C{(pygeodesy.Aer(bearing, elevation,

623 distance)).toNed(B{Ned}, name=B{name})}.

624

625 Create an NED vector from distance, bearing and elevation

626 (in local coordinate system).

627

628 @arg distance: NED vector length (C{meter}).

629 @arg bearing: NED vector bearing (compass C{degrees360}).

630 @arg elevation: NED vector elevation from local coordinate

631 frame horizontal (C{degrees}).

632 @kwarg Ned: Optional class to return the NED (C{Ned}) or

633 C{None}.

634 @kwarg name: Optional name (C{str}).

635

636 @return: An NED vector equivalent to this B{C{distance}},

637 B{C{bearing}} and B{C{elevation}} (DEPRECATED L{Ned})

638 or a DEPRECATED L{Ned3Tuple}C{(north, east, down)}

639 if C{B{Ned} is None}.

640

641 @raise ValueError: Invalid B{C{distance}}, B{C{bearing}}

642 or B{C{elevation}}.

643 '''

644 if True: # use new Aer class

645 n, e, d, _ = _Aer(bearing, elevation, distance).xyz4

646 else: # DEPRECATED

647 d = Distance(distance)

648

649 sb, cb, se, ce = sincos2d_(Bearing(bearing),

650 Height(elevation=elevation))

651 n = cb * d * ce

652 e = sb * d * ce

653 d *= se

654

655 r = _MODS.deprecated.classes.Ned3Tuple(n, e, -d) if Ned is None else \

656 Ned(n, e, -d)

657 return _xnamed(r, name)

658

659

660__all__ += _ALL_OTHER(Cartesian, LatLon, Ned, Nvector, # classes

661 meanOf, sumOf, toNed)

662

663# **) MIT License

664#

665# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.

666#

667# Permission is hereby granted, free of charge, to any person obtaining a

668# copy of this software and associated documentation files (the "Software"),

669# to deal in the Software without restriction, including without limitation

670# the rights to use, copy, modify, merge, publish, distribute, sublicense,

671# and/or sell copies of the Software, and to permit persons to whom the

672# Software is furnished to do so, subject to the following conditions:

673#

674# The above copyright notice and this permission notice shall be included

675# in all copies or substantial portions of the Software.

676#

677# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS

678# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

679# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL

680# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR

681# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,

682# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR

683# OTHER DEALINGS IN THE SOFTWARE.