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 _xinstanceof
12from pygeodesy.errors import _xkwds, _xkwds_not
13from pygeodesy.interns import NN, _a_, _A_, _angle_, _B_, _band_, _C_, \
14                             _convergence_, _datum_, _distance_, _E_, \
15                             _easting_, _epoch_, _h_, _height_, _hemipole_, \
16                             _lam_, _lat_, _lon_, _n_, _northing_, _number_, \
17                             _outside_, _phi_, _point_, _points_, _precision_, \
18                             _radius_, _reframe_, _scale_, _x_, _y_, _z_, \
19                             _zone_, _1_, _2_
20from pygeodesy.lazily import _ALL_LAZY
21from pygeodesy.named import _NamedTuple, _Pass, _xnamed
22from pygeodesy.units import Band, Bearing, Degrees, Degrees2, Easting, \
23                            Height, Int, Lam, Lat, Lon, Meter, Meter2, \
24                            Northing, Number_, Phi, Precision_, \
25                            Radians, Radius, Scalar, Str
26
27__all__ = _ALL_LAZY.namedTuples
28__version__ = '21.09.09'
29
30# __DUNDER gets mangled in class
31_elel_    = 'll'
32_final_   = 'final'
33_initial_ = 'initial'
34
35
36class Bearing2Tuple(_NamedTuple):
37    '''2-Tuple C{(initial, final)} bearings, both in compass C{degrees360}.
38    '''
39    _Names_ = (_initial_, _final_)
40    _Units_ = ( Bearing,   Bearing)
41
42
43class Bounds2Tuple(_NamedTuple):  # .geohash.py, .latlonBase.py, .points.py
44    '''2-Tuple C{(latlonSW, latlonNE)} with the bounds' lower-left and
45       upper-right corner as C{LatLon} instance.
46    '''
47    _Names_ = ('latlonSW', 'latlonNE')
48    _Units_ = (_Pass,      _Pass)
49
50
51class Bounds4Tuple(_NamedTuple):  # .geohash.py, .points.py
52    '''4-Tuple C{(latS, lonW, latN, lonE)} with the bounds' lower-left
53       C{(LatS, LowW)} and upper-right C{(latN, lonE)} corner lat- and
54       longitudes.
55    '''
56    _Names_ = ('latS', 'lonW', 'latN', 'lonE')
57    _Units_ = ( Lat,    Lon,    Lat,    Lon)
58
59
60class Destination2Tuple(_NamedTuple):  # .ellipsoidalKarney.py, -Vincenty.py
61    '''2-Tuple C{(destination, final)}, C{destination} in C{LatLon}
62       and C{final} bearing in compass C{degrees360}.
63    '''
64    _Names_ = ('destination', _final_)
65    _Units_ = (_Pass,          Bearing)
66
67
68class Destination3Tuple(_NamedTuple):  # .karney.py
69    '''3-Tuple C{(lat, lon, final)}, destination C{lat}, C{lon} in
70       C{degrees90} respectively C{degrees180} and C{final} bearing
71       in compass C{degrees360}.
72    '''
73    _Names_ = (_lat_, _lon_, _final_)
74    _Units_ = ( Lat,   Lon,   Bearing)
75
76
77class Distance2Tuple(_NamedTuple):  # .datum.py, .ellipsoidalBase.py
78    '''2-Tuple C{(distance, initial)}, C{distance} in C{meter} and
79       C{initial} bearing in compass C{degrees360}.
80    '''
81    _Names_ = (_distance_, _initial_)
82    _Units_ = ( Meter,      Bearing)
83
84
85class Distance3Tuple(_NamedTuple):  # .ellipsoidalKarney.py, -Vincenty.py
86    '''3-Tuple C{(distance, initial, final)}, C{distance} in C{meter}
87       and C{initial} and C{final} bearing, both in compass C{degrees360}.
88    '''
89    _Names_ = (_distance_, _initial_, _final_)
90    _Units_ = ( Meter,      Bearing,   Bearing)
91
92
93class Distance4Tuple(_NamedTuple):  # .formy.py, .points.py
94    '''4-Tuple C{(distance2, delta_lat, delta_lon, unroll_lon2)} with
95       the distance in C{degrees squared}, the latitudinal C{delta_lat
96       = B{lat2} - B{lat1}}, the wrapped, unrolled and adjusted
97       longitudinal C{delta_lon = B{lon2} - B{lon1}} and C{unroll_lon2},
98       the unrolled or original B{C{lon2}}.
99
100       @note: Use Function L{degrees2m} to convert C{degrees squared}
101              to C{meter} as M{degrees2m(sqrt(distance2), ...)} or
102              M{degrees2m(hypot(delta_lat, delta_lon), ...)}.
103    '''
104    _Names_ = ('distance2', 'delta_lat', 'delta_lon', 'unroll_lon2')
105    _Units_ = ( Degrees2,    Degrees,     Degrees,     Degrees)
106
107
108class EasNor2Tuple(_NamedTuple):  # .css.py, .osgr.py, .ups.py, .utm.py, .utmupsBase.py
109    '''2-Tuple C{(easting, northing)}, both in C{meter},
110       conventionally.
111    '''
112    _Names_ = (_easting_, _northing_)
113    _Units_ = ( Easting,   Northing)
114
115
116class EasNor3Tuple(_NamedTuple):  # .css.py, .lcc.py
117    '''3-Tuple C{(easting, northing, height)}, all in C{meter},
118       conventionally.
119    '''
120    _Names_ = (_easting_, _northing_, _height_)
121    _Units_ = ( Easting,   Northing,   Height)
122
123
124class Intersection3Tuple(_NamedTuple):  # .css.py, .lcc.py
125    '''3-Tuple C{(point, outside1, outside2)} of an intersection
126       C{point} and C{outside1}, the position of the C{point},
127       C{-1} if before the start, C{+1} if after the end and C{0}
128       if on or between the start and end point of the first line.
129       Similarly, C{outside2} is C{-2}, C{+2} or C{0} to indicate
130       the position of C{point} on the second line or path.  If a
131       path was specified with an initial bearing instead of an
132       end point, C{outside1} and/or C{outside2} will be C{0} if
133       the intersection C{point} is on the start point or C{+1}
134       respectively C{+2} if the intersection C{point} is after
135       the start point, in the direction of the bearing.
136    '''
137    _Names_ = (_point_, _outside_ + _1_, _outside_ + _2_)
138    _Units_ = (_Pass,    Int,             Int)
139
140
141class LatLon2Tuple(_NamedTuple):
142    '''2-Tuple C{(lat, lon)} in C{degrees90} and C{degrees180}.
143    '''
144    _Names_ = (_lat_, _lon_)
145    _Units_ = ( Lat,   Lon)
146
147    def to3Tuple(self, height):
148        '''Extend this L{LatLon2Tuple} to a L{LatLon3Tuple}.
149
150           @arg height: The height to add (C{scalar}).
151
152           @return: A L{LatLon3Tuple}C{(lat, lon, height)}.
153
154           @raise ValueError: Invalid B{C{height}}.
155        '''
156        return self._xtend(LatLon3Tuple, height)
157
158    def to4Tuple(self, height, datum):
159        '''Extend this L{LatLon2Tuple} to a L{LatLon4Tuple}.
160
161           @arg height: The height to add (C{scalar}).
162           @arg datum: The datum to add (C{Datum}).
163
164           @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
165
166           @raise TypeError: If B{C{datum}} not a C{Datum}.
167
168           @raise ValueError: Invalid B{C{height}}.
169        '''
170        return self.to3Tuple(height).to4Tuple(datum)
171
172
173class LatLon3Tuple(_NamedTuple):
174    '''3-Tuple C{(lat, lon, height)} in C{degrees90}, C{degrees180}
175       and C{meter}, conventionally.
176    '''
177    _Names_ = (_lat_, _lon_, _height_)
178    _Units_ = ( Lat,   Lon,   Height)
179
180    def to4Tuple(self, datum):
181        '''Extend this L{LatLon3Tuple} to a L{LatLon4Tuple}.
182
183           @arg datum: The datum to add (C{Datum}).
184
185           @return: A L{LatLon4Tuple}C{(lat, lon, height, datum)}.
186
187           @raise TypeError: If B{C{datum}} not a C{Datum}.
188        '''
189        from pygeodesy.datums import Datum
190        _xinstanceof(Datum, datum=datum)
191        return self._xtend(LatLon4Tuple, datum)
192
193
194class LatLon4Tuple(_NamedTuple):  # .cartesianBase.py, .css.py, .ecef.py, .lcc.py
195    '''4-Tuple C{(lat, lon, height, datum)} in C{degrees90},
196       C{degrees180}, C{meter} and L{Datum}.
197    '''
198    _Names_ = (_lat_, _lon_, _height_, _datum_)
199    _Units_ = ( Lat,   Lon,   Height,  _Pass)
200
201
202def _LL4Tuple(lat, lon, height, datum, LatLon, LatLon_kwds, inst=None, name=NN):
203    '''(INTERNAL) Return a L{LatLon4Tuple} or an B{C{LatLon}} instance.
204    '''
205    if LatLon is None:
206        r = LatLon4Tuple(lat, lon, height, datum, name=name)
207    else:
208        r = {} if inst is None else _xkwds_not(None,
209                                     epoch=  getattr(inst, _epoch_,   None),
210                                     reframe=getattr(inst, _reframe_, None))
211        kwds = _xkwds(LatLon_kwds, datum=datum, height=height, **r)
212        r = _xnamed(LatLon(lat, lon, **kwds), name)
213    return r
214
215
216class LatLonDatum3Tuple(_NamedTuple):  # .lcc.py, .osgr.py
217    '''3-Tuple C{(lat, lon, datum)} in C{degrees90}, C{degrees180}
218       and L{Datum}.
219    '''
220    _Names_ = (_lat_, _lon_, _datum_)
221    _Units_ = ( Lat,   Lon,  _Pass)
222
223
224class LatLonDatum5Tuple(_NamedTuple):  # .ups.py, .utm.py, .utmupsBase.py
225    '''5-Tuple C{(lat, lon, datum, convergence, scale)} in
226       C{degrees90}, C{degrees180}, L{Datum}, C{degrees}
227       and C{float}.
228    '''
229    _Names_ = (_lat_, _lon_, _datum_, _convergence_, _scale_)
230    _Units_ = ( Lat,   Lon,  _Pass,    Degrees,       Scalar)
231
232
233class LatLonPrec3Tuple(_NamedTuple):  # .gars.py, .wgrs.py
234    '''3-Tuple C{(lat, lon, precision)} in C{degrees}, C{degrees}
235       and C{int}.
236    '''
237    _Names_ = (_lat_, _lon_, _precision_)
238    _Units_ = ( Lat,   Lon,   Precision_)
239
240    def to5Tuple(self, height, radius):
241        '''Extend this L{LatLonPrec3Tuple} to a L{LatLonPrec5Tuple}.
242
243           @arg height: The height to add (C{float} or C{None}).
244           @arg radius: The radius to add (C{float} or C{None}).
245
246           @return: A L{LatLonPrec5Tuple}C{(lat, lon, precision,
247                    height, radius)}.
248        '''
249        return self._xtend(LatLonPrec5Tuple, height, radius)
250
251
252class LatLonPrec5Tuple(_NamedTuple):  # .wgrs.py
253    '''5-Tuple C{(lat, lon, precision, height, radius)} in C{degrees},
254       C{degrees}, C{int} and C{height} or C{radius} in C{meter} (or
255       C{None} if missing).
256    '''
257    _Names_ = (_lat_, _lon_, _precision_, _height_, _radius_)
258    _Units_ = ( Lat,   Lon,   Precision_,  Height,   Radius)
259
260
261class NearestOn3Tuple(_NamedTuple):  # .points.py, .sphericalTrigonometry.py
262    '''3-Tuple C{(closest, distance, angle)} of the C{closest}
263       point on the polygon, either a C{LatLon} instance or a
264       L{LatLon3Tuple}C{(lat, lon, height)} and the C{distance}
265       and C{angle} to the C{closest} point are in C{meter}
266       respectively compass C{degrees360}.
267    '''
268    _Names_ = ('closest', _distance_, _angle_)
269    _Units_ = (_Pass,      Meter,      Degrees)
270
271
272class PhiLam2Tuple(_NamedTuple):  # .frechet.py, .hausdorff.py, .latlonBase.py, .points.py, .vector3d.py
273    '''2-Tuple C{(phi, lam)} with latitude C{phi} in C{radians[PI_2]}
274       and longitude C{lam} in C{radians[PI]}.
275
276       @note: Using C{phi/lambda} for lat-/longitude in C{radians}
277              follows Chris Veness' U{convention
278              <https://www.Movable-Type.co.UK/scripts/latlong.html>}.
279    '''
280    _Names_ = (_phi_, _lam_)
281    _Units_ = ( Phi,   Lam)
282
283    def to3Tuple(self, height):
284        '''Extend this L{PhiLam2Tuple} to a L{PhiLam3Tuple}.
285
286           @arg height: The height to add (C{scalar}).
287
288           @return: A L{PhiLam3Tuple}C{(phi, lam, height)}.
289
290           @raise ValueError: Invalid B{C{height}}.
291        '''
292        return self._xtend(PhiLam3Tuple, height)
293
294    def to4Tuple(self, height, datum):
295        '''Extend this L{PhiLam2Tuple} to a L{PhiLam4Tuple}.
296
297           @arg height: The height to add (C{scalar}).
298           @arg datum: The datum to add (C{Datum}).
299
300           @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
301
302           @raise TypeError: If B{C{datum}} not a C{Datum}.
303
304           @raise ValueError: Invalid B{C{height}}.
305        '''
306        return self.to3Tuple(height).to4Tuple(datum)
307
308
309class PhiLam3Tuple(_NamedTuple):  # .nvector.py, extends -2Tuple
310    '''3-Tuple C{(phi, lam, height)} with latitude C{phi} in
311       C{radians[PI_2]}, longitude C{lam} in C{radians[PI]} and
312       C{height} in C{meter}.
313
314       @note: Using C{phi/lambda} for lat-/longitude in C{radians}
315              follows Chris Veness' U{convention
316              <https://www.Movable-Type.co.UK/scripts/latlong.html>}.
317    '''
318    _Names_ = (_phi_, _lam_, _height_)
319    _Units_ = ( Phi,   Lam,   Height)
320
321    def to4Tuple(self, datum):
322        '''Extend this L{PhiLam3Tuple} to a L{PhiLam4Tuple}.
323
324           @arg datum: The datum to add (C{Datum}).
325
326           @return: A L{PhiLam4Tuple}C{(phi, lam, height, datum)}.
327
328           @raise TypeError: If B{C{datum}} not a C{Datum}.
329        '''
330        from pygeodesy.datums import Datum
331        _xinstanceof(Datum, datum=datum)
332        return self._xtend(PhiLam4Tuple, datum)
333
334
335class PhiLam4Tuple(_NamedTuple):  # extends -3Tuple
336    '''4-Tuple C{(phi, lam, height, datum)} with latitude C{phi} in
337       C{radians[PI_2]}, longitude C{lam} in C{radians[PI]}, C{height}
338       in C{meter} and L{Datum}.
339
340       @note: Using C{phi/lambda} for lat-/longitude in C{radians}
341              follows Chris Veness' U{convention
342              <https://www.Movable-Type.co.UK/scripts/latlong.html>}.
343    '''
344    _Names_ = (_phi_, _lam_, _height_, _datum_)
345    _Units_ = ( Phi,   Lam,   Height,  _Pass)
346
347
348class Point3Tuple(_NamedTuple):
349    '''3-Tuple C{(x, y, ll)} in C{meter}, C{meter} and C{LatLon}.
350    '''
351    _Names_ = (_x_,   _y_,    _elel_)
352    _Units_ = ( Meter, Meter, _Pass)
353
354
355class Points2Tuple(_NamedTuple):  # .formy.py, .latlonBase.py
356    '''2-Tuple C{(number, points)} with the C{number} of points
357       and -possible reduced- C{list} or C{tuple} of C{points}.
358    '''
359    _Names_ = (_number_, _points_)
360    _Units_ = ( Number_, _Pass)
361
362
363class Triangle7Tuple(_NamedTuple):
364    '''7-Tuple C{(A, a, B, b, C, c, area)} with interior angles C{A},
365       C{B} and C{C} in C{degrees}, spherical sides C{a}, C{b} and
366       C{c} in C{meter} and the C{area} of a spherical triangle in
367       I{square} C{meter}.
368    '''
369    _Names_ = (_A_,     _a_,   _B_,     'b',   _C_,     'c',   'area')
370    _Units_ = ( Degrees, Meter, Degrees, Meter, Degrees, Meter, Meter2)
371
372
373class Triangle8Tuple(_NamedTuple):
374    '''8-Tuple C{(A, a, B, b, C, c, D, E)} with interior angles C{A},
375       C{B} and C{C}, spherical sides C{a}, C{b} and C{c}, I{spherical
376       deficit} C{D} and I{spherical excess} C{E} of a spherical
377       triangle, all in C{radians}.
378    '''
379    _Names_ = (_A_,     _a_,     _B_,     'b',     _C_,     'c',     'D',     _E_)
380    _Units_ = ( Radians, Radians, Radians, Radians, Radians, Radians, Radians, Radians)
381
382
383class Trilaterate5Tuple(_NamedTuple):  # .latlonBase.py, .nvector.py
384    '''5-Tuple C{(min, minPoint, max, maxPoint, n)} with C{min} and C{max}
385       in C{meter}, the corresponding trilaterated C{minPoint} and C{maxPoint}
386       as C{LatLon} and the number C{n}.  For area overlap, C{min} and C{max}
387       are the smallest respectively largest overlap found.  For perimeter
388       intersection, C{min} and C{max} represent the closest respectively
389       farthest intersection margin.  Count C{n} is the total number of
390       trilaterated overlaps or intersections found, C{0, 1, 2...6} with
391       C{0} meaning concentric.
392
393       @see: The C{ellipsoidalKarney-}, C{ellipsoidalVincenty-} and
394             C{sphericalTrigonometry.LatLon.trilaterate5} method for further
395             details on corner cases, like concentric or single trilaterated
396             results.
397   '''
398    _Names_ = (min.__name__, 'minPoint', max.__name__, 'maxPoint', _n_)
399    _Units_ = (Meter,        _Pass,      Meter,        _Pass,       Number_)
400
401
402class UtmUps2Tuple(_NamedTuple):  # .epsg.py
403    '''2-Tuple C{(zone, hemipole)} as C{int} and C{str}, where
404       C{zone} is C{1..60} for UTM or C{0} for UPS and C{hemipole}
405       C{'N'|'S'} is the UTM hemisphere or the UPS pole.
406    '''
407    _Names_ = (_zone_,  _hemipole_)
408    _Units_ = ( Number_, Str)
409
410
411class UtmUps5Tuple(_NamedTuple):  # .mgrs.py, .ups.py, .utm.py, .utmups.py
412    '''5-Tuple C{(zone, hemipole, easting, northing, band)} as C{int},
413       C{str}, C{meter}, C{meter} and C{band} letter, where C{zone}
414       is C{1..60} for UTM or C{0} for UPS, C{hemipole} C{'N'|'S'} is
415       the UTM hemisphere or the UPS pole and C{band} is C{""} or the
416       (longitudinal) UTM band C{'C'|'D'..'W'|'X'} or the (polar) UPS
417       band C{'A'|'B'|'Y'|'Z'}.
418    '''
419    _Names_ = (_zone_,  _hemipole_, _easting_, _northing_, _band_)
420    _Units_ = ( Number_, Str,        Easting,   Northing,   Band)
421
422    def __new__(cls, z, h, e, n, B, Error=None, name=NN):
423        if Error is not None:
424            e = Easting( e, Error=Error)
425            n = Northing(n, Error=Error)
426        return _NamedTuple.__new__(cls, z, h, e, n, B, name=name)
427
428
429class UtmUps8Tuple(_NamedTuple):  # .ups.py, .utm.py, .utmups.py
430    '''8-Tuple C{(zone, hemipole, easting, northing, band, datum,
431       convergence, scale)} as C{int}, C{str}, C{meter}, C{meter},
432       C{band} letter, C{Datum}, C{degrees} and C{scalar}, where
433       C{zone} is C{1..60} for UTM or C{0} for UPS, C{hemipole}
434       C{'N'|'S'} is the UTM hemisphere or the UPS pole and C{band}
435       is C{""} or the (longitudinal) UTM band C{'C'|'D'..'W'|'X'}
436       or the (polar) UPS band C{'A'|'B'|'Y'|'Z'}.
437    '''
438    _Names_ = (_zone_,  _hemipole_, _easting_, _northing_,
439               _band_,  _datum_, _convergence_, _scale_)
440    _Units_ = ( Number_, Str,        Easting,   Northing,
441                Band,   _Pass,    Degrees,       Scalar)
442
443    def __new__(cls, z, h, e, n, B, d, c, s, Error=None, name=NN):  # PYCHOK 11 args
444        if Error is not None:
445            e = Easting( e, Error=Error)
446            n = Northing(n, Error=Error)
447            c = Degrees(convergence=c, Error=Error)
448            s = Scalar(scale=s, Error=Error)
449        return _NamedTuple.__new__(cls, z, h, e, n, B, d, c, s, name=name)
450
451
452class UtmUpsLatLon5Tuple(_NamedTuple):  # .ups.py, .utm.py, .utmups.py
453    '''5-Tuple C{(zone, band, hemipole, lat, lon)} as C{int},
454       C{str}, C{str}, C{degrees90} and C{degrees180}, where
455       C{zone} is C{1..60} for UTM or C{0} for UPS, C{band} is
456       C{""} or the (longitudinal) UTM band C{'C'|'D'..'W'|'X'}
457       or (polar) UPS band C{'A'|'B'|'Y'|'Z'} and C{hemipole}
458       C{'N'|'S'} is the UTM hemisphere or the UPS pole.
459    '''
460    _Names_ = (_zone_,  _band_, _hemipole_, _lat_, _lon_)
461    _Units_ = ( Number_, Band,   Str,        Lat,   Lon)
462
463    def __new__(cls, z, B, h, lat, lon, Error=None, name=NN):
464        if Error is not None:
465            lat = Lat(lat, Error=Error)
466            lon = Lon(lon, Error=Error)
467        return _NamedTuple.__new__(cls, z, B, h, lat, lon, name=name)
468
469
470class Vector2Tuple(_NamedTuple):
471    '''2-Tuple C{(x, y)} of (geocentric) components, both in
472       C{meter} or C{units}.
473    '''
474    _Names_ = (_x_,    _y_)
475    _Units_ = ( Scalar, Scalar)
476
477    def to3Tuple(self, z):
478        '''Extend this L{Vector2Tuple} to a L{Vector3Tuple}.
479
480           @arg z: The Z component add (C{scalar}).
481
482           @return: A L{Vector3Tuple}C{(x, y, z)}.
483
484           @raise ValueError: Invalid B{C{z}}.
485        '''
486        return self._xtend(Vector3Tuple, z)
487
488
489class Vector3Tuple(_NamedTuple):
490    '''3-Tuple C{(x, y, z)} of (geocentric) components, all in
491       C{meter} or C{units}.
492    '''
493    _Names_ = (_x_,    _y_,    _z_)
494    _Units_ = ( Scalar, Scalar, Scalar)
495
496    def to4Tuple(self, h):
497        '''Extend this L{Vector3Tuple} to a L{Vector4Tuple}.
498
499           @arg h: The height to add (C{scalar}).
500
501           @return: A L{Vector4Tuple}C{(x, y, z, h)}.
502
503           @raise ValueError: Invalid B{C{h}}.
504        '''
505        return self._xtend(Vector4Tuple, h)
506
507
508class Vector4Tuple(_NamedTuple):  # .nvector.py
509    '''4-Tuple C{(x, y, z, h)} of (geocentric) components, all
510       in C{meter} or C{units}.
511    '''
512    _Names_ = (_x_,    _y_,    _z_,    _h_)
513    _Units_ = ( Scalar, Scalar, Scalar, Height)
514
515# **) MIT License
516#
517# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved.
518#
519# Permission is hereby granted, free of charge, to any person obtaining a
520# copy of this software and associated documentation files (the "Software"),
521# to deal in the Software without restriction, including without limitation
522# the rights to use, copy, modify, merge, publish, distribute, sublicense,
523# and/or sell copies of the Software, and to permit persons to whom the
524# Software is furnished to do so, subject to the following conditions:
525#
526# The above copyright notice and this permission notice shall be included
527# in all copies or substantial portions of the Software.
528#
529# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
530# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
531# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
532# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
533# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
534# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
535# OTHER DEALINGS IN THE SOFTWARE.
536
537# % env PYGEODESY_FOR_DOCS=1 python -m pygeodesy.named
538# all 71 locals OK
539