Coverage for pygeodesy / namedTuples.py: 91%

366 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-08-27 13:47 -0400

1 

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

3 

4u'''Named tuples. 

5 

6Tuples returned by C{pygeodesy} functions and class methods 

7are all instances of some C{Named...Tuple} class, all sub-classes 

8of C{_NamedTuple} defined in C{pygeodesy.named}. 

9''' 

10 

11from pygeodesy.basics import isinstanceof, issubclassof, map1, _xinstanceof 

12# from pygeodesy.cartesianBase import CartesianBase # _MODS 

13from pygeodesy.constants import INT0, NAN, _0_5, _isNAN, fabs # PYCHOK used! _0_5 

14# from pygeodesy.dms import toDMS # _MODS 

15from pygeodesy.errors import _TypeError, _xattr, _xkwds, _xkwds_not, _xkwds_pop2 

16# from pygeodesy.internals import typename # from .named 

17from pygeodesy.interns import NN, _1_, _2_, _a_, _A_, _area_, _angle_, _b_, _B_, \ 

18 _band_, _beta_, _c_, _C_, _D_, _datum_, _distance_, _E_, \ 

19 _easting_, _end_, _fi_, _gamma_, _H_, _h_, _height_, \ 

20 _hemipole_, _initial_, _j_, _lam_, _lat_, _lon_, _N_, \ 

21 _n_, _northing_, _number_, _outside_, _phi_, _point_, \ 

22 _precision_, _points_, _radius_, _scale_, _start_, \ 

23 _x_, _y_, _z_, _zone_ 

24# from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .named 

25from pygeodesy.named import _NamedTuple, _Pass, _ALL_LAZY, _MODS, typename 

26from pygeodesy.props import deprecated_property_RO, Property_RO, property_RO 

27from pygeodesy.units import Band, Bearing, Degrees, Degrees2, Easting, FIx, \ 

28 Height, Int, Lam, Lamd, Lat, Lon, Meter, Meter2, \ 

29 Northing, Number_, Phi, Phid, Precision_, Radians, \ 

30 Radius, Scalar, Str 

31# from math import fabs # from .constants 

32 

33__all__ = _ALL_LAZY.namedTuples 

34__version__ = '26.08.14' 

35 

36# __DUNDER gets mangled in class 

37_closest_ = 'closest' 

38_destination_ = 'destination' 

39_elel_ = 'll' 

40_final_ = 'final' 

41_fraction_ = 'fraction' 

42 

43 

44class Bearing2Tuple(_NamedTuple): 

45 '''2-Tuple C{(initial, final)} bearings, both in compass C{degrees360}. 

46 ''' 

47 _Names_ = (_initial_, _final_) 

48 _Units_ = ( Bearing, Bearing) 

49 

50 

51class Bounds2Tuple(_NamedTuple): # .geohash.py, .latlonBase.py, .points.py 

52 '''2-Tuple C{(latlonSW, latlonNE)} with the bounds' lower-left and 

53 upper-right corner as C{LatLon} instance. 

54 ''' 

55 _Names_ = ('latlonSW', 'latlonNE') 

56 _Units_ = (_Pass, _Pass) 

57 

58 

59class Bounds4Tuple(_NamedTuple): # .geohash.py, .points.py 

60 '''4-Tuple C{(latS, lonW, latN, lonE)} with the bounds' lower-left 

61 C{(LatS, LonW)} and upper-right C{(latN, lonE)} corner lat- and 

62 longitudes. 

63 ''' 

64 _Names_ = ('latS', 'lonW', 'latN', 'lonE') 

65 _Units_ = ( Lat, Lon, Lat, Lon) 

66 

67 def _dupof(self, which, *args, **name): 

68 kwds = name or dict(name=self.name or typename(which)) 

69 return self.classof(*args, **kwds) 

70 

71 def enclosures(self, S_other, *W_N_E, **name): 

72 '''Get the enclosures of this around an other L{Bounds4Tuple}. 

73 

74 @arg S_other: Bottom C{latS} (C{scalar}) or an other 

75 L{Bounds4Tuple} instance. 

76 @arg W_N_E: Left C{lonW}, top C{latN} and right C{lonE}, 

77 each a (C{scalar}) for C{scalar B{S_other}}. 

78 

79 @return: A L{Bounds4Tuple} with the I{margin} at each of 

80 the 4 sides, positive if this side I{encloses} 

81 (is on the I{outside} of) the other, negative 

82 if not or zero if abutting. 

83 ''' 

84 s, w, n, e, \ 

85 S, W, N, E = self._8_tuple(S_other, W_N_E) 

86 return self._dupof(Bounds4Tuple.enclosures, 

87 map1(float, S - s, W - w, 

88 n - N, e - E), **name) 

89 

90 def isinside(self, lat, lon, eps=0): 

91 '''Are B{C{lat}} and B{C{lon}} both inside these bounds? 

92 

93 @arg lat: Latitude (C{degrees}, scalar or C{str}). 

94 @arg lon: Longitude (C{degrees}, scalar or C{str}). 

95 @kwarg eps: Over-/undersize the C{RD} bounds (C{degrees}). 

96 

97 @return: C{False} if B{C{lat}} or B{C{lon}} is C{NAN} or 

98 outside these bounds, C{True} otherwise. 

99 ''' 

100 return _isinside(Lat(lat, clip=0), Lon(lon, clip=0), 

101 Degrees(eps=eps) if eps else 0, self) 

102 

103 @Property_RO 

104 def latC(self): 

105 '''Get the center latitude (C{degrees}). 

106 ''' 

107 return Lat(latC=(self.latS + self.latN) * _0_5) 

108 

109 @Property_RO 

110 def latZ(self): 

111 '''Get the latitudinal size (C{degrees}). 

112 ''' 

113 return Lat(latZ=self.latN - self.latS) 

114 

115 @Property_RO 

116 def lonC(self): 

117 '''Get the center longitude (C{degrees}). 

118 ''' 

119 return Lon(lonC=(self.lonW + self.lonE) * _0_5) 

120 

121 @Property_RO 

122 def lonZ(self): 

123 '''Get the longitudinal size (C{degrees}). 

124 ''' 

125 return Lon(lonZ=self.lonE - self.lonW) 

126 

127 def overlap(self, S_other, *W_N_E, **name): 

128 '''Intersect this with an other L{Bounds4Tuple}. 

129 

130 @arg S_other: Bottom C{latS} (C{scalar}) or an other 

131 L{Bounds4Tuple} instance. 

132 @arg W_N_E: Left C{lonW}, top C{latN} and right C{lonE}, 

133 each a (C{scalar}) for C{scalar B{S_other}}. 

134 

135 @return: C{None} if the bounds do not overlap, otherwise 

136 the intersection of both as a L{Bounds4Tuple}. 

137 ''' 

138 s, w, n, e, \ 

139 S, W, N, E = self._8_tuple(S_other, W_N_E) 

140 return None if s > N or n < S or w > E or e < W else \ 

141 self._dupof(Bounds4Tuple.overlap, 

142 max(s, S), max(w, W), 

143 min(n, N), min(e, E), **name) 

144 

145 def resize(self, eps): 

146 '''Get these bounds, over- or undersize by C{B{eps}}. 

147 

148 @arg eps: In- or decrease (C{degrees}). 

149 

150 @return: A L{Bounds4Tuple}C{(latS, lonW, latN, lonE)} 

151 with all 4 bounds resized. 

152 ''' 

153 return _resize4(self, Degrees(eps=eps)) 

154 

155 def _8_tuple(self, S_other, W_N_E): 

156 # return 8-tuple this (s, w, n, e) + other (S, W, N, E) 

157 if W_N_E: 

158 S_other = Bounds4Tuple(S_other, *W_N_E) 

159 else: 

160 _xinstanceof(Bounds4Tuple, S_other=S_other) 

161 return tuple(self) + S_other.toUnits() 

162 

163 def union(self, S_other, *W_N_E, **name): 

164 '''Union of this and an other L{Bounds4Tuple}. 

165 

166 @arg S_other: Bottom C{latS} (C{scalar}) or an other 

167 L{Bounds4Tuple} instance. 

168 @arg W_N_E: Left C{lonW}, top C{latN} and right C{lonE}, 

169 each a (C{scalar}) for C{scalar B{S_other}}. 

170 

171 @return: The C{union} a L{Bounds4Tuple}. 

172 ''' 

173 s, w, n, e, \ 

174 S, W, N, E = self._8_tuple(S_other, W_N_E) 

175 return self._dupof(Bounds4Tuple.union, 

176 min(s, S), min(w, W), 

177 max(n, N), max(e, E), **name) 

178 

179 

180class Circle4Tuple(_NamedTuple): 

181 '''4-Tuple C{(radius, height, lat, beta)} with the C{radius} and C{height} 

182 of a parallel I{circle of latitude} at (geodetic) latitude C{lat} and 

183 I{parametric (or reduced) auxiliary latitude} C{beta} on a I{biaxial 

184 ellipsoid}. 

185 

186 The C{height} is the (signed) distance along the z-axis between the 

187 parallel and the equator. At near-polar C{lat}s, the C{radius} is C{0}, 

188 the C{height} is the ellipsoid's polar radius (signed) and C{beta} 

189 equals C{lat}. The latter are in C{degrees90}, always. 

190 

191 @see: Class L{Ellipse5Tuple}. 

192 ''' 

193 _Names_ = (_radius_, _height_, _lat_, _beta_) 

194 _Units_ = ( Radius, Height, Lat, Lat) 

195 

196 @property_RO 

197 def abc3(self): 

198 '''Get the non-negative semi-axes as 3-tuple C{(a, b, c)}. 

199 ''' 

200 return map1(fabs, self.radius, self.radius, self.height) # PYCHOK named 

201 

202 

203class Destination2Tuple(_NamedTuple): # .ellipsoidalKarney.py, -Vincenty.py 

204 '''2-Tuple C{(destination, final)}, C{destination} in C{LatLon} 

205 and C{final} bearing in compass C{degrees360}. 

206 ''' 

207 _Names_ = (_destination_, _final_) 

208 _Units_ = (_Pass, Bearing) 

209 

210 

211class Destination3Tuple(_NamedTuple): # .karney.py 

212 '''3-Tuple C{(lat, lon, final)}, destination C{lat}, C{lon} in 

213 C{degrees90} respectively C{degrees180} and C{final} bearing 

214 in compass C{degrees360}. 

215 ''' 

216 _Names_ = (_lat_, _lon_, _final_) 

217 _Units_ = ( Lat, Lon, Bearing) 

218 

219 

220class Distance2Tuple(_NamedTuple): # .datum.py, .ellipsoidalBase.py 

221 '''2-Tuple C{(distance, initial)}, C{distance} in C{meter} and 

222 C{initial} bearing in compass C{degrees360}. 

223 ''' 

224 _Names_ = (_distance_, _initial_) 

225 _Units_ = ( Meter, Bearing) 

226 

227 

228class Distance3Tuple(_NamedTuple): # .ellipsoidalKarney.py, -Vincenty.py 

229 '''3-Tuple C{(distance, initial, final)}, C{distance} in C{meter} 

230 and C{initial} and C{final} bearing, both in compass C{degrees360}. 

231 ''' 

232 _Names_ = (_distance_, _initial_, _final_) 

233 _Units_ = ( Meter, Bearing, Bearing) 

234 

235 

236class Distance4Tuple(_NamedTuple): # .formy.py, .points.py 

237 '''4-Tuple C{(distance2, delta_lat, delta_lon, unroll_lon2)} with 

238 the distance in C{degrees squared}, the latitudinal C{delta_lat 

239 = B{lat2} - B{lat1}}, the wrapped, unrolled and adjusted 

240 longitudinal C{delta_lon = B{lon2} - B{lon1}} and C{unroll_lon2}, 

241 the unrolled or original B{C{lon2}}. 

242 

243 @note: Use Function L{pygeodesy.degrees2m} to convert C{degrees 

244 squared} to C{meter} as M{degrees2m(sqrt(distance2), ...)} 

245 or M{degrees2m(hypot(delta_lat, delta_lon), ...)}. 

246 ''' 

247 _Names_ = ('distance2', 'delta_lat', 'delta_lon', 'unroll_lon2') 

248 _Units_ = ( Degrees2, Degrees, Degrees, Degrees) 

249 

250 

251class EasNor2Tuple(_NamedTuple): # .css, .osgr, .ups, .utm, .utmupsBase 

252 '''2-Tuple C{(easting, northing)}, both in C{meter}, conventionally. 

253 ''' 

254 _Names_ = (_easting_, _northing_) 

255 _Units_ = ( Easting, Northing) 

256 

257 

258class EasNor3Tuple(_NamedTuple): # .css.py, .lcc.py 

259 '''3-Tuple C{(easting, northing, height)}, all in C{meter}, conventionally. 

260 ''' 

261 _Names_ = (_easting_, _northing_, _height_) 

262 _Units_ = ( Easting, Northing, Height) 

263 

264 

265class _Convergence(object): 

266 '''(INTERNAL) DEPRECATED Property C{convergence}, use property C{gamma}.''' 

267 @deprecated_property_RO 

268 def convergence(self): 

269 '''DEPRECATED, use property C{gamma}. 

270 ''' 

271 return self.gamma # PYCHOK self[.] 

272 

273 

274class Ellipse5Tuple(_NamedTuple): # in .triaxials.bases._UnOrderedTriaxialBase.ellipse5 

275 '''5-Tuple C{(a, b, height, lat, beta)} with semi-axes C{a} and C{b} of a parallel 

276 I{ellipse of latitude} at (geodetic) latitude C{lat} and I{parametric (or reduced) 

277 auxiliary latitude} C{beta} of a I{triaxial ellipsoid}. 

278 

279 The C{height} is the (signed) distance between the parallel and the triaxial's 

280 equatorial plane. At near-polar C{lat}s, C{a} and C{b} are C{0}, the C{height} 

281 is the triaxial semi-axis C{c} (signed) and C{beta} equals C{lat}. The latter 

282 are in C{degrees90}, always. 

283 

284 @see: Class L{Circle4Tuple}. 

285 ''' 

286 _Names_ = (_a_, _b_, _height_, _lat_, _beta_) 

287 _Units_ = ( Radius, Radius, Height, Lat, Lat) 

288 

289 @property_RO 

290 def abc3(self): 

291 '''Get the semi-axes as 3-tuple C{(a, b, c)}, non-negative. 

292 ''' 

293 return map1(fabs, self.a, self.b, self.height) # PYCHOK namedTuple 

294 

295 @property_RO 

296 def abc3ordered(self): 

297 '''Get the semi-axes as 3-tuple C{(a, b, c)}, non-negative, ordered. 

298 ''' 

299 return tuple(reversed(sorted(self.abc3))) 

300 

301 def toTriaxial(self, **Triaxial_and_kwds): # like .Ellipse.toTriaxial_ 

302 '''Return a L{Triaxial_<pygeodesy.Triaxial>} from this tuple's semi-axes C{abc3ordered}. 

303 

304 @kwarg Triaxial_and_kwds: Optional C{B{Triaxial}=Triaxial} class and additional 

305 C{Triaxial} keyword arguments. 

306 ''' 

307 T, kwds = _xkwds_pop2(Triaxial_and_kwds, Triaxial=_MODS.triaxials.Triaxial) 

308 return T(*self.abc3ordered, **_xkwds(kwds, name=self.name)) # 'NN' 

309 

310 def toTriaxial_(self, **Triaxial_and_kwds): # like .Ellipse.toTriaxial_ 

311 '''Return a L{Triaxial_<pygeodesy.Triaxial_>} from this tuple's semi-axes C{abc3}. 

312 

313 @kwarg Triaxial_and_kwds: Optional C{B{Triaxial}=Triaxial_} class and additional 

314 C{Triaxial_} keyword arguments. 

315 ''' 

316 T, kwds = _xkwds_pop2(Triaxial_and_kwds, Triaxial=_MODS.triaxials.Triaxial_) 

317 return T(*self.abc3, **_xkwds(kwds, name=self.name)) # 'NN' 

318 

319 

320class Forward4Tuple(_NamedTuple, _Convergence): 

321 '''4-Tuple C{(easting, northing, gamma, scale)} in C{meter}, C{meter}, meridian 

322 convergence C{gamma} at point in C{degrees} and the C{scale} of projection at 

323 point C{scalar}. 

324 ''' 

325 _Names_ = (_easting_, _northing_, _gamma_, _scale_) 

326 _Units_ = ( Easting, Northing, Degrees, Scalar) 

327 

328 

329class Intersection3Tuple(_NamedTuple): # .css.py, .lcc.py 

330 '''3-Tuple C{(point, outside1, outside2)} of an intersection C{point} and C{outside1}, 

331 the position of the C{point}, C{-1} if before the start, C{+1} if after the end and 

332 C{0} if on or between the start and end point of the first line. 

333 

334 Similarly, C{outside2} is C{-2}, C{+2} or C{0} to indicate the position of the 

335 intersection C{point} on the second line or path. 

336 

337 If a path was specified with an initial bearing instead of an end point, C{outside1} 

338 and/or C{outside2} will be C{0} if the intersection C{point} is on the start point 

339 or C{+1} respectively C{+2} if the intersection C{point} is after the start point, 

340 in the direction of the bearing. 

341 ''' 

342 _Names_ = (_point_, _outside_ + _1_, _outside_ + _2_) 

343 _Units_ = (_Pass, Int, Int) 

344 

345 

346class LatLon2Tuple(_NamedTuple): 

347 '''2-Tuple C{(lat, lon)} in C{degrees90} and C{degrees180}. 

348 ''' 

349 _Names_ = (_lat_, _lon_) 

350 _Units_ = ( Lat, Lon) 

351 

352 def to3Tuple(self, height, **name): 

353 '''Extend this L{LatLon2Tuple} to a L{LatLon3Tuple}. 

354 

355 @arg height: The height to add (C{scalar}). 

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

357 this name. 

358 

359 @return: A L{LatLon3Tuple}C{(lat, lon, height)}. 

360 

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

362 ''' 

363 return self._xtend(LatLon3Tuple, height, **name) 

364 

365 def to4Tuple(self, height, datum, **name): 

366 '''Extend this L{LatLon2Tuple} to a L{LatLon4Tuple}. 

367 

368 @arg height: The height to add (C{scalar}). 

369 @arg datum: The datum to add (C{Datum}). 

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

371 this name. 

372 

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

374 

375 @raise TypeError: If B{C{datum}} not a C{Datum}. 

376 

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

378 ''' 

379 return self.to3Tuple(height).to4Tuple(datum, **name) 

380 

381 

382class LatLon3Tuple(_NamedTuple): 

383 '''3-Tuple C{(lat, lon, height)} in C{degrees90}, C{degrees180} 

384 and C{meter}, conventionally. 

385 ''' 

386 _Names_ = LatLon2Tuple._Names_ + (_height_,) 

387 _Units_ = LatLon2Tuple._Units_ + ( Height,) 

388 

389 def to4Tuple(self, datum, **name): 

390 '''Extend this L{LatLon3Tuple} to a L{LatLon4Tuple}. 

391 

392 @arg datum: The datum to add (C{Datum}). 

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

394 this name. 

395 

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

397 

398 @raise TypeError: If B{C{datum}} not a C{Datum}. 

399 ''' 

400 _xinstanceof(_MODS.datums.Datum, datum=datum) 

401 return self._xtend(LatLon4Tuple, datum, **name) 

402 

403 

404class LatLon4Tuple(LatLon3Tuple): # .cartesianBase, .css, .ecef, .lcc 

405 '''4-Tuple C{(lat, lon, height, datum)} in C{degrees90}, 

406 C{degrees180}, C{meter} and L{Datum}. 

407 ''' 

408 _Names_ = LatLon3Tuple._Names_ + (_datum_,) 

409 _Units_ = LatLon3Tuple._Units_ + (_Pass,) 

410 

411 

412def _LL4Tuple(lat, lon, height, datum, LatLon, LatLon_kwds, inst=None, 

413 iteration=None, **name): 

414 '''(INTERNAL) Return a L{LatLon4Tuple} or a B{C{LatLon}} instance. 

415 ''' 

416 if LatLon is None: # ignore LatLon_kwds 

417 r = LatLon4Tuple(lat, lon, height, datum, **name) 

418 else: 

419 kwds = {} if inst is None else _xkwds_not(None, 

420# datum=_xattr(inst, datum=None), 

421 epoch=_xattr(inst, epoch=None), 

422 reframe=_xattr(inst, reframe=None)) # PYCHOK indent 

423 kwds.update(datum=datum, height=height, **name) 

424 if LatLon_kwds: 

425 kwds.update(LatLon_kwds) 

426 r = LatLon(lat, lon, **kwds) 

427 if iteration is not None: # like .named._namedTuple.__new__ 

428 r._iteration = iteration 

429 return r 

430 

431 

432class LatLonDatum3Tuple(_NamedTuple): # .lcc.py, .osgr.py 

433 '''3-Tuple C{(lat, lon, datum)} in C{degrees90}, C{degrees180} 

434 and L{Datum}. 

435 ''' 

436 _Names_ = LatLon2Tuple._Names_ + (_datum_,) 

437 _Units_ = LatLon2Tuple._Units_ + (_Pass,) 

438 

439 

440class LatLonDatum5Tuple(LatLonDatum3Tuple, _Convergence): # .ups.py, .utm.py, .utmupsBase.py 

441 '''5-Tuple C{(lat, lon, datum, gamma, scale)} in C{degrees90}, 

442 C{degrees180}, L{Datum}, C{degrees} and C{float}. 

443 ''' 

444 _Names_ = LatLonDatum3Tuple._Names_ + (_gamma_, _scale_) 

445 _Units_ = LatLonDatum3Tuple._Units_ + ( Degrees, Scalar) 

446 

447 

448class LatLonHeight3Tuple(_NamedTuple): # pyaxqg, pybelbg 

449 '''3-Tuple C{(lat, lon, H)} with orthometric height C{H} in C{meter}, conventionally. 

450 ''' 

451 _Names_ = LatLon2Tuple._Names_ + (_H_,) 

452 _Units_ = LatLon3Tuple._Units_ 

453 

454 

455class LatLonNgeoid3Tuple(_NamedTuple): # pyaxqg, pybelbg 

456 '''3-Tuple C{(lat, lon, N)} with geoid height C{N} in C{meter}, conventionally. 

457 ''' 

458 _Names_ = LatLon2Tuple._Names_ + (_N_,) 

459 _Units_ = LatLon3Tuple._Units_ 

460 

461 

462class _H_lat_lon_height4Tuple(_NamedTuple): # pyaxqg, pybelbg, pyrdnap 

463 '''4-Tuple C{(H, lat, lon, height)} (INTERNAL) Base and "middle" tuple for 

464 C{pyaxqg.AxQG8Tuple}, C{pybelbg.BeLBG7Tuple} and C{pyrdnap.RDNAP7Tuple}. 

465 ''' 

466 _Names_ = (_H_, _lat_, _lon_, _height_) # middle names, extended 

467 _Units_ = ( Height, Lat, Lon, Height) # middle units, extended 

468 

469 @Property_RO 

470 def h(self): 

471 '''Get the ellipsoidal height C{h} (C{meter}, conventionally) or C{NAN}. 

472 ''' 

473 return self.height # PYCHOK height 

474 

475 @Property_RO 

476 def lam(self): 

477 '''Get the longitude (B{C{radians}}). 

478 ''' 

479 return Lamd(self.lon) # PYCHOK lon 

480 

481 @Property_RO 

482 def latlon(self): 

483 '''Get the lat-, longitude in C{degrees} (L{LatLon2Tuple}C{(lat, lon)}). 

484 ''' 

485 return LatLon2Tuple(self.lat, self.lon, name=self.name) # PYCHOK lat, lon, name 

486 

487 @Property_RO 

488 def latlonheight(self): 

489 '''Get the lat-, longitude in C{degrees} and ellipsoidal height (L{LatLon3Tuple}C{(lat, lon, height)}). 

490 ''' 

491 return self.latlon.to3Tuple(self.height) # PYCHOK height 

492 

493 @Property_RO 

494 def latlonHeight(self): 

495 '''Get the lat-, longitude in C{degrees} and orthometric height (L{LatLonHeight3Tuple}C{(lat, lon, H)}). 

496 ''' 

497 return LatLonHeight3Tuple(self.lat, self.lon, self.H) # PYCHOK lat, lon, H 

498 

499 @Property_RO 

500 def latlonNgeoid(self): 

501 '''Get the lat-, longitude in C{degrees} and geoid height (L{LatLonNgeoid3Tuple}C{(lat, lon, N)}). 

502 ''' 

503 return LatLonNgeoid3Tuple(self.lat, self.lon, self.N, name=self.name) # PYCHOK lat, lon, name 

504 

505 @Property_RO 

506 def N(self): 

507 '''Get the geoid height C{N} (C{meter}, conventionally) or C{NAN}. 

508 ''' 

509 N = self.height - self.H # PYCHOK height, H 

510 return NAN if _isNAN(N) else Height(N=N) 

511 

512 @Property_RO 

513 def phi(self): 

514 '''Get the latitude (B{C{radians}}). 

515 ''' 

516 return Phid(self.lat) # PYCHOK lat 

517 

518 @Property_RO 

519 def philam(self): 

520 '''Get the lat- and longitude in C{radians} (L{PhiLam2Tuple}C{(phi, lam)}). 

521 ''' 

522 return PhiLam2Tuple(self.phi, self.lam, name=self.name) # PYCHOK lam, phi 

523 

524 @Property_RO 

525 def philamheight(self): 

526 '''Get the lat-, longitude in C{radians} and ellipsoidal height (L{PhiLam3Tuple}C{(phi, lam, height)}). 

527 ''' 

528 return self.philam.to3Tuple(self.height) # PYCHOK height 

529 

530 @Property_RO 

531 def philamHeight(self): 

532 '''Get the lat-, longitude in C{radians} and orthometric height (L{PhiLamHeight3Tuple}C{(phi, lam, H)}). 

533 ''' 

534 return PhiLamHeight3Tuple(self.phi, self.lam, self.H) # PYCHOK phi, lam, H 

535 

536 @Property_RO 

537 def philamNgeoid(self): 

538 '''Get the lat-, longitude in C{radians} and geoid height (L{PhiLamNgeoid3Tuple}C{(phi, lam, N)}). 

539 ''' 

540 return PhiLamNgeoid3Tuple(self.phi, self.lam, self.N, name=self.name) # PYCHOK phi, lam, name 

541 

542 

543class LatLonPrec3Tuple(_NamedTuple): # .gars.py, .wgrs.py 

544 '''3-Tuple C{(lat, lon, precision)} in C{degrees}, C{degrees} 

545 and C{int}. 

546 ''' 

547 _Names_ = (_lat_, _lon_, _precision_) 

548 _Units_ = ( Lat, Lon, Precision_) 

549 

550 def to5Tuple(self, height, radius, **name): 

551 '''Extend this L{LatLonPrec3Tuple} to a L{LatLonPrec5Tuple}. 

552 

553 @arg height: The height to add (C{float} or C{None}). 

554 @arg radius: The radius to add (C{float} or C{None}). 

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

556 this name. 

557 

558 @return: A L{LatLonPrec5Tuple}C{(lat, lon, precision, 

559 height, radius)}. 

560 ''' 

561 return self._xtend(LatLonPrec5Tuple, height, radius, **name) 

562 

563 

564class LatLonPrec5Tuple(LatLonPrec3Tuple): # .wgrs.py 

565 '''5-Tuple C{(lat, lon, precision, height, radius)} in C{degrees}, 

566 C{degrees}, C{int} and C{height} or C{radius} in C{meter} (or 

567 C{None} if missing). 

568 ''' 

569 _Names_ = LatLonPrec3Tuple._Names_ + (_height_, _radius_) 

570 _Units_ = LatLonPrec3Tuple._Units_ + ( Height, Radius) 

571 

572 

573class _NamedTupleTo(_NamedTuple): # in .testNamedTuples 

574 '''(INTERNAL) Base for C{-.toDegrees}, C{-.toRadians}. 

575 ''' 

576 def _Degrees3(self, *xs, **toDMS_kwds): 

577 '''(INTERNAL) Convert C{xs} from C{Radians} to C{Degrees} or C{toDMS}. 

578 ''' 

579 if toDMS_kwds: 

580 toDMS_kwds = _xkwds(toDMS_kwds, ddd=1, pos=NN) 

581 toDMS, s = _MODS.dms.toDMS, None 

582 else: 

583 toDMS, s = None, self 

584 for x in xs: 

585 if not isinstanceof(x, Degrees): 

586 x, s = x.toDegrees(), None 

587 yield toDMS(x, **toDMS_kwds) if toDMS else x 

588 yield s 

589 

590 def _Radians3(self, *xs, **unused): 

591 '''(INTERNAL) Convert C{xs} from C{Degrees} to C{Radians}. 

592 ''' 

593 s = self 

594 for x in xs: 

595 if not isinstanceof(x, Radians): 

596 x, s = x.toRadians(), None 

597 yield x 

598 yield s 

599 

600 

601class NearestOn2Tuple(_NamedTuple): # .ellipsoidalBaseDI 

602 '''2-Tuple C{(closest, fraction)} of the C{closest} point 

603 on and C{fraction} along a line (segment) between two 

604 points. The C{fraction} is C{0} if the closest point 

605 is the first or C{1} the second of the two points. 

606 Negative C{fraction}s indicate the closest point is 

607 C{before} the first point. For C{fraction > 1.0} 

608 the closest point is after the second point. 

609 ''' 

610 _Names_ = (_closest_, _fraction_) 

611 _Units_ = (_Pass, _Pass) 

612 

613 

614class NearestOn3Tuple(_NamedTuple): # .points.py, .sphericalTrigonometry 

615 '''3-Tuple C{(closest, distance, angle)} of the C{closest} 

616 point on the polygon, either a C{LatLon} instance or a 

617 L{LatLon3Tuple}C{(lat, lon, height)} and the C{distance} 

618 and C{angle} to the C{closest} point are in C{meter} 

619 respectively compass C{degrees360}. 

620 ''' 

621 _Names_ = (_closest_, _distance_, _angle_) 

622 _Units_ = (_Pass, Meter, Degrees) 

623 

624 

625# NearestOn4Tuple DEPRECATED, see .deprecated.classes.NearestOn4Tuple 

626 

627 

628class NearestOn5Tuple(_NamedTuple): 

629 '''5-Tuple C{(lat, lon, distance, angle, height)} all in C{degrees}, 

630 except C{height}. The C{distance} is the L{pygeodesy.equirectangular} 

631 distance between the closest and the reference B{C{point}} in C{degrees}. 

632 The C{angle} from the reference B{C{point}} to the closest point is in 

633 compass C{degrees360}, see function L{pygeodesy.compassAngle}. The 

634 C{height} is the (interpolated) height at the closest point in C{meter} 

635 or C{0}. 

636 ''' 

637 _Names_ = (_lat_, _lon_, _distance_, _angle_, _height_) 

638 _Units_ = ( Lat, Lon, Degrees, Degrees, Meter) 

639 

640 

641class NearestOn6Tuple(_NamedTuple): # .latlonBase.py, .vector3d.py 

642 '''6-Tuple C{(closest, distance, fi, j, start, end)} with the C{closest} 

643 point, the C{distance} in C{meter}, conventionally and the C{start} 

644 and C{end} point of the path or polygon edge. Fractional index C{fi} 

645 (an L{FIx} instance) and index C{j} indicate the path or polygon edge 

646 and the fraction along that edge with the C{closest} point. The 

647 C{start} and C{end} points may differ from the given path or polygon 

648 points at indices C{fi} respectively C{j}, when unrolled (C{wrap} is 

649 C{True}). Also, the C{start} and/or C{end} point may be the same 

650 instance as the C{closest} point, for example when the very first 

651 path or polygon point is the nearest. 

652 ''' 

653 _Names_ = (_closest_, _distance_, _fi_, _j_, _start_, _end_) 

654 _Units_ = (_Pass, Meter, FIx, Number_, _Pass , _Pass) 

655 

656 

657class NearestOn8Tuple(_NamedTuple): # .ellipsoidalBaseDI 

658 '''8-Tuple C{(closest, distance, fi, j, start, end, initial, final)}, 

659 like L{NearestOn6Tuple} but extended with the C{initial} and the 

660 C{final} bearing at the reference respectively the C{closest} 

661 point, both in compass C{degrees}. 

662 ''' 

663 _Names_ = NearestOn6Tuple._Names_ + Distance3Tuple._Names_[-2:] 

664 _Units_ = NearestOn6Tuple._Units_ + Distance3Tuple._Units_[-2:] 

665 

666 

667class PhiLam2Tuple(_NamedTuple): # .frechet, .hausdorff, .latlonBase, .points, .vector3d 

668 '''2-Tuple C{(phi, lam)} with latitude C{phi} and 

669 longitude C{lam}, both in C{radians}. 

670 ''' 

671 _Names_ = (_phi_, _lam_) 

672 _Units_ = ( Phi, Lam) 

673 

674 def to3Tuple(self, height, **name): 

675 '''Extend this L{PhiLam2Tuple} to a L{PhiLam3Tuple}. 

676 

677 @arg height: The height to add (C{scalar}). 

678 @kwarg name: Optional C{B{name}=NN} (C{str}), 

679 overriding this name. 

680 

681 @return: A L{PhiLam3Tuple}C{(phi, lam, height)}. 

682 

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

684 ''' 

685 return self._xtend(PhiLam3Tuple, height, **name) 

686 

687 def to4Tuple(self, height, datum): 

688 '''Extend this L{PhiLam2Tuple} to a L{PhiLam4Tuple}. 

689 

690 @arg height: The height to add (C{scalar}). 

691 @arg datum: The datum to add (C{Datum}). 

692 

693 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}. 

694 

695 @raise TypeError: If B{C{datum}} not a C{Datum}. 

696 

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

698 ''' 

699 return self.to3Tuple(height).to4Tuple(datum) 

700 

701 

702class PhiLam3Tuple(_NamedTuple): # .nvector.py, extends -2Tuple 

703 '''3-Tuple C{(phi, lam, height)} with latitude C{phi} and 

704 longitude C{lam}, both in C{radians} and C{height} in C{meter}. 

705 ''' 

706 _Names_ = PhiLam2Tuple._Names_ + (_height_,) 

707 _Units_ = PhiLam2Tuple._Units_ + (Height,) 

708 

709 def to4Tuple(self, datum, **name): 

710 '''Extend this L{PhiLam3Tuple} to a L{PhiLam4Tuple}. 

711 

712 @arg datum: The datum to add (C{Datum}). 

713 @kwarg name: Optional C{B{name}=NN} (C{str}), 

714 overriding this name. 

715 

716 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}. 

717 

718 @raise TypeError: If B{C{datum}} not a C{Datum}. 

719 ''' 

720 _xinstanceof(_MODS.datums.Datum, datum=datum) 

721 return self._xtend(PhiLam4Tuple, datum, **name) 

722 

723 

724class PhiLam4Tuple(_NamedTuple): # extends -3Tuple 

725 '''4-Tuple C{(phi, lam, height, datum)} with latitude C{phi} 

726 and longitude C{lam}, both in C{radians}, C{height} in 

727 C{meter} and C{datum} (L{Datum}). 

728 ''' 

729 _Names_ = PhiLam3Tuple._Names_ + (_datum_,) 

730 _Units_ = PhiLam3Tuple._Units_ + (_Pass,) 

731 

732 

733class PhiLamHeight3Tuple(_NamedTuple): # like LatLonHeight3Tuple 

734 '''3-Tuple C{(phi, lam, H)} with latitude C{phi} and longitude C{lam}, 

735 both in C{radians} and orthometric height C{H} in C{meter}. 

736 ''' 

737 _Names_ = PhiLam2Tuple._Names_ + (_H_,) 

738 _Units_ = PhiLam3Tuple._Units_ 

739 

740 

741class PhiLamNgeoid3Tuple(_NamedTuple): # like LatLonNgeoid3Tuple 

742 '''3-Tuple C{(phi, lam, N)} with latitude C{phi} and longitude 

743 C{lam}, both in C{radians} and geoid height C{N} in C{meter}. 

744 ''' 

745 _Names_ = PhiLam2Tuple._Names_ + (_N_,) 

746 _Units_ = PhiLam3Tuple._Units_ 

747 

748 

749class Point3Tuple(_NamedTuple): 

750 '''3-Tuple C{(x, y, ll)} in C{meter}, C{meter} and C{LatLon}. 

751 ''' 

752 _Names_ = (_x_, _y_, _elel_) 

753 _Units_ = ( Meter, Meter, _Pass) 

754 

755 

756class Points2Tuple(_NamedTuple): # .formy, .latlonBase 

757 '''2-Tuple C{(number, points)} with the C{number} of points 

758 and -possible reduced- C{list} or C{tuple} of C{points}. 

759 ''' 

760 _Names_ = (_number_, _points_) 

761 _Units_ = ( Number_, _Pass) 

762 

763 

764class RD4Tuple(_NamedTuple): # .ltp, pyrdnap 

765 '''4-Tuple C{(minRDx, minRDy, maxRDx, maxRDy)} with the region bounds' 

766 lower-left C{(minRDx, minRDy)} and upper-right C{(maxRDx, maxRDy)} 

767 corner in C{meter}, conventionaly. 

768 ''' 

769 _Names_ = ('minRDx', 'minRDy', 'maxRDx', 'maxRDy') 

770 _Units_ = ( Meter, Meter, Meter, Meter) 

771 

772 def isinside(self, RDx, RDy, eps=0): 

773 '''Are B{C{RDx}} and B{C{RDy}} both inside these C{RD} bounds? 

774 

775 @arg RDx: X coordinate (C{meter}). 

776 @arg RDy: Y coordinate (C{meter}). 

777 @kwarg eps: Over-/undersize the C{RD} bounds (C{meter}). 

778 

779 @return: C{False} if B{C{RDx}} or B{C{RDy}} is C{NAN} or 

780 outsize these C{RD} bounds, C{True} otherwise. 

781 ''' 

782 z = Meter(eps=eps) if eps else 0 

783 return _isinside(Meter(RDx=RDx), Meter(RDy=RDy), z, self) 

784 

785 def resize(self, eps): 

786 '''Get these bounds, over- or undersize by C{B{eps}}. 

787 

788 @arg eps: In- or decrease (C{degrees}). 

789 

790 @return: A L{RD4Tuple}C{(minRDx, minRDy, maxRDx, maxRDy)} 

791 with all 4 bounds resized. 

792 ''' 

793 return _resize4(self, Meter(eps=eps)) 

794 

795 

796class Reverse4Tuple(_NamedTuple, _Convergence): 

797 '''4-Tuple C{(lat, lon, gamma, scale)} with C{lat}- and 

798 C{lon}gitude in C{degrees}, meridian convergence C{gamma} 

799 at point in C{degrees} and the C{scale} of projection at 

800 point C{scalar}. 

801 ''' 

802 _Names_ = (_lat_, _lon_, _gamma_, _scale_) 

803 _Units_ = ( Lat, Lon, Degrees, Scalar) 

804 

805 

806class Triangle7Tuple(_NamedTuple): 

807 '''7-Tuple C{(A, a, B, b, C, c, area)} with interior angles C{A}, 

808 C{B} and C{C} in C{degrees}, spherical sides C{a}, C{b} and C{c} 

809 in C{meter} conventionally and the C{area} of a (spherical) 

810 triangle in I{square} C{meter} conventionally. 

811 ''' 

812 _Names_ = (_A_, _a_, _B_, _b_, _C_, _c_, _area_) 

813 _Units_ = ( Degrees, Meter, Degrees, Meter, Degrees, Meter, Meter2) 

814 

815 

816class Triangle8Tuple(_NamedTuple): 

817 '''8-Tuple C{(A, a, B, b, C, c, D, E)} with interior angles C{A}, 

818 C{B} and C{C}, spherical sides C{a}, C{b} and C{c}, the I{spherical 

819 deficit} C{D} and the I{spherical excess} C{E} of a (spherical) 

820 triangle, all in C{radians}. 

821 ''' 

822 _Names_ = (_A_, _a_, _B_, _b_, _C_, _c_, _D_, _E_) 

823 _Units_ = ( Radians, Radians, Radians, Radians, Radians, Radians, Radians, Radians) 

824 

825 

826class Trilaterate5Tuple(_NamedTuple): # .latlonBase, .nvector 

827 '''5-Tuple C{(min, minPoint, max, maxPoint, n)} with C{min} and C{max} 

828 in C{meter}, the corresponding trilaterated C{minPoint} and C{maxPoint} 

829 as C{LatLon} and the number C{n}. For area overlap, C{min} and C{max} 

830 are the smallest respectively largest overlap found. For perimeter 

831 intersection, C{min} and C{max} represent the closest respectively 

832 farthest intersection margin. Count C{n} is the total number of 

833 trilaterated overlaps or intersections found, C{0, 1, 2...6} with 

834 C{0} meaning concentric. 

835 

836 @see: The C{ellipsoidalKarney-}, C{ellipsoidalVincenty-} and 

837 C{sphericalTrigonometry.LatLon.trilaterate5} method for further 

838 details on corner cases, like concentric or single trilaterated 

839 results. 

840 ''' 

841 _Names_ = (min.__name__, 'minPoint', max.__name__, 'maxPoint', _n_) 

842 _Units_ = (Meter, _Pass, Meter, _Pass, Number_) 

843 

844 

845class UtmUps2Tuple(_NamedTuple): # .epsg.py 

846 '''2-Tuple C{(zone, hemipole)} as C{int} and C{str}, where 

847 C{zone} is C{1..60} for UTM or C{0} for UPS and C{hemipole} 

848 C{'N'|'S'} is the UTM hemisphere or the UPS pole. 

849 ''' 

850 _Names_ = (_zone_, _hemipole_) 

851 _Units_ = ( Number_, Str) 

852 

853 

854class UtmUps5Tuple(_NamedTuple): # .mgrs.py, .ups.py, .utm.py, .utmups.py 

855 '''5-Tuple C{(zone, hemipole, easting, northing, band)} as C{int}, 

856 C{str}, C{meter}, C{meter} and C{band} letter, where C{zone} is 

857 C{1..60} for UTM or C{0} for UPS, C{hemipole} C{'N'|'S'} is the UTM 

858 hemisphere or the UPS pole and C{band} is C{""} or the I{longitudinal} 

859 UTM band C{'C'|'D'|..|'W'|'X'} or I{polar} UPS band C{'A'|'B'|'Y'|'Z'}. 

860 ''' 

861 _Names_ = (_zone_, _hemipole_, _easting_, _northing_, _band_) 

862 _Units_ = ( Number_, Str, Easting, Northing, Band) 

863 

864 def __new__(cls, z, h, e, n, B, Error=None, **name): 

865 if Error is not None: 

866 e = Easting( e, Error=Error) 

867 n = Northing(n, Error=Error) 

868 return _NamedTuple.__new__(cls, z, h, e, n, B, **name) 

869 

870 

871class UtmUps8Tuple(_NamedTuple, _Convergence): # .ups, .utm, .utmups 

872 '''8-Tuple C{(zone, hemipole, easting, northing, band, datum, 

873 gamma, scale)} as C{int}, C{str}, C{meter}, C{meter}, C{band} 

874 letter, C{Datum}, C{degrees} and C{scalar}, where C{zone} is 

875 C{1..60} for UTM or C{0} for UPS, C{hemipole} C{'N'|'S'} is 

876 the UTM hemisphere or the UPS pole and C{band} is C{""} or 

877 the I{longitudinal} UTM band C{'C'|'D'|..|'W'|'X'} or 

878 I{polar} UPS band C{'A'|'B'|'Y'|'Z'}. 

879 ''' 

880 _Names_ = (_zone_, _hemipole_, _easting_, _northing_, 

881 _band_, _datum_, _gamma_, _scale_) 

882 _Units_ = ( Number_, Str, Easting, Northing, 

883 Band, _Pass, Degrees, Scalar) 

884 

885 def __new__(cls, z, h, e, n, B, d, g, s, Error=None, **name): # PYCHOK 11 args 

886 if Error is not None: 

887 e = Easting( e, Error=Error) 

888 n = Northing(n, Error=Error) 

889 g = Degrees(gamma=g, Error=Error) 

890 s = Scalar(scale=s, Error=Error) 

891 return _NamedTuple.__new__(cls, z, h, e, n, B, d, g, s, **name) 

892 

893 

894class UtmUpsLatLon5Tuple(_NamedTuple): # .ups.py, .utm.py, .utmups.py 

895 '''5-Tuple C{(zone, band, hemipole, lat, lon)} as C{int}, 

896 C{str}, C{str}, C{degrees90} and C{degrees180}, where 

897 C{zone} is C{1..60} for UTM or C{0} for UPS, C{band} is 

898 C{""} or the I{longitudinal} UTM band C{'C'|'D'|..|'W'|'X'} 

899 or I{polar} UPS band C{'A'|'B'|'Y'|'Z'} and C{hemipole} 

900 C{'N'|'S'} is the UTM hemisphere or the UPS pole. 

901 ''' 

902 _Names_ = (_zone_, _band_, _hemipole_, _lat_, _lon_) 

903 _Units_ = ( Number_, Band, Str, Lat, Lon) 

904 

905 def __new__(cls, z, B, h, lat, lon, Error=None, **name): 

906 if Error is not None: 

907 lat = Lat(lat, Error=Error) 

908 lon = Lon(lon, Error=Error) 

909 return _NamedTuple.__new__(cls, z, B, h, lat, lon, **name) 

910 

911 

912class Vector2Tuple(_NamedTuple): 

913 '''2-Tuple C{(x, y)} of (geocentric) components, each in 

914 C{meter} or the same C{units}. 

915 ''' 

916 _Names_ = (_x_, _y_) 

917 _Units_ = ( Scalar, Scalar) 

918 

919 def toCartesian(self, Cartesian, **Cartesian_kwds): 

920 '''Return this C{Vector2Tuple} as a C{Cartesian}. 

921 

922 @arg Cartesian: The C{Cartesian} class to use. 

923 @kwarg Cartesian_kwds: Optional, additional C{Cartesian} 

924 keyword arguments. 

925 

926 @return: The C{B{Cartesian}} instance with C{z=0}. 

927 ''' 

928 return _v2Cls(self.xyz, Cartesian, Cartesian_kwds) 

929 

930 def to3Tuple(self, z=INT0, **name): 

931 '''Extend this L{Vector2Tuple} to a L{Vector3Tuple}. 

932 

933 @kwarg z: The Z component add (C{scalar}). 

934 @kwarg name: Optional C{B{name}=NN} (C{str}), 

935 overriding this name. 

936 

937 @return: A L{Vector3Tuple}C{(x, y, z)}. 

938 

939 @raise ValueError: Invalid B{C{z}}. 

940 ''' 

941 return self._xtend(Vector3Tuple, z, **name) 

942 

943 @property_RO 

944 def xyz(self): 

945 '''Get X, Y and Z=0 components (C{Vector3Tuple}). 

946 ''' 

947 return Vector3Tuple(*self.xyz3) 

948 

949 @property_RO 

950 def xyz3(self): 

951 '''Get X, Y and Z=0 components as C{3-tuple}. 

952 ''' 

953 return self.x, self.y, INT0 

954 

955 

956class Vector3Tuple(_NamedTuple): 

957 '''3-Tuple C{(x, y, z)} of (geocentric) components, all in 

958 C{meter} or the same C{units}. 

959 ''' 

960 _Names_ = (_x_, _y_, _z_) 

961 _Units_ = ( Scalar, Scalar, Scalar) 

962 

963 def toCartesian(self, Cartesian, **Cartesian_kwds): 

964 '''Return this C{Vector3Tuple} as a C{Cartesian}. 

965 

966 @arg Cartesian: The C{Cartesian} class to use. 

967 @kwarg Cartesian_kwds: Optional, additional C{Cartesian} 

968 keyword arguments. 

969 

970 @return: The C{B{Cartesian}} instance. 

971 ''' 

972 return _v2Cls(self, Cartesian, Cartesian_kwds) 

973 

974 def to4Tuple(self, h=INT0, **name): 

975 '''Extend this L{Vector3Tuple} to a L{Vector4Tuple}. 

976 

977 @arg h: The height to add (C{scalar}). 

978 @kwarg name: Optional C{B{name}=NN} (C{str}), 

979 overriding this name. 

980 

981 @return: A L{Vector4Tuple}C{(x, y, z, h)}. 

982 

983 @raise ValueError: Invalid B{C{h}}. 

984 ''' 

985 return self._xtend(Vector4Tuple, h, **name) 

986 

987 @property_RO 

988 def xyz(self): 

989 '''Get X, Y and Z components (C{Vector3Tuple}). 

990 ''' 

991 return self 

992 

993 @property_RO 

994 def xyz3(self): 

995 '''Get X, Y and Z components as C{3-tuple}. 

996 ''' 

997 return tuple(self) 

998 

999 

1000class _xyzh_Tuple(_NamedTuple): # .nvector.py 

1001 '''(INTERNAL) Base 4/5-Tuple for C{Vector4Tuple}, 

1002 and C{triaxials.triaxials3.Cartesian5Tuple}. 

1003 ''' 

1004 _Names_ = (_x_, _y_, _z_, _h_) 

1005 _Units_ = ( Scalar, Scalar, Scalar, Height) 

1006 

1007 def toCartesian(self, Cartesian, **Cartesian_kwds): 

1008 '''Return this tuple as a C{Cartesian}. 

1009 

1010 @arg Cartesian: The C{Cartesian} class to use. 

1011 @kwarg Cartesian_kwds: Optional, additional C{Cartesian} 

1012 keyword arguments. 

1013 

1014 @return: The C{B{Cartesian}} instance. 

1015 ''' 

1016 return _v2Cls(self, Cartesian, Cartesian_kwds) 

1017 

1018 def to3Tuple(self): 

1019 '''Reduce this tuple to a L{Vector3Tuple}. 

1020 

1021 @return: A L{Vector3Tuple}C{(x, y, z)}. 

1022 ''' 

1023 return self.xyz 

1024 

1025 @property_RO 

1026 def xyz(self): 

1027 '''Get X, Y and Z components (L{Vector3Tuple}). 

1028 ''' 

1029 return Vector3Tuple(*self.xyz3) 

1030 

1031 @property_RO 

1032 def xyz3(self): 

1033 '''Get X, Y and Z components as C{3-tuple}. 

1034 ''' 

1035 return tuple(self[:3]) 

1036 

1037 

1038class Vector4Tuple(_xyzh_Tuple): # .nvector.py 

1039 '''4-Tuple C{(x, y, z, h)} of (geocentric) components, all 

1040 in C{meter} or the same C{units}. 

1041 ''' 

1042 _Names_ = _xyzh_Tuple._Names_ 

1043 _Units_ = _xyzh_Tuple._Units_ 

1044 

1045 

1046def _isinside(x, y, eps, bounds4): # in pyaxqg, pybelbg 

1047 '''(INTERNAL) Is C{x} and C{y} inside a bounds 4-tuple? 

1048 

1049 @return: C{False} if C{x} or C{y} is C{NAN} or outside, C{True} otherwise. 

1050 ''' 

1051 # _xinstanceof(Bounds4Tuple, LB4Tuple, RD4Tuple, bounds4=bound4) 

1052 L, B, R, T = bounds4 

1053 return ((L - x) <= eps and (x - R) <= eps and 

1054 (B - y) <= eps and (y - T) <= eps) if eps else \ 

1055 (L <= x <= R and B <= y <= T) 

1056 

1057 

1058def _resize2(lo, hi, eps): 

1059 '''(INTERNAL) Resize 2-tuple C{(lo, hi)} by C{eps}. 

1060 ''' 

1061 # assert lo <= hi 

1062 if eps: 

1063 lo -= eps 

1064 hi += eps 

1065 if lo > hi: 

1066 lo = hi = (lo + hi) * _0_5 

1067 return lo, hi 

1068 

1069 

1070def _resize4(bounds4, eps): # in pyaxqg, pybelbg 

1071 '''(INTERNAL) Resize a bounds 4-tuple by C{eps}. 

1072 ''' 

1073 # _xinstanceof(Bounds4Tuple, LB4Tuple, RD4Tuple, bounds4=bounds4) 

1074 L, B, R, T = bounds4 

1075 if eps: 

1076 L, R = _resize2(L, R, eps) 

1077 B, T = _resize2(B, T, eps) 

1078 return bounds4.classof(L, B, R, T, name=typename(bounds4.resize)) 

1079 

1080 

1081def _v2Cls(v, Cls, Cartesian_kwds): # in .vector3d 

1082 if issubclassof(Cls, _MODS.cartesianBase.CartesianBase): # _MODS.vector3d.Vector3d) 

1083 return Cls(v, **Cartesian_kwds) 

1084 raise _TypeError(Cartesian=Cls, **Cartesian_kwds) 

1085 

1086# **) MIT License 

1087# 

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

1089# 

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

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

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

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

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

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

1096# 

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

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

1099# 

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

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

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

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

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

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

1106# OTHER DEALINGS IN THE SOFTWARE.