Coverage for pygeodesy / namedTuples.py: 89%

303 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-07-18 13:43 -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, _0_5, 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_, \ 

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

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

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, Lat, Lon, Meter, Meter2, \ 

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

30 Radius, Scalar, Str 

31# from math import fabs # from .constants 

32 

33__all__ = _ALL_LAZY.namedTuples 

34__version__ = '26.07.09' 

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, LowW)} 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 enclosures(self, S_other, *W_N_E): 

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

69 

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

71 L{Bounds4Tuple} instance. 

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

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

74 

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

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

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

78 if not or zero if abutting. 

79 ''' 

80 s, w, n, e, \ 

81 S, W, N, E = self._plus_other8(S_other, W_N_E) 

82 return self.classof(map1(float, S - s, W - w, n - N, e - E), # *map1 

83 name=typename(Bounds4Tuple.enclosures)) 

84 

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

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

87 

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

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

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

91 

92 @return: C{None} if B{C{lat}} or B{C{lon}} is NAN, C{False} 

93 if outside these bounds, C{True} otherwise. 

94 ''' 

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

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

97 

98 @Property_RO 

99 def latC(self): 

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

101 ''' 

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

103 

104 @Property_RO 

105 def latZ(self): 

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

107 ''' 

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

109 

110 @Property_RO 

111 def lonC(self): 

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

113 ''' 

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

115 

116 @Property_RO 

117 def lonZ(self): 

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

119 ''' 

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

121 

122 def overlap(self, S_other, *W_N_E): 

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

124 

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

126 L{Bounds4Tuple} instance. 

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

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

129 

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

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

132 ''' 

133 s, w, n, e, \ 

134 S, W, N, E = self._plus_other8(S_other, W_N_E) 

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

136 self.classof(max(s, S), max(w, W), min(n, N), min(e, E), 

137 name=typename(Bounds4Tuple.overlap)) 

138 

139 def _plus_other8(self, S_other, W_N_E): 

140 # return this (s, w, n, e) + other (S, W, N, E) 

141 if W_N_E: 

142 S_other = map1(float, S_other, *W_N_E) 

143 else: 

144 _xinstanceof(Bounds4Tuple, S_other=S_other) 

145 return self + S_other 

146 

147 def resize(self, eps): 

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

149 

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

151 

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

153 with all 4 bounds resized. 

154 ''' 

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

156 

157 

158class Circle4Tuple(_NamedTuple): 

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

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

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

162 ellipsoid}. 

163 

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

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

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

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

168 

169 @see: Class L{Ellipse5Tuple}. 

170 ''' 

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

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

173 

174 @property_RO 

175 def abc3(self): 

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

177 ''' 

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

179 

180 

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

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

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

184 ''' 

185 _Names_ = (_destination_, _final_) 

186 _Units_ = (_Pass, Bearing) 

187 

188 

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

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

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

192 in compass C{degrees360}. 

193 ''' 

194 _Names_ = (_lat_, _lon_, _final_) 

195 _Units_ = ( Lat, Lon, Bearing) 

196 

197 

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

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

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

201 ''' 

202 _Names_ = (_distance_, _initial_) 

203 _Units_ = ( Meter, Bearing) 

204 

205 

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

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

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

209 ''' 

210 _Names_ = (_distance_, _initial_, _final_) 

211 _Units_ = ( Meter, Bearing, Bearing) 

212 

213 

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

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

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

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

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

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

220 

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

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

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

224 ''' 

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

226 _Units_ = ( Degrees2, Degrees, Degrees, Degrees) 

227 

228 

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

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

231 ''' 

232 _Names_ = (_easting_, _northing_) 

233 _Units_ = ( Easting, Northing) 

234 

235 

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

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

238 ''' 

239 _Names_ = (_easting_, _northing_, _height_) 

240 _Units_ = ( Easting, Northing, Height) 

241 

242 

243class _Convergence(object): 

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

245 @deprecated_property_RO 

246 def convergence(self): 

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

248 ''' 

249 return self.gamma # PYCHOK self[.] 

250 

251 

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

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

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

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

256 

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

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

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

260 are in C{degrees90}, always. 

261 

262 @see: Class L{Circle4Tuple}. 

263 ''' 

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

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

266 

267 @property_RO 

268 def abc3(self): 

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

270 ''' 

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

272 

273 @property_RO 

274 def abc3ordered(self): 

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

276 ''' 

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

278 

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

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

281 

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

283 C{Triaxial} keyword arguments. 

284 ''' 

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

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

287 

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

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

290 

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

292 C{Triaxial_} keyword arguments. 

293 ''' 

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

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

296 

297 

298class Forward4Tuple(_NamedTuple, _Convergence): 

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

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

301 point C{scalar}. 

302 ''' 

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

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

305 

306 

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

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

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

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

311 

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

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

314 

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

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

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

318 in the direction of the bearing. 

319 ''' 

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

321 _Units_ = (_Pass, Int, Int) 

322 

323 

324class LatLon2Tuple(_NamedTuple): 

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

326 ''' 

327 _Names_ = (_lat_, _lon_) 

328 _Units_ = ( Lat, Lon) 

329 

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

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

332 

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

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

335 this name. 

336 

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

338 

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

340 ''' 

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

342 

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

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

345 

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

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

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

349 this name. 

350 

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

352 

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

354 

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

356 ''' 

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

358 

359 

360class LatLon3Tuple(_NamedTuple): 

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

362 and C{meter}, conventionally. 

363 ''' 

364 _Names_ = (_lat_, _lon_, _height_) 

365 _Units_ = ( Lat, Lon, Height) 

366 

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

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

369 

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

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

372 this name. 

373 

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

375 

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

377 ''' 

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

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

380 

381 

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

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

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

385 ''' 

386 _Names_ = (_lat_, _lon_, _height_, _datum_) 

387 _Units_ = ( Lat, Lon, Height, _Pass) 

388 

389 

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

391 iteration=None, **name): 

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

393 ''' 

394 if LatLon is None: # ignore LatLon_kwds 

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

396 else: 

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

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

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

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

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

402 if LatLon_kwds: 

403 kwds.update(LatLon_kwds) 

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

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

406 r._iteration = iteration 

407 return r 

408 

409 

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

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

412 and L{Datum}. 

413 ''' 

414 _Names_ = (_lat_, _lon_, _datum_) 

415 _Units_ = ( Lat, Lon, _Pass) 

416 

417 

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

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

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

421 ''' 

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

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

424 

425 

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

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

428 and C{int}. 

429 ''' 

430 _Names_ = (_lat_, _lon_, _precision_) 

431 _Units_ = ( Lat, Lon, Precision_) 

432 

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

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

435 

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

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

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

439 this name. 

440 

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

442 height, radius)}. 

443 ''' 

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

445 

446 

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

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

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

450 C{None} if missing). 

451 ''' 

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

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

454 

455 

456class _NamedTupleTo(_NamedTuple): # in .testNamedTuples 

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

458 ''' 

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

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

461 ''' 

462 if toDMS_kwds: 

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

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

465 else: 

466 toDMS, s = None, self 

467 for x in xs: 

468 if not isinstanceof(x, Degrees): 

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

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

471 yield s 

472 

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

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

475 ''' 

476 s = self 

477 for x in xs: 

478 if not isinstanceof(x, Radians): 

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

480 yield x 

481 yield s 

482 

483 

484class NearestOn2Tuple(_NamedTuple): # .ellipsoidalBaseDI 

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

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

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

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

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

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

491 the closest point is after the second point. 

492 ''' 

493 _Names_ = (_closest_, _fraction_) 

494 _Units_ = (_Pass, _Pass) 

495 

496 

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

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

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

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

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

502 respectively compass C{degrees360}. 

503 ''' 

504 _Names_ = (_closest_, _distance_, _angle_) 

505 _Units_ = (_Pass, Meter, Degrees) 

506 

507 

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

509 

510 

511class NearestOn5Tuple(_NamedTuple): 

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

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

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

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

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

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

518 or C{0}. 

519 ''' 

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

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

522 

523 

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

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

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

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

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

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

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

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

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

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

534 path or polygon point is the nearest. 

535 ''' 

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

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

538 

539 

540class NearestOn8Tuple(_NamedTuple): # .ellipsoidalBaseDI 

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

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

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

544 point, both in compass C{degrees}. 

545 ''' 

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

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

548 

549 

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

551 '''2-Tuple C{(phi, lam)} with latitude C{phi} in C{radians[PI_2]} 

552 and longitude C{lam} in C{radians[PI]}. 

553 

554 @note: Using C{phi/lambda} for lat-/longitude in C{radians} 

555 follows Chris Veness' U{convention 

556 <https://www.Movable-Type.co.UK/scripts/latlong.html>}. 

557 ''' 

558 _Names_ = (_phi_, _lam_) 

559 _Units_ = ( Phi, Lam) 

560 

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

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

563 

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

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

566 overriding this name. 

567 

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

569 

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

571 ''' 

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

573 

574 def to4Tuple(self, height, datum): 

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

576 

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

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

579 

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

581 

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

583 

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

585 ''' 

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

587 

588 

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

590 '''3-Tuple C{(phi, lam, height)} with latitude C{phi} in 

591 C{radians[PI_2]}, longitude C{lam} in C{radians[PI]} and 

592 C{height} in C{meter}. 

593 

594 @note: Using C{phi/lambda} for lat-/longitude in C{radians} 

595 follows Chris Veness' U{convention 

596 <https://www.Movable-Type.co.UK/scripts/latlong.html>}. 

597 ''' 

598 _Names_ = (_phi_, _lam_, _height_) 

599 _Units_ = ( Phi, Lam, Height) 

600 

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

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

603 

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

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

606 overriding this name. 

607 

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

609 

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

611 ''' 

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

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

614 

615 

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

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

618 C{radians[PI_2]}, longitude C{lam} in C{radians[PI]}, C{height} 

619 in C{meter} and L{Datum}. 

620 

621 @note: Using C{phi/lambda} for lat-/longitude in C{radians} 

622 follows Chris Veness' U{convention 

623 <https://www.Movable-Type.co.UK/scripts/latlong.html>}. 

624 ''' 

625 _Names_ = (_phi_, _lam_, _height_, _datum_) 

626 _Units_ = ( Phi, Lam, Height, _Pass) 

627 

628 

629class Point3Tuple(_NamedTuple): 

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

631 ''' 

632 _Names_ = (_x_, _y_, _elel_) 

633 _Units_ = ( Meter, Meter, _Pass) 

634 

635 

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

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

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

639 ''' 

640 _Names_ = (_number_, _points_) 

641 _Units_ = ( Number_, _Pass) 

642 

643 

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

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

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

647 corner in C{meter}, conventionaly. 

648 ''' 

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

650 _Units_ = ( Meter, Meter, Meter, Meter) 

651 

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

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

654 

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

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

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

658 

659 @return: C{False} if B{C{RDx}} or B{C{RDy}} is outsize 

660 these C{RD} bounds or C{NAN}, C{True} otherwise. 

661 ''' 

662 return _isinside(Meter(RDx=RDx), Meter(RDy=RDy), 

663 Meter(eps=eps) if eps else 0, self) 

664 

665 def resize(self, eps): 

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

667 

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

669 

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

671 with all 4 bounds resized. 

672 ''' 

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

674 

675 

676class Reverse4Tuple(_NamedTuple, _Convergence): 

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

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

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

680 point C{scalar}. 

681 ''' 

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

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

684 

685 

686class Triangle7Tuple(_NamedTuple): 

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

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

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

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

691 ''' 

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

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

694 

695 

696class Triangle8Tuple(_NamedTuple): 

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

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

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

700 triangle, all in C{radians}. 

701 ''' 

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

703 _Units_ = ( Radians, Radians, Radians, Radians, Radians, Radians, Radians, Radians) 

704 

705 

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

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

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

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

710 are the smallest respectively largest overlap found. For perimeter 

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

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

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

714 C{0} meaning concentric. 

715 

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

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

718 details on corner cases, like concentric or single trilaterated 

719 results. 

720 ''' 

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

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

723 

724 

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

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

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

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

729 ''' 

730 _Names_ = (_zone_, _hemipole_) 

731 _Units_ = ( Number_, Str) 

732 

733 

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

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

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

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

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

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

740 ''' 

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

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

743 

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

745 if Error is not None: 

746 e = Easting( e, Error=Error) 

747 n = Northing(n, Error=Error) 

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

749 

750 

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

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

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

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

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

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

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

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

759 ''' 

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

761 _band_, _datum_, _gamma_, _scale_) 

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

763 Band, _Pass, Degrees, Scalar) 

764 

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

766 if Error is not None: 

767 e = Easting( e, Error=Error) 

768 n = Northing(n, Error=Error) 

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

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

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

772 

773 

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

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

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

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

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

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

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

781 ''' 

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

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

784 

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

786 if Error is not None: 

787 lat = Lat(lat, Error=Error) 

788 lon = Lon(lon, Error=Error) 

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

790 

791 

792class Vector2Tuple(_NamedTuple): 

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

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

795 ''' 

796 _Names_ = (_x_, _y_) 

797 _Units_ = ( Scalar, Scalar) 

798 

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

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

801 

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

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

804 keyword arguments. 

805 

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

807 ''' 

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

809 

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

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

812 

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

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

815 overriding this name. 

816 

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

818 

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

820 ''' 

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

822 

823 @property_RO 

824 def xyz(self): 

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

826 ''' 

827 return Vector3Tuple(*self.xyz3) 

828 

829 @property_RO 

830 def xyz3(self): 

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

832 ''' 

833 return self.x, self.y, INT0 

834 

835 

836class Vector3Tuple(_NamedTuple): 

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

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

839 ''' 

840 _Names_ = (_x_, _y_, _z_) 

841 _Units_ = ( Scalar, Scalar, Scalar) 

842 

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

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

845 

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

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

848 keyword arguments. 

849 

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

851 ''' 

852 return _v2Cls(self, Cartesian, Cartesian_kwds) 

853 

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

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

856 

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

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

859 overriding this name. 

860 

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

862 

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

864 ''' 

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

866 

867 @property_RO 

868 def xyz(self): 

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

870 ''' 

871 return self 

872 

873 @property_RO 

874 def xyz3(self): 

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

876 ''' 

877 return tuple(self) 

878 

879 

880class Vector4Tuple(_NamedTuple): # .nvector.py 

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

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

883 ''' 

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

885 _Units_ = ( Scalar, Scalar, Scalar, Height) 

886 

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

888 '''Return this C{Vector4Tuple} as a C{Cartesian}. 

889 

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

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

892 keyword arguments. 

893 

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

895 ''' 

896 return _v2Cls(self, Cartesian, Cartesian_kwds) 

897 

898 def to3Tuple(self): 

899 '''Reduce this L{Vector4Tuple} to a L{Vector3Tuple}. 

900 

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

902 ''' 

903 return self.xyz 

904 

905 @property_RO 

906 def xyz(self): 

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

908 ''' 

909 return Vector3Tuple(*self.xyz) 

910 

911 @property_RO 

912 def xyz3(self): 

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

914 ''' 

915 return tuple(self[:3]) 

916 

917 

918def _isinside(x, y, eps, bounds4): 

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

920 ''' 

921 # _xinstanceof(Bounds4Tuple, RD4Tuple, bounds4=bound4) 

922 L, B, R, T = bounds4 

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

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

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

926 

927 

928def _resize4(bounds4, eps): 

929 '''(INTERNAL) Resize a bounds 4-tuple. 

930 ''' 

931 # _xinstanceof(Bounds4Tuple, RD4Tuple, bounds4=bounds4) 

932 L, B, R, T = bounds4 

933 if eps: 

934 L -= eps 

935 B -= eps 

936 R += eps 

937 T += eps 

938 if L > R: 

939 L = R = (L + R) * _0_5 

940 if B > T: 

941 B = T = (B + T) * _0_5 

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

943 

944 

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

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

947 return Cls(v, **Cartesian_kwds) 

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

949 

950# **) MIT License 

951# 

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

953# 

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

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

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

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

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

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

960# 

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

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

963# 

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

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

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

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

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

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

970# OTHER DEALINGS IN THE SOFTWARE.