Coverage for pygeodesy/namedTuples.py: 95%

262 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-03-25 15:01 -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, fabs 

14# from pygeodesy.dms import toDMS # _MODS 

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

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

17 _band_, _beta_, _c_, _C_, _D_, _datum_, _distance_, \ 

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

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

20 _n_, _northing_, _number_, _outside_, _phi_, \ 

21 _point_, _precision_, _points_, _radius_, _scale_, \ 

22 _start_, _x_, _y_, _z_, _zone_ 

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

24from pygeodesy.named import _NamedTuple, _Pass, _ALL_LAZY, _MODS 

25from pygeodesy.props import deprecated_property_RO, property_RO 

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

27 Height, Int, Lam, Lat, Lon, Meter, Meter2, \ 

28 Northing, Number_, Phi, Precision_, Radians, \ 

29 Radius, Scalar, Str 

30# from math import fabs # from .constants 

31 

32__all__ = _ALL_LAZY.namedTuples 

33__version__ = '26.03.12' 

34 

35# __DUNDER gets mangled in class 

36_closest_ = 'closest' 

37_destination_ = 'destination' 

38_elel_ = 'll' 

39_final_ = 'final' 

40_fraction_ = 'fraction' 

41 

42 

43class Bearing2Tuple(_NamedTuple): 

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

45 ''' 

46 _Names_ = (_initial_, _final_) 

47 _Units_ = ( Bearing, Bearing) 

48 

49 

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

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

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

53 ''' 

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

55 _Units_ = (_Pass, _Pass) 

56 

57 

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

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

60 C{(LatS, LowW)} and upper-right C{(latN, lonE)} corner lat- and 

61 longitudes. 

62 ''' 

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

64 _Units_ = ( Lat, Lon, Lat, Lon) 

65 

66 def enclosures(self, S_other, *W_N_E): 

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

68 

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

70 L{Bounds4Tuple} instance. 

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

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

73 

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

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

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

77 if not or zero if abutting. 

78 ''' 

79 s, w, n, e = self 

80 S, W, N, E = map1(float, S_other, *W_N_E) if W_N_E else S_other 

81 return Bounds4Tuple(map1(float, S - s, W - w, n - N, e - E)) # *map1 

82 

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

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

85 

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

87 L{Bounds4Tuple} instance. 

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

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

90 

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

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

93 ''' 

94 s, w, n, e = self 

95 S, W, N, E = map1(float, S_other, *W_N_E) if W_N_E else S_other 

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

97 Bounds4Tuple(max(s, S), max(w, W), min(n, N), min(e, E)) 

98 

99 

100class Circle4Tuple(_NamedTuple): 

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

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

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

104 ellipsoid}. 

105 

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

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

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

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

110 

111 @see: Class L{Ellipse5Tuple}. 

112 ''' 

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

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

115 

116 @property_RO 

117 def abc3(self): 

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

119 ''' 

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

121 

122 

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

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

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

126 ''' 

127 _Names_ = (_destination_, _final_) 

128 _Units_ = (_Pass, Bearing) 

129 

130 

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

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

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

134 in compass C{degrees360}. 

135 ''' 

136 _Names_ = (_lat_, _lon_, _final_) 

137 _Units_ = ( Lat, Lon, Bearing) 

138 

139 

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

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

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

143 ''' 

144 _Names_ = (_distance_, _initial_) 

145 _Units_ = ( Meter, Bearing) 

146 

147 

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

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

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

151 ''' 

152 _Names_ = (_distance_, _initial_, _final_) 

153 _Units_ = ( Meter, Bearing, Bearing) 

154 

155 

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

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

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

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

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

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

162 

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

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

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

166 ''' 

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

168 _Units_ = ( Degrees2, Degrees, Degrees, Degrees) 

169 

170 

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

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

173 ''' 

174 _Names_ = (_easting_, _northing_) 

175 _Units_ = ( Easting, Northing) 

176 

177 

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

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

180 ''' 

181 _Names_ = (_easting_, _northing_, _height_) 

182 _Units_ = ( Easting, Northing, Height) 

183 

184 

185class _Convergence(object): 

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

187 @deprecated_property_RO 

188 def convergence(self): 

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

190 ''' 

191 return self.gamma # PYCHOK self[.] 

192 

193 

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

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

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

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

198 

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

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

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

202 are in C{degrees90}, always. 

203 

204 @see: Class L{Circle4Tuple}. 

205 ''' 

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

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

208 

209 @property_RO 

210 def abc3(self): 

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

212 ''' 

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

214 

215 @property_RO 

216 def abc3ordered(self): 

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

218 ''' 

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

220 

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

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

223 

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

225 C{Triaxial} keyword arguments. 

226 ''' 

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

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

229 

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

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

232 

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

234 C{Triaxial_} keyword arguments. 

235 ''' 

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

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

238 

239 

240class Forward4Tuple(_NamedTuple, _Convergence): 

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

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

243 point C{scalar}. 

244 ''' 

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

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

247 

248 

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

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

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

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

253 

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

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

256 

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

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

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

260 in the direction of the bearing. 

261 ''' 

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

263 _Units_ = (_Pass, Int, Int) 

264 

265 

266class LatLon2Tuple(_NamedTuple): 

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

268 ''' 

269 _Names_ = (_lat_, _lon_) 

270 _Units_ = ( Lat, Lon) 

271 

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

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

274 

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

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

277 this name. 

278 

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

280 

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

282 ''' 

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

284 

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

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

287 

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

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

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

291 this name. 

292 

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

294 

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

296 

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

298 ''' 

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

300 

301 

302class LatLon3Tuple(_NamedTuple): 

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

304 and C{meter}, conventionally. 

305 ''' 

306 _Names_ = (_lat_, _lon_, _height_) 

307 _Units_ = ( Lat, Lon, Height) 

308 

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

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

311 

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

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

314 this name. 

315 

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

317 

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

319 ''' 

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

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

322 

323 

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

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

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

327 ''' 

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

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

330 

331 

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

333 iteration=None, **name): 

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

335 ''' 

336 if LatLon is None: # ignore LatLon_kwds 

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

338 else: 

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

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

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

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

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

344 if LatLon_kwds: 

345 kwds.update(LatLon_kwds) 

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

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

348 r._iteration = iteration 

349 return r 

350 

351 

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

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

354 and L{Datum}. 

355 ''' 

356 _Names_ = (_lat_, _lon_, _datum_) 

357 _Units_ = ( Lat, Lon, _Pass) 

358 

359 

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

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

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

363 ''' 

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

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

366 

367 

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

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

370 and C{int}. 

371 ''' 

372 _Names_ = (_lat_, _lon_, _precision_) 

373 _Units_ = ( Lat, Lon, Precision_) 

374 

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

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

377 

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

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

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

381 this name. 

382 

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

384 height, radius)}. 

385 ''' 

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

387 

388 

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

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

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

392 C{None} if missing). 

393 ''' 

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

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

396 

397 

398class _NamedTupleTo(_NamedTuple): # in .testNamedTuples 

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

400 ''' 

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

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

403 ''' 

404 if toDMS_kwds: 

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

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

407 else: 

408 toDMS, s = None, self 

409 for x in xs: 

410 if not isinstanceof(x, Degrees): 

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

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

413 yield s 

414 

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

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

417 ''' 

418 s = self 

419 for x in xs: 

420 if not isinstanceof(x, Radians): 

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

422 yield x 

423 yield s 

424 

425 

426class NearestOn2Tuple(_NamedTuple): # .ellipsoidalBaseDI 

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

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

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

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

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

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

433 the closest point is after the second point. 

434 ''' 

435 _Names_ = (_closest_, _fraction_) 

436 _Units_ = (_Pass, _Pass) 

437 

438 

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

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

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

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

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

444 respectively compass C{degrees360}. 

445 ''' 

446 _Names_ = (_closest_, _distance_, _angle_) 

447 _Units_ = (_Pass, Meter, Degrees) 

448 

449 

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

451 

452 

453class NearestOn5Tuple(_NamedTuple): 

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

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

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

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

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

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

460 or C{0}. 

461 ''' 

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

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

464 

465 

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

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

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

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

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

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

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

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

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

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

476 path or polygon point is the nearest. 

477 ''' 

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

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

480 

481 

482class NearestOn8Tuple(_NamedTuple): # .ellipsoidalBaseDI 

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

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

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

486 point, both in compass C{degrees}. 

487 ''' 

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

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

490 

491 

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

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

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

495 

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

497 follows Chris Veness' U{convention 

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

499 ''' 

500 _Names_ = (_phi_, _lam_) 

501 _Units_ = ( Phi, Lam) 

502 

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

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

505 

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

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

508 overriding this name. 

509 

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

511 

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

513 ''' 

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

515 

516 def to4Tuple(self, height, datum): 

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

518 

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

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

521 

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

523 

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

525 

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

527 ''' 

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

529 

530 

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

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

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

534 C{height} in C{meter}. 

535 

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

537 follows Chris Veness' U{convention 

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

539 ''' 

540 _Names_ = (_phi_, _lam_, _height_) 

541 _Units_ = ( Phi, Lam, Height) 

542 

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

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

545 

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

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

548 overriding this name. 

549 

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

551 

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

553 ''' 

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

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

556 

557 

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

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

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

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

562 

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

564 follows Chris Veness' U{convention 

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

566 ''' 

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

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

569 

570 

571class Point3Tuple(_NamedTuple): 

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

573 ''' 

574 _Names_ = (_x_, _y_, _elel_) 

575 _Units_ = ( Meter, Meter, _Pass) 

576 

577 

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

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

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

581 ''' 

582 _Names_ = (_number_, _points_) 

583 _Units_ = ( Number_, _Pass) 

584 

585 

586class Reverse4Tuple(_NamedTuple, _Convergence): 

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

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

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

590 point C{scalar}. 

591 ''' 

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

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

594 

595 

596class Triangle7Tuple(_NamedTuple): 

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

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

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

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

601 ''' 

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

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

604 

605 

606class Triangle8Tuple(_NamedTuple): 

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

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

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

610 triangle, all in C{radians}. 

611 ''' 

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

613 _Units_ = ( Radians, Radians, Radians, Radians, Radians, Radians, Radians, Radians) 

614 

615 

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

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

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

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

620 are the smallest respectively largest overlap found. For perimeter 

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

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

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

624 C{0} meaning concentric. 

625 

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

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

628 details on corner cases, like concentric or single trilaterated 

629 results. 

630 ''' 

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

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

633 

634 

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

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

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

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

639 ''' 

640 _Names_ = (_zone_, _hemipole_) 

641 _Units_ = ( Number_, Str) 

642 

643 

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

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

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

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

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

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

650 ''' 

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

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

653 

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

655 if Error is not None: 

656 e = Easting( e, Error=Error) 

657 n = Northing(n, Error=Error) 

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

659 

660 

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

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

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

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

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

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

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

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

669 ''' 

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

671 _band_, _datum_, _gamma_, _scale_) 

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

673 Band, _Pass, Degrees, Scalar) 

674 

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

676 if Error is not None: 

677 e = Easting( e, Error=Error) 

678 n = Northing(n, Error=Error) 

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

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

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

682 

683 

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

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

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

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

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

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

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

691 ''' 

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

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

694 

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

696 if Error is not None: 

697 lat = Lat(lat, Error=Error) 

698 lon = Lon(lon, Error=Error) 

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

700 

701 

702class Vector2Tuple(_NamedTuple): 

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

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

705 ''' 

706 _Names_ = (_x_, _y_) 

707 _Units_ = ( Scalar, Scalar) 

708 

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

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

711 

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

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

714 keyword arguments. 

715 

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

717 ''' 

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

719 

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

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

722 

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

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

725 overriding this name. 

726 

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

728 

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

730 ''' 

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

732 

733 @property_RO 

734 def xyz(self): 

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

736 ''' 

737 return Vector3Tuple(*self.xyz3) 

738 

739 @property_RO 

740 def xyz3(self): 

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

742 ''' 

743 return self.x, self.y, INT0 

744 

745 

746class Vector3Tuple(_NamedTuple): 

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

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

749 ''' 

750 _Names_ = (_x_, _y_, _z_) 

751 _Units_ = ( Scalar, Scalar, Scalar) 

752 

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

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

755 

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

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

758 keyword arguments. 

759 

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

761 ''' 

762 return _v2Cls(self, Cartesian, Cartesian_kwds) 

763 

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

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

766 

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

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

769 overriding this name. 

770 

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

772 

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

774 ''' 

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

776 

777 @property_RO 

778 def xyz(self): 

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

780 ''' 

781 return self 

782 

783 @property_RO 

784 def xyz3(self): 

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

786 ''' 

787 return tuple(self) 

788 

789 

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

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

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

793 ''' 

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

795 _Units_ = ( Scalar, Scalar, Scalar, Height) 

796 

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

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

799 

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

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

802 keyword arguments. 

803 

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

805 ''' 

806 return _v2Cls(self, Cartesian, Cartesian_kwds) 

807 

808 def to3Tuple(self): 

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

810 

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

812 ''' 

813 return self.xyz 

814 

815 @property_RO 

816 def xyz(self): 

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

818 ''' 

819 return Vector3Tuple(*self.xyz) 

820 

821 @property_RO 

822 def xyz3(self): 

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

824 ''' 

825 return tuple(self[:3]) 

826 

827 

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

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

830 return Cls(v, **Cartesian_kwds) 

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

832 

833# **) MIT License 

834# 

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

836# 

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

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

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

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

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

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

843# 

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

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

846# 

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

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

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

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

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

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

853# OTHER DEALINGS IN THE SOFTWARE.