Coverage for pygeodesy/namedTuples.py: 95%
262 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-25 15:01 -0400
« prev ^ index » next coverage.py v7.10.7, created at 2026-03-25 15:01 -0400
2# -*- coding: utf-8 -*-
4u'''Named tuples.
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'''
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
32__all__ = _ALL_LAZY.namedTuples
33__version__ = '26.03.12'
35# __DUNDER gets mangled in class
36_closest_ = 'closest'
37_destination_ = 'destination'
38_elel_ = 'll'
39_final_ = 'final'
40_fraction_ = 'fraction'
43class Bearing2Tuple(_NamedTuple):
44 '''2-Tuple C{(initial, final)} bearings, both in compass C{degrees360}.
45 '''
46 _Names_ = (_initial_, _final_)
47 _Units_ = ( Bearing, Bearing)
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)
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)
66 def enclosures(self, S_other, *W_N_E):
67 '''Get the enclosures of this around an other L{Bounds4Tuple}.
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}}.
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
83 def overlap(self, S_other, *W_N_E):
84 '''Intersect this with an other L{Bounds4Tuple}.
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}}.
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))
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}.
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.
111 @see: Class L{Ellipse5Tuple}.
112 '''
113 _Names_ = (_radius_, _height_, _lat_, _beta_)
114 _Units_ = ( Radius, Height, Lat, Lat)
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
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)
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)
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)
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)
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}}.
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)
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)
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)
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[.]
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}.
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.
204 @see: Class L{Circle4Tuple}.
205 '''
206 _Names_ = (_a_, _b_, _height_, _lat_, _beta_)
207 _Units_ = ( Radius, Radius, Height, Lat, Lat)
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
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)))
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}.
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'
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}.
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'
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)
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.
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.
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)
266class LatLon2Tuple(_NamedTuple):
267 '''2-Tuple C{(lat, lon)} in C{degrees90} and C{degrees180}.
268 '''
269 _Names_ = (_lat_, _lon_)
270 _Units_ = ( Lat, Lon)
272 def to3Tuple(self, height, **name):
273 '''Extend this L{LatLon2Tuple} to a L{LatLon3Tuple}.
275 @arg height: The height to add (C{scalar}).
276 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding
277 this name.
279 @return: A L{LatLon3Tuple}C{(lat, lon, height)}.
281 @raise ValueError: Invalid B{C{height}}.
282 '''
283 return self._xtend(LatLon3Tuple, height, **name)
285 def to4Tuple(self, height, datum, **name):
286 '''Extend this L{LatLon2Tuple} to a L{LatLon4Tuple}.
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.
293 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
295 @raise TypeError: If B{C{datum}} not a C{Datum}.
297 @raise ValueError: Invalid B{C{height}}.
298 '''
299 return self.to3Tuple(height).to4Tuple(datum, **name)
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)
309 def to4Tuple(self, datum, **name):
310 '''Extend this L{LatLon3Tuple} to a L{LatLon4Tuple}.
312 @arg datum: The datum to add (C{Datum}).
313 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding
314 this name.
316 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
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)
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)
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
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)
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)
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_)
375 def to5Tuple(self, height, radius, **name):
376 '''Extend this L{LatLonPrec3Tuple} to a L{LatLonPrec5Tuple}.
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.
383 @return: A L{LatLonPrec5Tuple}C{(lat, lon, precision,
384 height, radius)}.
385 '''
386 return self._xtend(LatLonPrec5Tuple, height, radius, **name)
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)
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
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
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)
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)
450# NearestOn4Tuple DEPRECATED, see .deprecated.classes.NearestOn4Tuple
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)
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)
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:]
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]}.
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)
503 def to3Tuple(self, height, **name):
504 '''Extend this L{PhiLam2Tuple} to a L{PhiLam3Tuple}.
506 @arg height: The height to add (C{scalar}).
507 @kwarg name: Optional C{B{name}=NN} (C{str}),
508 overriding this name.
510 @return: A L{PhiLam3Tuple}C{(phi, lam, height)}.
512 @raise ValueError: Invalid B{C{height}}.
513 '''
514 return self._xtend(PhiLam3Tuple, height, **name)
516 def to4Tuple(self, height, datum):
517 '''Extend this L{PhiLam2Tuple} to a L{PhiLam4Tuple}.
519 @arg height: The height to add (C{scalar}).
520 @arg datum: The datum to add (C{Datum}).
522 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
524 @raise TypeError: If B{C{datum}} not a C{Datum}.
526 @raise ValueError: Invalid B{C{height}}.
527 '''
528 return self.to3Tuple(height).to4Tuple(datum)
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}.
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)
543 def to4Tuple(self, datum, **name):
544 '''Extend this L{PhiLam3Tuple} to a L{PhiLam4Tuple}.
546 @arg datum: The datum to add (C{Datum}).
547 @kwarg name: Optional C{B{name}=NN} (C{str}),
548 overriding this name.
550 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
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)
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}.
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)
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)
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)
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)
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)
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)
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.
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_)
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)
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)
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)
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)
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)
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)
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)
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)
709 def toCartesian(self, Cartesian, **Cartesian_kwds):
710 '''Return this C{Vector2Tuple} as a C{Cartesian}.
712 @arg Cartesian: The C{Cartesian} class to use.
713 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
714 keyword arguments.
716 @return: The C{B{Cartesian}} instance with C{z=0}.
717 '''
718 return _v2Cls(self.xyz, Cartesian, Cartesian_kwds)
720 def to3Tuple(self, z=INT0, **name):
721 '''Extend this L{Vector2Tuple} to a L{Vector3Tuple}.
723 @kwarg z: The Z component add (C{scalar}).
724 @kwarg name: Optional C{B{name}=NN} (C{str}),
725 overriding this name.
727 @return: A L{Vector3Tuple}C{(x, y, z)}.
729 @raise ValueError: Invalid B{C{z}}.
730 '''
731 return self._xtend(Vector3Tuple, z, **name)
733 @property_RO
734 def xyz(self):
735 '''Get X, Y and Z=0 components (C{Vector3Tuple}).
736 '''
737 return Vector3Tuple(*self.xyz3)
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
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)
753 def toCartesian(self, Cartesian, **Cartesian_kwds):
754 '''Return this C{Vector3Tuple} as a C{Cartesian}.
756 @arg Cartesian: The C{Cartesian} class to use.
757 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
758 keyword arguments.
760 @return: The C{B{Cartesian}} instance.
761 '''
762 return _v2Cls(self, Cartesian, Cartesian_kwds)
764 def to4Tuple(self, h=INT0, **name):
765 '''Extend this L{Vector3Tuple} to a L{Vector4Tuple}.
767 @arg h: The height to add (C{scalar}).
768 @kwarg name: Optional C{B{name}=NN} (C{str}),
769 overriding this name.
771 @return: A L{Vector4Tuple}C{(x, y, z, h)}.
773 @raise ValueError: Invalid B{C{h}}.
774 '''
775 return self._xtend(Vector4Tuple, h, **name)
777 @property_RO
778 def xyz(self):
779 '''Get X, Y and Z components (C{Vector3Tuple}).
780 '''
781 return self
783 @property_RO
784 def xyz3(self):
785 '''Get X, Y and Z components as C{3-tuple}.
786 '''
787 return tuple(self)
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)
797 def toCartesian(self, Cartesian, **Cartesian_kwds):
798 '''Return this C{Vector4Tuple} as a C{Cartesian}.
800 @arg Cartesian: The C{Cartesian} class to use.
801 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
802 keyword arguments.
804 @return: The C{B{Cartesian}} instance.
805 '''
806 return _v2Cls(self, Cartesian, Cartesian_kwds)
808 def to3Tuple(self):
809 '''Reduce this L{Vector4Tuple} to a L{Vector3Tuple}.
811 @return: A L{Vector3Tuple}C{(x, y, z)}.
812 '''
813 return self.xyz
815 @property_RO
816 def xyz(self):
817 '''Get X, Y and Z components (L{Vector3Tuple}).
818 '''
819 return Vector3Tuple(*self.xyz)
821 @property_RO
822 def xyz3(self):
823 '''Get X, Y and Z components as C{3-tuple}.
824 '''
825 return tuple(self[:3])
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)
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.