Coverage for pygeodesy/cartesianBase.py: 92%

1

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

3

4 u'''(INTERNAL) Private C{CartesianBase} class for elliposiodal, spherical and N-/vectorial

5 C{Cartesian}s and public functions L{rtp2xyz}, L{rtp2xyz_}, L{xyz2rtp} and L{xyz2rtp_}.

6

7 After I{(C) Chris Veness 2011-2024} published under the same MIT Licence**, see

8 U{https://www.Movable-Type.co.UK/scripts/latlong.html},

9 U{https://www.Movable-Type.co.UK/scripts/latlong-vectors.html} and

10 U{https://www.Movable-Type.co.UK/scripts/geodesy/docs/latlon-ellipsoidal.js.html}.

11'''

12

13 from pygeodesy.basics import _isin, _xinstanceof, typename

14 from pygeodesy.constants import EPS, EPS0, INT0, NAN, PI2, _isfinite, \

15 _0_0, _1_0, _N_1_0, _2_0, _4_0, _6_0, isnear0

16 from pygeodesy.datums import Datum, _earth_ellipsoid, _spherical_datum, \

17 Transform, _WGS84

18# from pygeodesy.ecef import EcefKarney # _MODS

19 from pygeodesy.ecefLocals import _EcefLocal

20 from pygeodesy.errors import _IsnotError, _TypeError, _ValueError, _xattr, \

21 _xdatum, _xkwds, _xkwds_get, _xkwds_pop2

22 from pygeodesy.fmath import cbrt, hypot, hypot_, hypot2, fabs, sqrt # hypot

23# from pygeodesy.formy import _hartzell # _MODS

24 from pygeodesy.fsums import fsumf_, Fmt

25# from pygeodesy.internals import typename # from .basics

26 from pygeodesy.interns import _COMMASPACE_, _datum_, _no_, _phi_

27 from pygeodesy.interns import _ellipsoidal_, _spherical_ # PYCHOK used!

28 from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS

29 from pygeodesy.named import _name2__, _Pass

30 from pygeodesy.namedTuples import LatLon4Tuple, _NamedTupleTo, Vector3Tuple, \

31 Vector4Tuple

32# from pygeodesy.nvectorBase import _N_Vector # _MODS

33 from pygeodesy.props import deprecated_method, Property, Property_RO, \

34 property_doc_, property_RO, _update_all

35# from pygeodesy import resections as _resections # _MODS.into

36# from pygeodesy.streprs import Fmt # from .fsums

37# from pygeodesy.triaxials.triaxial5 import Triaxial_ # _MODS

38 from pygeodesy.units import Degrees, Height, _heigHt, _isMeter, Meter, Radians

39 from pygeodesy.utily import acos1, atan2, sincos2d, sincos2_, degrees, radians

40 from pygeodesy.vector3d import Vector3d, _xyzhdlln4

41# from pygeodesy.vector3dBase import _xyz3 # _MODS

42

43# from math import degrees, fabs, radians, sqrt # from .fmath, .utily

44

45 __all__ = _ALL_LAZY.cartesianBase

46 __version__ = '26.01.06'

47

48 _r_ = 'r'

49 _resections = _MODS.into(resections=__name__)

50 _theta_ = 'theta'

51

52

53 class CartesianBase(Vector3d, _EcefLocal):

54 '''(INTERNAL) Base class for ellipsoidal and spherical C{Cartesian}.

55 '''

56 _datum = None # L{Datum}, to be overriden

57 _height = None # height (L{Height}), set or approximated

58

59 def __init__(self, x_xyz, y=None, z=None, datum=None, **ll_name):

60 '''New C{Cartesian...}.

61

62 @arg x_xyz: Cartesian X coordinate (C{scalar}) or a C{Cartesian},

63 L{Ecef9Tuple}, L{Vector3Tuple} or L{Vector4Tuple}.

64 @kwarg y: Cartesian Y coordinate (C{scalar}), ignored if B{C{x_xyz}}

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

66 @kwarg z: Cartesian Z coordinate (C{scalar}), like B{C{y}}.

67 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}

68 or L{a_f2Tuple}).

69 @kwarg ll_name: Optional C{B{name}=NN} (C{str}) and optional, original

70 latlon C{B{ll}=None} (C{LatLon}).

71

72 @raise TypeError: Non-scalar B{C{x_xyz}}, B{C{y}} or B{C{z}} coordinate

73 or B{C{x_xyz}} not a C{Cartesian}, L{Ecef9Tuple},

74 L{Vector3Tuple} or L{Vector4Tuple} or B{C{datum}} is

75 not a L{Datum}.

76 '''

77 h, d, ll, n = _xyzhdlln4(x_xyz, None, datum, **ll_name)

78 Vector3d.__init__(self, x_xyz, y=y, z=z, ll=ll, name=n)

79 if h is not None:

80 self._height = Height(h)

81 if d is not None:

82 self.datum = d

83

84# def __matmul__(self, other): # PYCHOK Python 3.5+

85# '''Return C{NotImplemented} for C{c_ = c @ datum} and C{c_ = c @ transform}.

86# '''

87# return NotImplemented if isinstance(other, (Datum, Transform)) else \

88# _NotImplemented(self, other)

89

90 def cassini(self, pointB, pointC, alpha, beta, useZ=False):

91 '''3-Point resection between this and 2 other points using U{Cassini

92 <https://NL.WikiPedia.org/wiki/Achterwaartse_insnijding>}'s method.

93

94 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

95 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

96 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

97 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

98 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to

99 B{C{pointC}} (C{degrees}, non-negative).

100 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to

101 B{C{pointC}} (C{degrees}, non-negative).

102 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

103 force C{z=INT0} (C{bool}).

104

105 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.

106

107 @return: The survey point, an instance of this (sub-)class.

108

109 @raise ResectionError: Near-coincident, -colinear or -concyclic points

110 or negative or invalid B{C{alpha}} or B{C{beta}}.

111

112 @raise TypeError: Invalid B{C{pointA}}, B{C{pointB}} or B{C{pointM}}.

113

114 @see: Function L{pygeodesy.cassini} for references and more details.

115 '''

116 return _resections.cassini(self, pointB, pointC, alpha, beta,

117 useZ=useZ, datum=self.datum)

118

119 @deprecated_method

120 def collins(self, pointB, pointC, alpha, beta, useZ=False):

121 '''DEPRECATED, use method L{collins5}.'''

122 return self.collins5(pointB, pointC, alpha, beta, useZ=useZ)

123

124 def collins5(self, pointB, pointC, alpha, beta, useZ=False):

125 '''3-Point resection between this and 2 other points using U{Collins<https://Dokumen.tips/

126 documents/three-point-resection-problem-introduction-kaestner-burkhardt-method.html>}' method.

127

128 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

129 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

130 @arg pointC: Center point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

131 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

132 @arg alpha: Angle subtended by triangle side C{b} from B{C{pointA}} to

133 B{C{pointC}} (C{degrees}, non-negative).

134 @arg beta: Angle subtended by triangle side C{a} from B{C{pointB}} to

135 B{C{pointC}} (C{degrees}, non-negative).

136 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise

137 force C{z=INT0} (C{bool}).

138

139 @note: Typically, B{C{pointC}} is between this and B{C{pointB}}.

140

141 @return: L{Collins5Tuple}C{(pointP, pointH, a, b, c)} with survey C{pointP},

142 auxiliary C{pointH}, each an instance of this (sub-)class and

143 triangle sides C{a}, C{b} and C{c}.

144

145 @raise ResectionError: Near-coincident, -colinear or -concyclic points

146 or negative or invalid B{C{alpha}} or B{C{beta}}.

147

148 @raise TypeError: Invalid B{C{pointB}} or B{C{pointM}}.

149

150 @see: Function L{pygeodesy.collins5} for references and more details.

151 '''

152 return _resections.collins5(self, pointB, pointC, alpha, beta,

153 useZ=useZ, datum=self.datum)

154

155 @deprecated_method

156 def convertDatum(self, datum2, **datum):

157 '''DEPRECATED, use method L{toDatum}.'''

158 return self.toDatum(datum2, **datum)

159

160 @property_doc_(''' this cartesian's datum (L{Datum}).''')

161 def datum(self):

162 '''Get this cartesian's datum (L{Datum}).

163 '''

164 return self._datum

165

166 @datum.setter # PYCHOK setter!

167 def datum(self, datum):

168 '''Set this cartesian's C{datum} I{without conversion}

169 (L{Datum}), ellipsoidal or spherical.

170

171 @raise TypeError: The B{C{datum}} is not a L{Datum}.

172 '''

173 d = _spherical_datum(datum, name=self.name)

174 D = self._datum

175 if D: # is not None

176 if d.isEllipsoidal and not D.isEllipsoidal:

177 raise _IsnotError(_ellipsoidal_, datum=datum)

178 elif d.isSpherical and not D.isSpherical:

179 raise _IsnotError(_spherical_, datum=datum)

180 if D != d: # or (D and D.name != d.name)

181 _update_all(self)

182 self._datum = d

183

184 def destinationXyz(self, delta, Cartesian=None, **name_Cartesian_kwds):

185 '''Calculate the destination using a I{local} delta from this cartesian.

186

187 @arg delta: Local delta to the destination (L{XyzLocal}, L{Enu}, L{Ned}

188 or L{Local9Tuple}).

189 @kwarg Cartesian: Optional (geocentric) class to return the destination

190 or C{None}.

191 @kwarg name_Cartesian_kwds: Optional C{B{name}=NN} (C{str}) and optionally,

192 additional B{C{Cartesian}} keyword arguments, ignored if

193 C{B{Cartesian} is None}.

194

195 @return: Destination as a C{B{Cartesian}(x, y, z, **B{Cartesian_kwds})}

196 instance or if C{B{Cartesian} is None}, an L{Ecef9Tuple}C{(x, y,

197 z, lat, lon, height, C, M, datum)} with C{M=None} always.

198

199 @raise TypeError: Invalid B{C{delta}}, B{C{Cartesian}} or B{C{Cartesian_kwds}}

200 item or C{datum} missing or incompatible.

201 '''

202 n, kwds = _name2__(name_Cartesian_kwds, _or_nameof=self)

203 if Cartesian is None:

204 r = self._ltp._local2ecef(delta, nine=True) # _EcefLocal._ltp

205 else:

206 d = self.datum

207 if not d:

208 raise _TypeError(delta=delta, txt=_no_(_datum_))

209 t = _xkwds_get(kwds, datum=d)

210 if _xattr(t, ellipsoid=None) != d.ellipsoid:

211 raise _TypeError(datum=t, txt=str(d))

212 c = self._ltp._local2ecef(delta, nine=False) # _EcefLocal._ltp

213 r = Cartesian(*c, **kwds)

214 return r.renamed(n) if n else r

215

216 @Property_RO

217 def _ecef9(self):

218 '''(INTERNAL) Helper for L{toEcef}, L{toLocal} and L{toLtp} (L{Ecef9Tuple}).

219 '''

220 return self.Ecef(self.datum, name=self.name).reverse(self, M=True)

221

222 @property_RO

223 def ellipsoidalCartesian(self):

224 '''Get the C{Cartesian type} iff ellipsoidal, overloaded in L{CartesianEllipsoidalBase}.

225 '''

226 return False

227

228 def hartzell(self, los=False, earth=None):

229 '''Compute the intersection of a Line-Of-Sight from this cartesian Point-Of-View

230 (pov) and this cartesian's C{datum} ellipsoid surface.

231

232 @kwarg los: Line-Of-Sight, I{direction} to the ellipsoid (L{Los}, L{Vector3d}),

233 C{True} for the I{normal, plumb} onto the surface or I{False} or

234 C{None} to point to the center of the ellipsoid.

235 @kwarg earth: The earth model (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}, L{a_f2Tuple}

236 or C{scalar} radius in C{meter}), overriding this cartesian's

237 datum.

238

239 @return: The intersection (C{Cartesian}) with C{.height} set to the distance to

240 this C{pov}.

241

242 @raise IntersectionError: Null or bad C{pov} or B{C{los}}, this C{pov} is inside

243 the ellipsoid or B{C{los}} points outside or away from

244 the ellipsoid.

245

246 @raise TypeError: Invalid B{C{los}} or invalid or undefined B{C{earth}} or C{datum}.

247

248 @see: Function L{hartzell<pygeodesy.formy.hartzell>} for further details.

249 '''

250 return _MODS.formy._hartzell(self, los, earth)

251

252 @Property

253 def height(self):

254 '''Get the height (C{meter}).

255 '''

256 return self._height4.h if self._height is None else self._height

257

258 @height.setter # PYCHOK setter!

259 def height(self, height):

260 '''Set the height (C{meter}).

261

262 @raise TypeError: Invalid B{C{height}} C{type}.

263

264 @raise ValueError: Invalid B{C{height}}.

265 '''

266 h = Height(height)

267 if self._height != h:

268 _update_all(self)

269 self._height = h

270

271 def _height2C(self, r, Cartesian=None, datum=None, height=INT0, **kwds):

272 '''(INTERNAL) Helper for methods C{.height3} and C{.height4}.

273 '''

274 if Cartesian is not None:

275 r = Cartesian(r, **kwds)

276 if datum is not None:

277 r.datum = datum

278 if height is not None:

279 r.height = height # Height(height)

280 return r

281

282 def height3(self, earth=None, height=None, **Cartesian_and_kwds):

283 '''Compute the cartesian at a height above or below this certesian's

284 C{datum} ellipsoid surface.

285

286 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,

287 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},

288 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).

289 @kwarg height: The height (C{meter}, conventionally), overriding this

290 cartesian's height.

291 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return

292 the cartesian I{at height} and additional B{C{Cartesian}}

293 keyword arguments.

294

295 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None},

296 a L{Vector3Tuple}C{(x, y, z)} with the C{x}, C{y} and C{z}

297 coordinates I{at height} in C{meter}, conventionally.

298

299 @note: This cartesian's coordinates are returned if B{C{earth}} and this

300 datum or B{C{height}} and/or this height are C{None} or undefined.

301

302 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}

303 does not accept a B{C{datum}} keyword agument.

304

305 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.

306 '''

307 n = typename(self.height3)

308 d = self.datum if earth is None else _spherical_datum(earth, name=n)

309 c, h = self, _heigHt(self, height)

310 if h and d:

311 R, r = self.Roc2(earth=d)

312 if R > EPS0:

313 R = (R + h) / R

314 r = ((r + h) / r) if r > EPS0 else _1_0

315 c = c.times_(R, R, r)

316

317 r = Vector3Tuple(c.x, c.y, c.z, name=n)

318 if Cartesian_and_kwds:

319 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))

320 return r

321

322 @Property_RO

323 def _height4(self):

324 '''(INTERNAL) Get this C{height4}-tuple.

325 '''

326 try:

327 r = self.datum.ellipsoid.height4(self, normal=True)

328 except (AttributeError, ValueError): # no datum, null cartesian,

329 r = Vector4Tuple(self.x, self.y, self.z, NAN, name__=self.height4)

330 return r

331

332 def height4(self, earth=None, normal=True, **Cartesian_and_kwds):

333 '''Compute the projection of this point on and the height above or below

334 this datum's ellipsoid surface.

335

336 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,

337 I{overriding} this datum (L{Datum}, L{Ellipsoid},

338 L{Ellipsoid2}, L{a_f2Tuple}, L{Triaxial}, L{Triaxial_},

339 L{JacobiConformal} or C{meter}, conventionally).

340 @kwarg normal: If C{True}, the projection is the nearest point on the

341 ellipsoid's surface, otherwise the intersection of the

342 radial line to the ellipsoid's center and surface C{bool}).

343 @kwarg Cartesian_and_kwds: Optional C{B{Cartesian}=None} class to return

344 the I{projection} and additional B{C{Cartesian}} keyword

345 arguments.

346

347 @return: An instance of B{C{Cartesian}} or if C{B{Cartesian} is None}, a

348 L{Vector4Tuple}C{(x, y, z, h)} with the I{projection} C{x}, C{y}

349 and C{z} coordinates and height C{h} in C{meter}, conventionally.

350

351 @note: Include keyword argument C{B{datum}=None} if class B{C{Cartesian}}

352 does not accept a B{C{datum}} keyword agument.

353

354 @raise TriaxialError: No convergence in triaxial root finding.

355

356 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.

357

358 @see: Methods L{Ellipsoid.height4} and L{Triaxial_.height4} for more information.

359 '''

360 n = typename(self.height4)

361 d = self.datum if earth is None else earth

362 if normal and d is self.datum:

363 r = self._height4

364 elif isinstance(d, _MODS.triaxials.Triaxial_):

365 r = d.height4(self, normal=normal)

366 try:

367 d = d.toEllipsoid(name=n)

368 except (TypeError, ValueError): # TriaxialError

369 d = None

370 else:

371 r = _earth_ellipsoid(d).height4(self, normal=normal)

372

373 if Cartesian_and_kwds:

374 if d and not isinstance(d, Datum):

375 d = _spherical_datum(d, name=n)

376 r = self._height2C(r, **_xkwds(Cartesian_and_kwds, datum=d))

377 return r

378

379 @Property_RO

380 def isEllipsoidal(self):

381 '''Check whether this cartesian is ellipsoidal (C{bool} or C{None} if unknown).

382 '''

383 return _xattr(self.datum, isEllipsoidal=None)

384

385 @Property_RO

386 def isSpherical(self):

387 '''Check whether this cartesian is spherical (C{bool} or C{None} if unknown).

388 '''

389 return _xattr(self.datum, isSpherical=None)

390

391 @Property_RO

392 def latlon(self):

393 '''Get this cartesian's (geodetic) lat- and longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}).

394 '''

395 return self.toEcef().latlon

396

397 @Property_RO

398 def latlonheight(self):

399 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height (L{LatLon3Tuple}C{(lat, lon, height)}).

400 '''

401 return self.toEcef().latlonheight

402

403 @Property_RO

404 def latlonheightdatum(self):

405 '''Get this cartesian's (geodetic) lat-, longitude in C{degrees} with height and datum (L{LatLon4Tuple}C{(lat, lon, height, datum)}).

406 '''

407 return self.toEcef().latlonheightdatum

408

409 @Property_RO

410 def _N_vector(self):

411 '''(INTERNAL) Get the (C{nvectorBase._N_Vector}).

412 '''

413 _N = _MODS.nvectorBase._N_Vector

414 return self._n_xyzh4(self.datum, Vector=_N)

415

416 def _n_xyzh4(self, datum, Vector=Vector4Tuple):

417 '''(INTERNAL) Get the n-vector components as L{Vector4Tuple}.

418 '''

419 def _ErrorEPS0(x):

420 return _ValueError(origin=self, txt=Fmt.PARENSPACED(EPS0=x))

421

422 _xinstanceof(Datum, datum=datum)

423 # <https://www.Movable-Type.co.UK/scripts/geodesy/docs/

424 # latlon-nvector-ellipsoidal.js.html#line309>,

425 # <https://GitHub.com/pbrod/nvector>/src/nvector/core.py>

426 # _equation23 and <https://www.NavLab.net/nvector>

427 E = datum.ellipsoid

428 x, y, z = self.xyz3

429

430 # Kenneth Gade eqn 23

431 p = hypot2(x, y) * E.a2_

432 q = z**2 * E.e21 * E.a2_

433 r = fsumf_(p, q, -E.e4) / _6_0

434 if isnear0(r):

435 raise _ErrorEPS0(r)

436 s = (p * q * E.e4) / (_4_0 * r**3)

437 if s < 0:

438 raise _ErrorEPS0(s)

439 t = cbrt(fsumf_(_1_0, s, sqrt(s * (_2_0 + s))))

440 if isnear0(t):

441 raise _ErrorEPS0(t)

442 u = fsumf_(_1_0, t, _1_0 / t) * r

443 v = sqrt(u**2 + E.e4 * q)

444 t = v * _2_0

445 if t < EPS0: # isnear0

446 raise _ErrorEPS0(t)

447 w = fsumf_(u, v, -q) * E.e2 / t

448 k = sqrt(fsumf_(u, v, w**2)) - w

449 if isnear0(k):

450 raise _ErrorEPS0(k)

451 t = k + E.e2

452 if isnear0(t):

453 raise _ErrorEPS0(t)

454 e = k / t

455# d = e * hypot(x, y)

456# tmp = 1 / hypot(d, z) == 1 / hypot(e * hypot(x, y), z)

457 t = hypot_(x * e, y * e, z) # == 1 / tmp

458 if t < EPS0: # isnear0

459 raise _ErrorEPS0(t)

460 h = fsumf_(k, E.e2, _N_1_0) / k * t

461 s = e / t # == e * tmp

462 return Vector(x * s, y * s, z / t, h, name=self.name)

463

464 @Property_RO

465 def philam(self):

466 '''Get this cartesian's (geodetic) lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}).

467 '''

468 return self.toEcef().philam

469

470 @Property_RO

471 def philamheight(self):

472 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height (L{PhiLam3Tuple}C{(phi, lam, height)}).

473 '''

474 return self.toEcef().philamheight

475

476 @Property_RO

477 def philamheightdatum(self):

478 '''Get this cartesian's (geodetic) lat-, longitude in C{radians} with height and datum (L{PhiLam4Tuple}C{(phi, lam, height, datum)}).

479 '''

480 return self.toEcef().philamheightdatum

481

482 def pierlot(self, point2, point3, alpha12, alpha23, useZ=False, eps=EPS):

483 '''3-Point resection between this and two other points using U{Pierlot

484 <http://www.Telecom.ULg.ac.Be/triangulation>}'s method C{ToTal} with

485 I{approximate} limits for the (pseudo-)singularities.

486

487 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

488 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

489 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

490 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

491 @arg alpha12: Angle subtended from this point to B{C{point2}} or

492 B{C{alpha2 - alpha}} (C{degrees}).

493 @arg alpha23: Angle subtended from B{C{point2}} to B{C{point3}} or

494 B{C{alpha3 - alpha2}} (C{degrees}).

495 @kwarg useZ: If C{True}, interpolate the Z component, otherwise use C{z=INT0}

496 (C{bool}).

497 @kwarg eps: Tolerance for C{cot} (pseudo-)singularities (C{float}).

498

499 @note: This point, B{C{point2}} and B{C{point3}} are ordered counter-clockwise.

500

501 @return: The survey (or robot) point, an instance of this (sub-)class.

502

503 @raise ResectionError: Near-coincident, -colinear or -concyclic points

504 or invalid B{C{alpha12}} or B{C{alpha23}}.

505

506 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.

507

508 @see: Function L{pygeodesy.pierlot} for references and more details.

509 '''

510 return _resections.pierlot(self, point2, point3, alpha12, alpha23,

511 useZ=useZ, eps=eps, datum=self.datum)

512

513 def pierlotx(self, point2, point3, alpha1, alpha2, alpha3, useZ=False):

514 '''3-Point resection between this and two other points using U{Pierlot

515 <http://www.Telecom.ULg.ac.Be/publi/publications/pierlot/Pierlot2014ANewThree>}'s

516 method C{ToTal} with I{exact} limits for the (pseudo-)singularities.

517

518 @arg point2: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

519 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

520 @arg point3: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple},

521 C{Vector4Tuple} or C{Vector2Tuple} if C{B{useZ}=False}).

522 @arg alpha1: Angle at B{C{point1}} (C{degrees}).

523 @arg alpha2: Angle at B{C{point2}} (C{degrees}).

524 @arg alpha3: Angle at B{C{point3}} (C{degrees}).

525 @kwarg useZ: If C{True}, interpolate the survey point's Z component,

526 otherwise use C{z=INT0} (C{bool}).

527

528 @return: The survey (or robot) point, an instance of this (sub-)class.

529

530 @raise ResectionError: Near-coincident, -colinear or -concyclic points or

531 invalid B{C{alpha1}}, B{C{alpha2}} or B{C{alpha3}}.

532

533 @raise TypeError: Invalid B{C{point2}} or B{C{point3}}.

534

535 @see: Function L{pygeodesy.pierlotx} for references and more details.

536 '''

537 return _resections.pierlotx(self, point2, point3, alpha1, alpha2, alpha3,

538 useZ=useZ, datum=self.datum)

539

540 def Roc2(self, earth=None):

541 '''Compute this cartesian's I{normal} and I{pseudo, z-based} radius of curvature.

542

543 @kwarg earth: A datum, ellipsoid, triaxial ellipsoid or earth radius,

544 I{overriding} this cartesian's datum (L{Datum}, L{Ellipsoid},

545 L{Ellipsoid2}, L{a_f2Tuple} or C{meter}, conventionally).

546

547 @return: 2-Tuple C{(R, r)} with the I{normal} and I{pseudo, z-based} radius of

548 curvature C{R} respectively C{r}, both in C{meter} conventionally.

549

550 @raise TypeError: Invalid or undefined B{C{earth}} or C{datum}.

551 '''

552 r = z = fabs( self.z)

553 R, _0 = hypot(self.x, self.y), EPS0

554 if R < _0: # polar

555 R = z

556 elif z > _0: # non-equatorial

557 d = self.datum if earth is None else _spherical_datum(earth)

558 e = self.toLatLon(datum=d, height=0, LatLon=None) # Ecef9Tuple

559 M = e.M # EcefMatrix

560 sa, ca = map(fabs, (M._2_2_, M._2_1_) if M else sincos2d(e.lat))

561 if ca < _0: # polar

562 R = z

563 else: # prime-vertical, normal roc R

564 R = R / ca # /= chokes PyChecker

565 r = R if sa < _0 else (r / sa) # non-/equatorial

566 return R, r

567

568 @property_RO

569 def sphericalCartesian(self):

570 '''Get the C{Cartesian type} iff spherical, overloaded in L{CartesianSphericalBase}.

571 '''

572 return False

573

574 @deprecated_method

575 def tienstra(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):

576 '''DEPRECATED, use method L{tienstra7}.'''

577 return self.tienstra7(pointB, pointC, alpha, beta=beta, gamma=gamma, useZ=useZ)

578

579 def tienstra7(self, pointB, pointC, alpha, beta=None, gamma=None, useZ=False):

580 '''3-Point resection between this and two other points using U{Tienstra

581 <https://WikiPedia.org/wiki/Tienstra_formula>}'s formula.

582

583 @arg pointB: Second point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

584 C{Vector2Tuple} if C{B{useZ}=False}).

585 @arg pointC: Third point (C{Cartesian}, L{Vector3d}, C{Vector3Tuple}, C{Vector4Tuple} or

586 C{Vector2Tuple} if C{B{useZ}=False}).

587 @arg alpha: Angle subtended by triangle side C{a} from B{C{pointB}} to B{C{pointC}} (C{degrees},

588 non-negative).

589 @kwarg beta: Angle subtended by triangle side C{b} from this to B{C{pointC}} (C{degrees},

590 non-negative) or C{None} if C{B{gamma} is not None}.

591 @kwarg gamma: Angle subtended by triangle side C{c} from this to B{C{pointB}} (C{degrees},

592 non-negative) or C{None} if C{B{beta} is not None}.

593 @kwarg useZ: If C{True}, use and interpolate the Z component, otherwise force C{z=INT0}

594 (C{bool}).

595

596 @note: This point, B{C{pointB}} and B{C{pointC}} are ordered clockwise.

597

598 @return: L{Tienstra7Tuple}C{(pointP, A, B, C, a, b, c)} with survey C{pointP},

599 an instance of this (sub-)class and triangle angle C{A} at this point,

600 C{B} at B{C{pointB}} and C{C} at B{C{pointC}} in C{degrees} and

601 triangle sides C{a}, C{b} and C{c}.

602

603 @raise ResectionError: Near-coincident, -colinear or -concyclic points or sum of

604 B{C{alpha}}, B{C{beta}} and B{C{gamma}} not C{360} or

605 negative B{C{alpha}}, B{C{beta}} or B{C{gamma}}.

606

607 @raise TypeError: Invalid B{C{pointB}} or B{C{pointC}}.

608

609 @see: Function L{pygeodesy.tienstra7} for references and more details.

610 '''

611 return _resections.tienstra7(self, pointB, pointC, alpha, beta, gamma,

612 useZ=useZ, datum=self.datum)

613

614 @deprecated_method

615 def to2ab(self): # PYCHOK no cover

616 '''DEPRECATED, use property C{philam}.

617

618 @return: A L{PhiLam2Tuple}C{(phi, lam)}.

619 '''

620 return self.philam

621

622 @deprecated_method

623 def to2ll(self): # PYCHOK no cover

624 '''DEPRECATED, use property C{latlon}.

625

626 @return: A L{LatLon2Tuple}C{(lat, lon)}.

627 '''

628 return self.latlon

629

630 @deprecated_method

631 def to3llh(self, datum=None): # PYCHOK no cover

632 '''DEPRECATED, use property L{latlonheight} or L{latlonheightdatum}.

633

634 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.

635

636 @note: This method returns a B{C{-4Tuple}} I{and not a} C{-3Tuple}

637 as its name may suggest.

638 '''

639 t = self.toLatLon(datum=datum, LatLon=None)

640 return LatLon4Tuple(t.lat, t.lon, t.height, t.datum, name=self.name)

641

642# def _to3LLh(self, datum, LL, **pairs): # OBSOLETE

643# '''(INTERNAL) Helper for C{subclass.toLatLon} and C{.to3llh}.

644# '''

645# r = self.to3llh(datum) # LatLon3Tuple

646# if LL is not None:

647# r = LL(r.lat, r.lon, height=r.height, datum=datum, name=self.name)

648# for n, v in pairs.items():

649# setattr(r, n, v)

650# return r

651

652 def toDatum(self, datum2, datum=None):

653 '''Convert this cartesian from one datum to an other.

654

655 @arg datum2: Datum to convert I{to} (L{Datum}).

656 @kwarg datum: Datum to convert I{from} (L{Datum}).

657

658 @return: The converted point (C{Cartesian}).

659

660 @raise TypeError: B{C{datum2}} or B{C{datum}}

661 invalid.

662 '''

663 _xinstanceof(Datum, datum2=datum2)

664

665 c = self if _isin(datum, None, self.datum) else \

666 self.toDatum(datum)

667

668 i, d = False, c.datum

669 if d == datum2:

670 return c.copy() if c is self else c

671

672 elif d is None or (d.transform.isunity and

673 datum2.transform.isunity):

674 return c.dup(datum=datum2)

675

676 elif d == _WGS84:

677 d = datum2 # convert from WGS84 to datum2

678

679 elif datum2 == _WGS84:

680 i = True # convert to WGS84 by inverse transformation

681

682 else: # neither datum2 nor c.datum is WGS84, invert to WGS84 first

683 c = c.toTransform(d.transform, inverse=True, datum=_WGS84)

684 d = datum2

685

686 return c.toTransform(d.transform, inverse=i, datum=datum2)

687

688 def toEcef(self):

689 '''Convert this cartesian to I{geodetic} (lat-/longitude) coordinates.

690

691 @return: An L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)}

692 with C{C} and C{M} if available.

693

694 @raise EcefError: A C{.datum} or an ECEF issue.

695 '''

696 return self._ecef9

697

698 def toLatLon(self, datum=None, height=None, LatLon=None, **LatLon_kwds): # see .ecef.Ecef9Tuple.toDatum

699 '''Convert this cartesian to a I{geodetic} (lat-/longitude) point.

700

701 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2} or L{a_f2Tuple}).

702 @kwarg height: Optional height, overriding the converted height (C{meter}), only if

703 C{B{LatLon} is not None}.

704 @kwarg LatLon: Optional class to return the geodetic point (C{LatLon}) or C{None}.

705 @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignored if

706 C{B{LatLon} is None}.

707

708 @return: The geodetic point (B{C{LatLon}}) or if C{B{LatLon}is None}, an

709 L{Ecef9Tuple}C{(x, y, z, lat, lon, height, C, M, datum)} with C{C}

710 and C{M} if available.

711

712 @raise TypeError: Invalid B{C{datum}} or B{C{LatLon_kwds}}.

713 '''

714 d = _spherical_datum(datum or self.datum, name=self.name)

715 if d == self.datum:

716 r = self.toEcef()

717 else:

718 c = self.toDatum(d)

719 r = c.Ecef(d, name=self.name).reverse(c, M=LatLon is None)

720

721 if LatLon: # class or .classof

722 h = _heigHt(r, height)

723 r = LatLon(r.lat, r.lon, datum=r.datum, height=h,

724 **_xkwds(LatLon_kwds, name=r.name))

725 _xdatum(r.datum, d)

726 return r

727

728 def toNvector(self, Nvector=None, datum=None, **name_Nvector_kwds):

729 '''Convert this cartesian to C{n-vector} components, I{including height}.

730

731 @kwarg Nvector: Optional class to return the C{n-vector} components

732 (C{Nvector}) or C{None}.

733 @kwarg datum: Optional datum (L{Datum}, L{Ellipsoid}, L{Ellipsoid2}

734 or L{a_f2Tuple}) overriding this cartesian's datum.

735 @kwarg name_Nvector_kwds: Optional C{B{name}=NN} (C{str}) and optionally,

736 additional B{C{Nvector}} keyword arguments, ignored if

737 C{B{Nvector} is None}.

738

739 @return: An B{C{Nvector}} or a L{Vector4Tuple}C{(x, y, z, h)} if

740 C{B{Nvector} is None}.

741

742 @raise TypeError: Invalid B{C{Nvector}}, B{C{datum}} or

743 B{C{name_Nvector_kwds}} item.

744

745 @raise ValueError: B{C{Cartesian}} at origin.

746 '''

747 r, d = self._N_vector.xyzh, self.datum

748 if datum is not None:

749 d = _spherical_datum(datum, name=self.name)

750 if d != self.datum:

751 r = self._n_xyzh4(d)

752

753 if Nvector is None:

754 n, _ = _name2__(name_Nvector_kwds, _or_nameof=self)

755 if n:

756 r = r.dup(name=n)

757 else:

758 kwds = _xkwds(name_Nvector_kwds, h=r.h, datum=d)

759 r = Nvector(r.x, r.y, r.z, **self._name1__(kwds))

760 return r

761

762 def toRtp(self):

763 '''Convert this cartesian to I{spherical, polar} coordinates.

764

765 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}

766 and C{phi}, both in L{Degrees}.

767

768 @see: Function L{xyz2rtp_} and class L{RadiusThetaPhi3Tuple}.

769 '''

770 return _rtp3(self.toRtp, Degrees, self, name=self.name)

771

772 def toStr(self, prec=3, fmt=Fmt.SQUARE, sep=_COMMASPACE_): # PYCHOK expected

773 '''Return the string representation of this cartesian.

774

775 @kwarg prec: Number of (decimal) digits, unstripped (C{int}).

776 @kwarg fmt: Enclosing backets format (C{letter}).

777 @kwarg sep: Separator to join (C{str}).

778

779 @return: Cartesian represented as "[x, y, z]" (C{str}).

780 '''

781 return Vector3d.toStr(self, prec=prec, fmt=fmt, sep=sep)

782

783 def toTransform(self, transform, inverse=False, datum=None):

784 '''Apply a Helmert transform to this cartesian.

785

786 @arg transform: Transform to apply (L{Transform} or L{TransformXform}).

787 @kwarg inverse: Apply the inverse of the C{B{transform}} (C{bool}).

788 @kwarg datum: Datum for the transformed cartesian (L{Datum}), overriding

789 this cartesian's datum but I{not} taken it into account.

790

791 @return: A transformed cartesian (C{Cartesian}) or a copy of this

792 cartesian if C{B{transform}.isunity}.

793

794 @raise TypeError: Invalid B{C{transform}}.

795 '''

796 _xinstanceof(Transform, transform=transform)

797 if transform.isunity:

798 c = self.dup(datum=datum or self.datum)

799 else:

800 # if inverse and d != _WGS84:

801 # raise _ValueError(inverse=inverse, datum=d,

802 # txt_not_=_WGS84.name)

803 xyz = transform.transform(*self.xyz3, inverse=inverse)

804 c = self.dup(xyz=xyz, datum=datum or self.datum)

805 return c

806

807 def toVector(self, Vector=None, **Vector_kwds):

808 '''Return this cartesian's I{geocentric} components as vector.

809

810 @kwarg Vector: Optional class to return the I{geocentric}

811 components (L{Vector3d}) or C{None}.

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

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

814

815 @return: A B{C{Vector}} or a L{Vector3Tuple}C{(x, y, z)} if

816 C{B{Vector} is None}.

817

818 @raise TypeError: Invalid B{C{Vector}} or B{C{Vector_kwds}}.

819 '''

820 return self.xyz if Vector is None else Vector(

821 self.x, self.y, self.z, **self._name1__(Vector_kwds))

822

823

824 class RadiusThetaPhi3Tuple(_NamedTupleTo):

825 '''3-Tuple C{(r, theta, phi)} with radial distance C{r} in C{meter}, inclination

826 C{theta} (with respect to the positive z-axis) and azimuthal angle C{phi} in

827 L{Degrees} I{or} L{Radians} representing a U{spherical, polar position

828 <https://WikiPedia.org/wiki/Spherical_coordinate_system>}.

829 '''

830 _Names_ = (_r_, _theta_, _phi_)

831 _Units_ = ( Meter, _Pass, _Pass)

832

833 def toCartesian(self, **name_Cartesian_and_kwds):

834 '''Convert this L{RadiusThetaPhi3Tuple} to a cartesian C{(x, y, z)} vector.

835

836 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN}, overriding this

837 name and optional class C{B{Cartesian}=None} and additional

838 C{B{Cartesian}} keyword arguments.

839

840 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword

841 argument is given, a L{Vector3Tuple}C{(x, y, z)} with C{x}, C{y}

842 and C{z} in the same units as radius C{r}, C{meter} conventionally.

843

844 @see: Function L{rtp2xyz_}.

845 '''

846 r, t, p = self

847 t, p, _ = _NamedTupleTo._Radians3(self, t, p)

848 return rtp2xyz_(r, t, p, **name_Cartesian_and_kwds)

849

850 def toDegrees(self, **name):

851 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Degrees}.

852

853 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.

854

855 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}

856 and C{phi} both in L{Degrees}.

857 '''

858 return self._toX3U(_NamedTupleTo._Degrees3, Degrees, name)

859

860 def toRadians(self, **name):

861 '''Convert this L{RadiusThetaPhi3Tuple}'s angles to L{Radians}.

862

863 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding this name.

864

865 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta}

866 and C{phi} both in L{Radians}.

867 '''

868 return self._toX3U(_NamedTupleTo._Radians3, Radians, name)

869

870 def _toU(self, U):

871 M = RadiusThetaPhi3Tuple._Units_[0] # Meter

872 return self.reUnit(M, U, U).toUnits()

873

874 def _toX3U(self, _X3, U, name):

875 r, t, p = self

876 t, p, s = _X3(self, t, p)

877 if s is None or name:

878 n = self._name__(name)

879 s = self.classof(r, t, p, name=n)._toU(U)

880 return s

881

882

883 def rtp2xyz(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds):

884 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.

885

886 @arg theta: Inclination B{C{theta}} (C{degrees} with respect to the positive z-axis),

887 required if C{B{r_rtp}} is C{scalar}, ignored otherwise.

888 @arg phi: Azimuthal angle B{C{phi}} (C{degrees}), like B{C{theta}}.

889

890 @see: Function L{rtp2xyz_} for further details.

891 '''

892 if isinstance(r_rtp, RadiusThetaPhi3Tuple):

893 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

894 else:

895 c = rtp2xyz_(r_rtp, radians(theta), radians(phi), **name_Cartesian_and_kwds)

896 return c

897

898

899 def rtp2xyz_(r_rtp, theta=0, phi=0, **name_Cartesian_and_kwds):

900 '''Convert I{spherical, polar} C{(r, theta, phi)} to cartesian C{(x, y, z)} coordinates.

901

902 @arg r_rtp: Radial distance (C{scalar}, conventially C{meter}) or a previous

903 L{RadiusThetaPhi3Tuple} instance.

904 @arg theta: Inclination B{C{theta}} (C{radians} with respect to the positive z-axis),

905 required if C{B{r_rtp}} is C{scalar}, ignored otherwise.

906 @arg phi: Azimuthal angle B{C{phi}} (C{radians}), like B{C{theta}}.

907 @kwarg name_Cartesian_and_kwds: Optional C{B{name}=NN} (C{str}), C{B{Cartesian}=None}

908 class to return the coordinates and optionally, additional C{B{Cartesian}}

909 keyword arguments.

910

911 @return: A C{B{Cartesian}(x, y, z)} instance or if no C{B{Cartesian}} keyword argument

912 is given a L{Vector3Tuple}C{(x, y, z)}, with C{x}, C{y} and C{z} in the same

913 units as radius C{r}, C{meter} conventionally.

914

915 @raise TypeError: Invalid B{C{r_rtp}}, B{C{theta}}, B{C{phi}} or

916 B{C{name_Cartesian_and_kwds}} item.

917

918 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}

919 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and functions L{rtp2xyz}

920 and L{xyz2rtp}.

921 '''

922 if isinstance(r_rtp, RadiusThetaPhi3Tuple):

923 c = r_rtp.toCartesian(**name_Cartesian_and_kwds)

924 elif _isMeter(r_rtp):

925 r = r_rtp

926 if r and _isfinite(r):

927 s, z, y, x = sincos2_(theta, phi)

928 s *= r

929 z *= r

930 y *= s

931 x *= s

932 else:

933 x = y = z = r

934

935 n, kwds = _name2__(**name_Cartesian_and_kwds)

936 C, kwds = _xkwds_pop2(kwds, Cartesian=None)

937 c = Vector3Tuple(x, y, z, name=n) if C is None else \

938 C(x, y, z, name=n, **kwds)

939 else:

940 raise _TypeError(r_rtp=r_rtp, theta=theta, phi=phi)

941 return c

942

943

944 def _rtp3(where, U, *x_y_z, **name):

945 '''(INTERNAL) Helper for C{.toRtp}, C{xyz2rtp} and C{xyz2rtp_}.

946 '''

947 x, y, z = _MODS.vector3dBase._xyz3(where, *x_y_z)

948 r = hypot_(x, y, z)

949 if r > 0:

950 t = acos1(z / r)

951 p = atan2(y, x)

952 while p < 0:

953 p += PI2

954 if U is Degrees:

955 t = degrees(t)

956 p = degrees(p)

957 else:

958 t = p = _0_0

959 return RadiusThetaPhi3Tuple(r, t, p, **name)._toU(U)

960

961

962 def xyz2rtp(x_xyz, y=0, z=0, **name):

963 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.

964

965 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with C{theta} and C{phi}, both

966 in L{Degrees}.

967

968 @see: Function L{xyz2rtp_} for further details.

969 '''

970 return _rtp3(xyz2rtp, Degrees, x_xyz, y, z, **name)

971

972

973 def xyz2rtp_(x_xyz, y=0, z=0, **name):

974 '''Convert cartesian C{(x, y, z)} to I{spherical, polar} C{(r, theta, phi)} coordinates.

975

976 @arg x_xyz: X component (C{scalar}) or a cartesian (C{Cartesian}, L{Ecef9Tuple},

977 C{Nvector}, L{Vector3d}, L{Vector3Tuple}, L{Vector4Tuple} or a C{tuple} or

978 C{list} of 3+ C{scalar} items) if no C{y_z} specified.

979 @arg y: Y component (C{scalar}), required if C{B{x_xyz}} is C{scalar}, ignored otherwise.

980 @arg z: Z component (C{scalar}), like B{C{y}}.

981 @kwarg name: Optional C{B{name}=NN} (C{str}).

982

983 @return: L{RadiusThetaPhi3Tuple}C{(r, theta, phi)} with radial distance C{r} (C{meter},

984 same units as C{x}, C{y} and C{z}), inclination C{theta} (polar angle with

985 respect to the positive z-axis) and azimuthal angle C{phi}, both in L{Radians}.

986

987 @see: U{Physics convention<https://WikiPedia.org/wiki/Spherical_coordinate_system>}

988 (ISO 80000-2:2019), class L{RadiusThetaPhi3Tuple} and function L{xyz2rtp}.

989 '''

990 return _rtp3(xyz2rtp_, Radians, x_xyz, y, z, **name)

991

992

993 __all__ += _ALL_DOCS(CartesianBase)

994

995# **) MIT License

996#

997# Copyright (C) 2016-2026 -- mrJean1 at Gmail -- All Rights Reserved.

998#

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

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

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

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

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

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

1005#

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

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

1008#

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

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

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

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

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

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

1015# OTHER DEALINGS IN THE SOFTWARE.