Coverage for pygeodesy / namedTuples.py: 89%
303 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-18 13:43 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-07-18 13:43 -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, _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
33__all__ = _ALL_LAZY.namedTuples
34__version__ = '26.07.09'
36# __DUNDER gets mangled in class
37_closest_ = 'closest'
38_destination_ = 'destination'
39_elel_ = 'll'
40_final_ = 'final'
41_fraction_ = 'fraction'
44class Bearing2Tuple(_NamedTuple):
45 '''2-Tuple C{(initial, final)} bearings, both in compass C{degrees360}.
46 '''
47 _Names_ = (_initial_, _final_)
48 _Units_ = ( Bearing, Bearing)
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)
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)
67 def enclosures(self, S_other, *W_N_E):
68 '''Get the enclosures of this around an other L{Bounds4Tuple}.
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}}.
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))
85 def isinside(self, lat, lon, eps=0):
86 '''Are B{C{lat}} and B{C{lon}} both inside these bounds?
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}).
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)
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)
104 @Property_RO
105 def latZ(self):
106 '''Get the latitudinal size (C{degrees}).
107 '''
108 return Lat(latZ=self.latN - self.latS)
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)
116 @Property_RO
117 def lonZ(self):
118 '''Get the longitudinal size (C{degrees}).
119 '''
120 return Lon(lonZ=self.lonE - self.lonW)
122 def overlap(self, S_other, *W_N_E):
123 '''Intersect this with an other L{Bounds4Tuple}.
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}}.
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))
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
147 def resize(self, eps):
148 '''Get these bounds, over- or undersize by C{B{eps}}.
150 @arg eps: In- or decrease (C{degrees}).
152 @return: A L{Bounds4Tuple}C{(latS, lonW, latN, lonE)}
153 with all 4 bounds resized.
154 '''
155 return _resize4(self, Degrees(eps=eps))
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}.
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.
169 @see: Class L{Ellipse5Tuple}.
170 '''
171 _Names_ = (_radius_, _height_, _lat_, _beta_)
172 _Units_ = ( Radius, Height, Lat, Lat)
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
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)
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)
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)
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)
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}}.
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)
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)
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)
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[.]
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}.
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.
262 @see: Class L{Circle4Tuple}.
263 '''
264 _Names_ = (_a_, _b_, _height_, _lat_, _beta_)
265 _Units_ = ( Radius, Radius, Height, Lat, Lat)
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
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)))
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}.
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'
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}.
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'
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)
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.
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.
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)
324class LatLon2Tuple(_NamedTuple):
325 '''2-Tuple C{(lat, lon)} in C{degrees90} and C{degrees180}.
326 '''
327 _Names_ = (_lat_, _lon_)
328 _Units_ = ( Lat, Lon)
330 def to3Tuple(self, height, **name):
331 '''Extend this L{LatLon2Tuple} to a L{LatLon3Tuple}.
333 @arg height: The height to add (C{scalar}).
334 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding
335 this name.
337 @return: A L{LatLon3Tuple}C{(lat, lon, height)}.
339 @raise ValueError: Invalid B{C{height}}.
340 '''
341 return self._xtend(LatLon3Tuple, height, **name)
343 def to4Tuple(self, height, datum, **name):
344 '''Extend this L{LatLon2Tuple} to a L{LatLon4Tuple}.
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.
351 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
353 @raise TypeError: If B{C{datum}} not a C{Datum}.
355 @raise ValueError: Invalid B{C{height}}.
356 '''
357 return self.to3Tuple(height).to4Tuple(datum, **name)
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)
367 def to4Tuple(self, datum, **name):
368 '''Extend this L{LatLon3Tuple} to a L{LatLon4Tuple}.
370 @arg datum: The datum to add (C{Datum}).
371 @kwarg name: Optional C{B{name}=NN} (C{str}), overriding
372 this name.
374 @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
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)
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)
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
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)
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)
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_)
433 def to5Tuple(self, height, radius, **name):
434 '''Extend this L{LatLonPrec3Tuple} to a L{LatLonPrec5Tuple}.
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.
441 @return: A L{LatLonPrec5Tuple}C{(lat, lon, precision,
442 height, radius)}.
443 '''
444 return self._xtend(LatLonPrec5Tuple, height, radius, **name)
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)
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
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
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)
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)
508# NearestOn4Tuple DEPRECATED, see .deprecated.classes.NearestOn4Tuple
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)
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)
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:]
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]}.
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)
561 def to3Tuple(self, height, **name):
562 '''Extend this L{PhiLam2Tuple} to a L{PhiLam3Tuple}.
564 @arg height: The height to add (C{scalar}).
565 @kwarg name: Optional C{B{name}=NN} (C{str}),
566 overriding this name.
568 @return: A L{PhiLam3Tuple}C{(phi, lam, height)}.
570 @raise ValueError: Invalid B{C{height}}.
571 '''
572 return self._xtend(PhiLam3Tuple, height, **name)
574 def to4Tuple(self, height, datum):
575 '''Extend this L{PhiLam2Tuple} to a L{PhiLam4Tuple}.
577 @arg height: The height to add (C{scalar}).
578 @arg datum: The datum to add (C{Datum}).
580 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
582 @raise TypeError: If B{C{datum}} not a C{Datum}.
584 @raise ValueError: Invalid B{C{height}}.
585 '''
586 return self.to3Tuple(height).to4Tuple(datum)
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}.
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)
601 def to4Tuple(self, datum, **name):
602 '''Extend this L{PhiLam3Tuple} to a L{PhiLam4Tuple}.
604 @arg datum: The datum to add (C{Datum}).
605 @kwarg name: Optional C{B{name}=NN} (C{str}),
606 overriding this name.
608 @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
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)
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}.
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)
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)
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)
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)
652 def isinside(self, RDx, RDy, eps=0):
653 '''Are B{C{RDx}} and B{C{RDy}} both inside these C{RD} bounds?
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}).
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)
665 def resize(self, eps):
666 '''Get these bounds, over- or undersize by C{B{eps}}.
668 @arg eps: In- or decrease (C{degrees}).
670 @return: A L{RD4Tuple}C{(minRDx, minRDy, maxRDx, maxRDy)}
671 with all 4 bounds resized.
672 '''
673 return _resize4(self, Meter(eps=eps))
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)
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)
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)
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.
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_)
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)
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)
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)
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)
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)
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)
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)
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)
799 def toCartesian(self, Cartesian, **Cartesian_kwds):
800 '''Return this C{Vector2Tuple} as a C{Cartesian}.
802 @arg Cartesian: The C{Cartesian} class to use.
803 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
804 keyword arguments.
806 @return: The C{B{Cartesian}} instance with C{z=0}.
807 '''
808 return _v2Cls(self.xyz, Cartesian, Cartesian_kwds)
810 def to3Tuple(self, z=INT0, **name):
811 '''Extend this L{Vector2Tuple} to a L{Vector3Tuple}.
813 @kwarg z: The Z component add (C{scalar}).
814 @kwarg name: Optional C{B{name}=NN} (C{str}),
815 overriding this name.
817 @return: A L{Vector3Tuple}C{(x, y, z)}.
819 @raise ValueError: Invalid B{C{z}}.
820 '''
821 return self._xtend(Vector3Tuple, z, **name)
823 @property_RO
824 def xyz(self):
825 '''Get X, Y and Z=0 components (C{Vector3Tuple}).
826 '''
827 return Vector3Tuple(*self.xyz3)
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
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)
843 def toCartesian(self, Cartesian, **Cartesian_kwds):
844 '''Return this C{Vector3Tuple} as a C{Cartesian}.
846 @arg Cartesian: The C{Cartesian} class to use.
847 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
848 keyword arguments.
850 @return: The C{B{Cartesian}} instance.
851 '''
852 return _v2Cls(self, Cartesian, Cartesian_kwds)
854 def to4Tuple(self, h=INT0, **name):
855 '''Extend this L{Vector3Tuple} to a L{Vector4Tuple}.
857 @arg h: The height to add (C{scalar}).
858 @kwarg name: Optional C{B{name}=NN} (C{str}),
859 overriding this name.
861 @return: A L{Vector4Tuple}C{(x, y, z, h)}.
863 @raise ValueError: Invalid B{C{h}}.
864 '''
865 return self._xtend(Vector4Tuple, h, **name)
867 @property_RO
868 def xyz(self):
869 '''Get X, Y and Z components (C{Vector3Tuple}).
870 '''
871 return self
873 @property_RO
874 def xyz3(self):
875 '''Get X, Y and Z components as C{3-tuple}.
876 '''
877 return tuple(self)
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)
887 def toCartesian(self, Cartesian, **Cartesian_kwds):
888 '''Return this C{Vector4Tuple} as a C{Cartesian}.
890 @arg Cartesian: The C{Cartesian} class to use.
891 @kwarg Cartesian_kwds: Optional, additional C{Cartesian}
892 keyword arguments.
894 @return: The C{B{Cartesian}} instance.
895 '''
896 return _v2Cls(self, Cartesian, Cartesian_kwds)
898 def to3Tuple(self):
899 '''Reduce this L{Vector4Tuple} to a L{Vector3Tuple}.
901 @return: A L{Vector3Tuple}C{(x, y, z)}.
902 '''
903 return self.xyz
905 @property_RO
906 def xyz(self):
907 '''Get X, Y and Z components (L{Vector3Tuple}).
908 '''
909 return Vector3Tuple(*self.xyz)
911 @property_RO
912 def xyz3(self):
913 '''Get X, Y and Z components as C{3-tuple}.
914 '''
915 return tuple(self[:3])
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)
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))
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)
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.