1
2# -*- coding: utf-8 -*-
3
4u'''Various C{Float}, C{Int} and C{Str}ing units.
5
6Sub-classes C{Float}, C{Int} and C{Str} from basic C{float},
7C{int} respectively C{str} to named units as L{Degrees},
8L{Feet}, L{Meter}, L{Radians}, etc.
9'''
10
11from pygeodesy.basics import isstr, issubclassof
12from pygeodesy.dms import F__F, F__F_, parseDMS, parseRad, \
13                          S_NUL, S_SEP, _toDMS
14from pygeodesy.errors import _IsnotError, RangeError, TRFError, \
15                              UnitError, _xkwds_popitem
16from pygeodesy.interns import EPS, EPS1, NN, PI, PI_2, PI2, _band_, \
17                             _bearing_, _degrees_, _degrees2_, \
18                             _distance_, _E_, _easting_, _epoch_, \
19                             _EW_, _feet_, _height_, _invalid_, _N_, \
20                             _lam_, _lat_, _LatLon_, _lon_, _meter_, \
21                             _meter2_, _northing_, _NS_, _NSEW_, \
22                             _number_, _PERCENT_, _phi_, _precision_, \
23                             _radians_, _radians2_, _radius_, _S_, \
24                             _scalar_, _SPACE_, _UNDER_, _units_, \
25                             _W_, _zone_, _0_0, _0_001
26from pygeodesy.interns import _std_  # PYCHOK used!
27from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _getenv  # PYCHOK used!
28from pygeodesy.named import modulename, _Named
29from pygeodesy.props import Property_RO, property_doc_
30from pygeodesy.streprs import Fmt, fstr
31
32from math import radians
33
34__all__ = _ALL_LAZY.units
35__version__ = '21.08.24'
36
37
38class _NamedUnit(_Named):
39    '''(INTERNAL) Base class for C{units}.
40    '''
41    _std_repr = True  # set below
42    _units    = None
43
44    def _toRepr(self, value):
45        '''(INTERNAL) Representation "<name> (<value>)" or "<classname>(<value>)".
46        '''
47        t = NN(self.name, _SPACE_) if self.name else self.classname
48        return Fmt.PAREN(t, value)
49
50    @property_doc_(' standard C{repr} or named C{toRepr} representation.')
51    def std_repr(self):
52        '''Get the representation (C{bool}, C{True} means standard).
53        '''
54        return self._std_repr
55
56    @std_repr.setter  # PYCHOK setter!
57    def std_repr(self, std):
58        '''Set the representation (C{True} or C{"std"} for standard).
59        '''
60        self._std_repr = std in (True, _std_)
61
62    @property_doc_(' units name.')
63    def units(self):
64        '''Get the units name (C{str}).
65        '''
66        if self._units is None:
67            self._units = self.classname
68        return self._units
69
70    @units.setter  # PYCHOK setter!
71    def units(self, units):
72        '''Set the units name for this instance (C{str} or C{None} for default).
73        '''
74        self._units = None if units is None else str(units)
75
76
77class Float(float, _NamedUnit):
78    '''Named C{float}.
79    '''
80    # _std_repr = True  # set below
81
82    def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
83        '''New named C{float} instance.
84
85           @kwarg arg: The value (any C{type} convertable to C{float}).
86           @kwarg name: Optional instance name (C{str}).
87           @kwarg Error: Optional error to raise, overriding the
88                         default L{UnitError}.
89           @kwarg name_arg: Optional C{name=arg} keyword argument,
90                            inlieu of B{C{name}} and B{C{arg}}.
91
92           @returns: A C{Float} instance.
93
94           @raise Error: Invalid B{C{arg}}.
95        '''
96        if name_arg:
97            name, arg = _xkwds_popitem(name_arg)
98        try:
99            self = float.__new__(cls, arg)
100            if name:
101                _NamedUnit.name.fset(self, name)  # see _Named.name
102            return self
103
104        except (TypeError, ValueError) as x:  # XXX not ... as x:
105            raise _Error(cls, arg, name=name, Error=Error, txt=str(x))
106
107    def __repr__(self):  # to avoid MRO(float)
108        '''Return a representation of this C{Float}.
109
110           @see: Method C{Float.toRepr} and property C{Float.std_repr}.
111
112           @note: Use C{env} variable C{PYGEODESY_FLOAT_STD_REPR=std} prior
113                  to C{import pygeodesy} to get the standard C{repr} or set
114                  property C{std_repr=False} to always get the named C{toRepr}
115                  representation.
116        '''
117        return self.toRepr(std=self._std_repr)
118
119    def __str__(self):  # to avoid MRO(float)
120        '''Return this C{Float} as standard C{str}.
121        '''
122        # XXX must use super(Float, self)... since super()...
123        # only works for Python 3+ and float.__str__(self)
124        # invokes .__repr__(self); calling self.toRepr(std=True)
125        # super(Float, self).__repr__() mimicks this behavior
126        return super(Float, self).__repr__()  # see .test.testCss.py
127
128    def toRepr(self, prec=12, fmt=Fmt.g, ints=False, std=False):  # PYCHOK prec=8, ...
129        '''Return a representation of this C{Float}.
130
131           @kwarg std: Use the standard C{repr} or the named
132                       representation (C{bool}).
133
134           @see: Function L{fstr} for more documentation.
135        '''
136        # XXX must use super(Float, self)... since
137        # super()... only works for Python 3+
138        return super(Float, self).__repr__() if std else \
139               self._toRepr(fstr(self, prec=prec, fmt=fmt, ints=ints))
140
141    def toStr(self, prec=12, fmt=Fmt.g, ints=False):  # PYCHOK prec=8, ...
142        '''Format this C{Float} as C{str}.
143
144           @see: Function L{fstr} for more documentation.
145        '''
146        return fstr(self, prec=prec, fmt=fmt, ints=ints)
147
148
149class Float_(Float):
150    '''Named C{float} with optional C{low} and C{high} limit.
151    '''
152    def __new__(cls, arg=None, name=NN, Error=UnitError, low=EPS, high=None, **name_arg):
153        '''New named C{float} instance with limits.
154
155           @arg cls: This class (C{Float_} or sub-class).
156           @kwarg arg: The value (any C{type} convertable to C{float}).
157           @kwarg name: Optional instance name (C{str}).
158           @kwarg Error: Optional error to raise, overriding the default
159                         L{UnitError}.
160           @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
161           @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
162           @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of
163                            B{C{name}} and B{C{arg}}.
164
165           @returns: A C{Float_} instance.
166
167           @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or
168                         above B{C{high}}.
169        '''
170        if name_arg:
171            name, arg = _xkwds_popitem(name_arg)
172        self = Float.__new__(cls, arg=arg, name=name, Error=Error)
173        if (low is not None) and self < low:
174            txt = Fmt.limit(below=Fmt.g(low, prec=6, ints=isinstance(self, Epoch)))
175        elif (high is not None) and self > high:
176            txt = Fmt.limit(above=Fmt.g(high, prec=6, ints=isinstance(self, Epoch)))
177        else:
178            return self
179        raise _Error(cls, arg, name=name, Error=Error, txt=txt)
180
181
182class Int(int, _NamedUnit):
183    '''Named C{int}.
184    '''
185    # _std_repr = True  # set below
186
187    def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
188        '''New named C{int} instance.
189
190           @kwarg arg: The value (any C{type} convertable to C{float}).
191           @kwarg name: Optional instance name (C{str}).
192           @kwarg Error: Optional error to raise, overriding the
193                         default L{UnitError}.
194           @kwarg name_arg: Optional C{name=arg} keyword argument,
195                            inlieu of B{C{name}} and B{C{arg}}.
196
197           @returns: An C{Int} instance.
198
199           @raise Error: Invalid B{C{arg}}.
200        '''
201        if name_arg:
202            name, arg = _xkwds_popitem(name_arg)
203        try:
204            self = int.__new__(cls, arg)
205            if name:
206                _NamedUnit.name.fset(self, name)  # see _Named.name
207            return self
208
209        except (TypeError, ValueError) as x:  # XXX not ... as x:
210            raise _Error(cls, arg, name=name, Error=Error, txt=str(x))
211
212    def __repr__(self):  # to avoid MRO(int)
213        '''Return a representation of this named C{int}.
214
215           @see: Method C{Int.toRepr} and property C{Int.std_repr}.
216
217           @note: Use C{env} variable C{PYGEODESY_INT_STD_REPR=std}
218                  prior to C{import pygeodesy} to get the standard
219                  C{repr} or set property C{std_repr=False} to always
220                  get the named C{toRepr} representation.
221        '''
222        return self.toRepr(std=self._std_repr)
223
224    def __str__(self):  # to avoid MRO(int)
225        '''Return this C{Int} as standard C{str}.
226        '''
227        return self.toStr()
228
229    def toRepr(self, std=False, **unused):  # PYCHOK **unused
230        '''Return the representation of thisb is True C{Int}.
231
232           @kwarg std: Use the standard C{repr} or the named
233                       representation (C{bool}).
234        '''
235        r = int.__repr__(self)  # self.toStr()
236        return r if std else self._toRepr(r)
237
238    def toStr(self, **unused):  # PYCHOK **unused
239        '''Return this C{Int} as standard C{str}.
240        '''
241        # XXX must use '%d' % (self,) since
242        # int.__str__(self) fails with 3.8+
243        return '%d' % (self,)
244
245
246class Int_(Int):
247    '''Named C{int} with optional limits C{low} and C{high}.
248    '''
249    def __new__(cls, arg=None, name=NN, Error=UnitError, low=0, high=None, **name_arg):
250        '''New named C{int} instance with limits.
251
252           @kwarg cls: This class (C{Int_} or sub-class).
253           @arg arg: The value (any C{type} convertable to C{int}).
254           @kwarg name: Optional instance name (C{str}).
255           @kwarg Error: Optional error to raise, overriding the default
256                         C{UnitError}.
257           @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
258           @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
259           @kwarg name_arg: Optional C{name=arg} keyword argument,
260                            inlieu of B{C{name}} and B{C{arg}}.
261
262           @returns: An L{Int_} instance.
263
264           @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or
265                         above B{C{high}}.
266        '''
267        if name_arg:
268            name, arg = _xkwds_popitem(name_arg)
269        self = Int.__new__(cls, arg=arg, name=name, Error=Error)
270        if (low is not None) and self < low:
271            txt = Fmt.limit(below=low)
272        elif (high is not None) and self > high:
273            txt = Fmt.limit(above=high)
274        else:
275            return self
276        raise _Error(cls, arg, name=name, Error=Error, txt=txt)
277
278
279class Bool(Int, _NamedUnit):
280    '''Named C{bool}, a sub-class of C{int} like Python's C{bool}.
281    '''
282    # _std_repr = True  # set below
283    _bool_True_or_False = None
284
285    def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
286        '''New named C{bool} instance.
287
288           @kwarg cls: This class (C{Bool} or sub-class).
289           @kwarg arg: The value (any C{type} convertable to C{bool}).
290           @kwarg name: Optional instance name (C{str}).
291           @kwarg Error: Optional error to raise, overriding the
292                         default L{UnitError}.
293           @kwarg name_arg: Optional C{name=arg} keyword argument,
294                            inlieu of B{C{name}} and B{C{arg}}.
295
296           @returns: A L{Bool}, a C{bool}-like instance.
297
298           @raise Error: Invalid B{C{arg}}.
299        '''
300        if name_arg:
301            name, arg = _xkwds_popitem(name_arg)
302        try:
303            b = bool(arg)
304        except (TypeError, ValueError) as x:  # XXX not ... as x:
305            raise _Error(cls, arg, name=name, Error=Error, txt=str(x))
306
307        self = Int.__new__(cls, b, name=name, Error=Error)
308        self._bool_True_or_False = b
309        return self
310
311    # <https://StackOverflow.com/questions/9787890/assign-class-boolean-value-in-python>
312    def __bool__(self):  # PYCHOK Python 3+
313        return self._bool_True_or_False
314    __nonzero__ = __bool__  # PYCHOK Python 2-
315
316    def toRepr(self, std=False, **unused):  # PYCHOK **unused
317        '''Return the representation of this C{Bool}.
318
319           @kwarg std: Use the standard C{repr} or the named
320                       representation (C{bool}).
321
322           @note: Use C{env} variable C{PYGEODESY_BOOL_STD_REPR=std}
323                  prior to C{import pygeodesy} to get the standard
324                  C{repr} or set property C{std_repr=False} to always
325                  get the named C{toRepr} representation.
326        '''
327        r = repr(self._bool_True_or_False)  # self.toStr()
328        return r if std else self._toRepr(r)
329
330    def toStr(self, **unused):  # PYCHOK **unused
331        '''Return this C{Bool} as standard C{str}.
332        '''
333        return str(self._bool_True_or_False)
334
335
336class Str(str, _NamedUnit):
337    '''Named C{str}.
338    '''
339    # _std_repr = True  # set below
340
341    def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
342        '''New named C{str} instance.
343
344           @kwarg cls: This class (C{Str} or sub-class).
345           @kwarg arg: The value (any C{type} convertable to C{str}).
346           @kwarg name: Optional instance name (C{str}).
347           @kwarg Error: Optional error to raise, overriding
348                         the default (C{ValueError}).
349           @kwarg name_arg: Optional C{name=arg} keyword argument,
350                            inlieu of B{C{name}} and B{C{arg}}.
351
352           @returns: A L{Str} instance.
353
354           @raise Error: Invalid B{C{arg}}.
355        '''
356        if name_arg:
357            name, arg = _xkwds_popitem(name_arg)
358        try:
359            self = str.__new__(cls, arg)
360            if name:
361                _NamedUnit.name.fset(self, name)  # see _Named.name
362            return self
363
364        except (TypeError, ValueError) as x:  # XXX not ... as x:
365            raise _Error(cls, arg, name=name, Error=Error, txt=str(x))
366
367    def __repr__(self):
368        '''Return a representation of this C{Str}.
369
370           @see: Method C{Str.toRepr} and property C{Str.std_repr}.
371
372           @note: Use C{env} variable C{PYGEODESY_STR_STD_REPR=std}
373                  prior to C{import pygeodesy} to get the standard
374                  C{repr} or set property C{std_repr=False} to always
375                  get the named C{toRepr} representation.
376        '''
377        return self.toRepr(std=self._std_repr)  # see .test/testGars.py
378
379    def __str__(self):
380        '''Return this C{Str} as standard C{str}.
381        '''
382        return self.toStr()
383
384    def toRepr(self, std=False, **unused):  # PYCHOK **unused
385        '''Return the named representation of this C{Str}.
386
387           @kwarg std: Use the standard C{repr} or the named
388                       representation (C{bool}).
389        '''
390        # must use super(Str, self).. since
391        # super()... only works for Python 3+ and
392        # str.__repr__(self) fails with Python 3.8+
393        r = super(Str, self).__repr__()
394        return r if std else self._toRepr(r)
395
396    def toStr(self, **unused):  # PYCHOK **unused
397        '''Return this C{Str} as standard C{str}.
398        '''
399        # must use super(Str, self)... since
400        # super()... only works for Python 3+ and
401        # str.__str__(self) fails with Python 3.8+
402        return super(Str, self).__str__()
403
404
405_Str_degrees  = Str(_degrees_)   # PYCHOK in .frechet, .hausdorff
406_Str_meter    = Str(_meter_)     # PYCHOK in .frechet, .hausdorff
407_Str_NN       = Str(NN)          # PYCHOK in .frechet, .hausdorff
408_Str_radians  = Str(_radians_)   # PYCHOK in .frechet, .hausdorff
409_Str_radians2 = Str(_radians2_)  # PYCHOK in .frechet, .hausdorff
410
411
412class Band(Str):
413    '''Named C{str} representing a UTM/UPS band letter, unchecked.
414    '''
415    def __new__(cls, arg=None, name=_band_, **Error_name_arg):
416        '''See L{Str}.
417        '''
418        return Str.__new__(cls, arg=arg, name=name, **Error_name_arg)
419
420
421class Degrees(Float):
422    '''Named C{float} representing a coordinate in C{degrees}, optionally clipped.
423    '''
424    _ddd_ = 1  # default for .dms._toDMS
425    _suf_ = S_NUL, S_NUL, S_NUL
426    _sep_ = S_SEP
427
428    def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, clip=0, **name_arg):
429        '''New named C{Degrees} instance.
430
431           @arg cls: This class (C{Degrees} or sub-class).
432           @kwarg arg: The value (any C{type} convertable to C{float} or
433                       parsable by L{parseDMS}).
434           @kwarg name: Optional instance name (C{str}).
435           @kwarg Error: Optional error to raise, overriding the default
436                         L{UnitError}.
437           @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
438           @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}}
439                        (C{degrees} or C{0} or C{None} for unclipped).
440           @kwarg name_arg: Optional C{name=arg} keyword argument,
441                            inlieu of B{C{name}} and B{C{arg}}.
442
443           @returns: A C{Degrees} instance.
444
445           @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the
446                         B{C{clip}} range and L{rangerrors} set to C{True}.
447        '''
448        if name_arg:
449            name, arg = _xkwds_popitem(name_arg)
450        try:
451            return Float.__new__(cls, parseDMS(arg, suffix=suffix, clip=clip),
452                                      name=name, Error=Error)
453        except RangeError as x:
454            t, E = str(x), type(x)
455        except (TypeError, ValueError) as x:
456            t, E = str(x), Error
457        raise _Error(cls, arg, name=name, Error=E, txt=t)
458
459    def toRepr(self, prec=None, fmt=F__F_, ints=False, std=False):  # PYCHOK prec=8, ...
460        return Float.toRepr(self, std=std) if std else \
461               self._toRepr(self.toStr(prec=prec, fmt=fmt, ints=ints))
462
463    def toStr(self, prec=None, fmt=F__F_, ints=False):  # PYCHOK prec=8, ...
464        if fmt.startswith(_PERCENT_):  # use regular formatting
465            p = 8 if prec is None else prec
466            return fstr(self, prec=p, fmt=fmt, ints=ints, sep=self._sep_)
467        else:
468            return _toDMS(self, fmt, prec, self._sep_, self._ddd_,
469                                           self._suf_[0 if self > 0 else
470                                                     (1 if self < 0 else 2)])
471
472
473class Degrees_(Degrees):
474    '''Named C{Degrees} representing a coordinate in C{degrees} with optional limits C{low} and C{high}.
475    '''
476    def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, low=None, high=None, **name_arg):
477        '''New named C{Degrees} instance.
478
479           @arg cls: This class (C{Degrees_} or sub-class).
480           @kwarg arg: The value (any C{type} convertable to C{float} or
481                       parsable by L{parseDMS}).
482           @kwarg name: Optional instance name (C{str}).
483           @kwarg Error: Optional error to raise, overriding the default
484                         L{UnitError}.
485           @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
486           @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
487           @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
488           @kwarg name_arg: Optional C{name=arg} keyword argument,
489                            inlieu of B{C{name}} and B{C{arg}}.
490
491           @returns: A C{Degrees} instance.
492
493           @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} below B{C{low}}
494                         or above B{C{high}}.
495        '''
496        if name_arg:
497            name, arg = _xkwds_popitem(name_arg)
498        self = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
499        if (low is not None) and self < low:
500            txt = Fmt.limit(below=low)
501        elif (high is not None) and self > high:
502            txt = Fmt.limit(above=high)
503        else:
504            return self
505        raise _Error(cls, arg, name=name, Error=Error, txt=txt)
506
507
508class Degrees2(Float):
509    '''Named C{float} representing a distance in C{degrees squared}.
510    '''
511    def __new__(cls, arg=None, name=_degrees2_, **Error_name_arg):
512        '''See L{Float}.
513        '''
514        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
515
516
517class Radians(Float):
518    '''Named C{float} representing a coordinate in C{radians}, optionally clipped.
519    '''
520    def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, clip=0, **name_arg):
521        '''New named C{Radians} instance.
522
523           @arg cls: This class (C{Radians} or sub-class).
524           @kwarg arg: The value (any C{type} convertable to C{float} or
525                       parsable by L{parseRad}).
526           @kwarg name: Optional instance name (C{str}).
527           @kwarg Error: Optional error to raise, overriding the default
528                         L{UnitError}.
529           @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
530           @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}}
531                        (C{radians} or C{0} or C{None} for unclipped).
532           @kwarg name_arg: Optional C{name=arg} keyword argument,
533                            inlieu of B{C{name}} and B{C{arg}}.
534
535           @returns: A C{Radians} instance.
536
537           @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} below B{C{low}}
538                         or above B{C{high}}.
539        '''
540        if name_arg:
541            name, arg = _xkwds_popitem(name_arg)
542        try:
543            return Float.__new__(cls, parseRad(arg, suffix=suffix, clip=clip),
544                                      name=name, Error=Error)
545        except RangeError as x:
546            t, E = str(x), type(x)
547        except (TypeError, ValueError) as x:
548            t, E = str(x), Error
549        raise _Error(cls, arg, name=name, Error=E, txt=t)
550
551    def toRepr(self, prec=8, fmt=F__F, ints=False, std=False):  # PYCHOK prec=8, ...
552        return Float.toRepr(self, prec=prec, fmt=fmt, ints=ints, std=std)
553
554    def toStr(self, prec=8, fmt=F__F, ints=False):  # PYCHOK prec=8, ...
555        return fstr(self, prec=prec, fmt=fmt, ints=ints)
556
557
558class Radians_(Radians):
559    '''Named C{float} representing a coordinate in C{radians} with optional limits C{low} and C{high}.
560    '''
561    def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, low=_0_0, high=PI2, **name_arg):
562        '''New named C{Radians} instance.
563
564           @arg cls: This class (C{Radians_} or sub-class).
565           @kwarg arg: The value (any C{type} convertable to C{float} or
566                       parsable by L{parseRad}).
567           @kwarg name: Optional instance name (C{str}).
568           @kwarg Error: Optional error to raise, overriding the default
569                         L{UnitError}.
570           @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
571           @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
572           @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
573           @kwarg name_arg: Optional C{name=arg} keyword argument,
574                            inlieu of B{C{name}} and B{C{arg}}.
575
576           @returns: A C{Radians_} instance.
577
578           @raise Error: Invalid B{C{arg}} or B{C{abs(deg)}} outside the
579                         B{C{clip}} range and L{rangerrors} set to C{True}.
580        '''
581        if name_arg:
582            name, arg = _xkwds_popitem(name_arg)
583        self = Radians.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
584        if (low is not None) and self < low:
585            txt = Fmt.limit(below=low)
586        elif (high is not None) and self > high:
587            txt = Fmt.limit(above=high)
588        else:
589            return self
590        raise _Error(cls, arg, name=name, Error=Error, txt=txt)
591
592
593class Radians2(Float_):
594    '''Named C{float} representing a distance in C{radians squared}.
595    '''
596    def __new__(cls, arg=None, name=_radians2_, Error=UnitError, **name_arg):
597        '''See L{Float_}.
598        '''
599        return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)
600
601
602class Bearing(Degrees):
603    '''Named C{float} representing a bearing in compass C{degrees} from (true) North.
604    '''
605    _ddd_ =  1
606    _suf_ = _N_ * 3  # always suffix N
607
608    def __new__(cls, arg=None, name=_bearing_, Error=UnitError, clip=0, **name_arg):
609        '''See L{Degrees}.
610        '''
611        if name_arg:
612            name, arg = _xkwds_popitem(name_arg)
613        d = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=_N_, clip=clip)
614        b = d % 360
615        return d if b == d else Degrees.__new__(cls, arg=b, name=name, Error=Error)
616
617
618class Bearing_(Radians):
619    '''Named C{float} representing a bearing in C{radians} from compass C{degrees} from (true) North.
620    '''
621    def __new__(cls, arg=None, name=_bearing_, clip=0, **Error_name_arg):
622        '''See L{Bearing} and L{Radians}.
623        '''
624        d = Bearing.__new__(cls, arg=arg, name=name, clip=clip, **Error_name_arg)
625        return Radians.__new__(cls, radians(d), name=name)
626
627
628class Distance(Float):
629    '''Named C{float} representing a distance, conventionally in C{meter}.
630    '''
631    def __new__(cls, arg=None, name=_distance_, **Error_name_arg):
632        '''See L{Distance}.
633        '''
634        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
635
636
637class Distance_(Float_):
638    '''Named C{float} with optional C{low} and C{high} limits representing a distance, conventionally in C{meter}.
639    '''
640    def __new__(cls, arg=None, name=_distance_, low=EPS, **high_Error_name_arg):
641        '''See L{Distance_}.
642        '''
643        return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
644
645
646class Easting(Float):
647    '''Named C{float} representing an easting, conventionally in C{meter}.
648    '''
649    def __new__(cls, arg=None, name=_easting_, Error=UnitError, falsed=False, osgr=False, **name_arg):
650        '''New named C{Easting} or C{Easting of Point} instance.
651
652           @arg cls: This class (C{Easting} or sub-class).
653           @kwarg arg: The value (any C{type} convertable to C{float}).
654           @kwarg name: Optional instance name (C{str}).
655           @kwarg Error: Optional error to raise, overriding the default
656                         L{UnitError}.
657           @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
658           @kwarg osgr: Check B{C{arg}} as an OSGR easting (C{bool}).
659           @kwarg name_arg: Optional C{name=arg} keyword argument,
660                            inlieu of B{C{name}} and B{C{arg}}.
661
662           @returns: An C{Easting} instance.
663
664           @raise Error: Invalid B{C{arg}} or negative, falsed B{C{arg}}.
665        '''
666        if name_arg:
667            name, arg = _xkwds_popitem(name_arg)
668        self = Float.__new__(cls, arg=arg, name=name, Error=Error)
669        if osgr and (self < 0 or self > 700e3):  # like Veness
670            raise _Error(cls, arg, name=name, Error=Error)
671        elif falsed and self < 0:
672            raise _Error(cls, arg, name=name, Error=Error, txt='negative, falsed')
673        return self
674
675
676class Epoch(Float_):  # by .ellipsoidalBase
677    '''Named C{epoch} with optional C{low} and C{high} limits representing a fractional calendar year.
678    '''
679
680    _std_repr = False
681
682    def __new__(cls, arg=None, name=_epoch_, Error=TRFError, low=1900, high=9999, **name_arg):
683        '''See L{Float_}.
684        '''
685        if name_arg:
686            name, arg = _xkwds_popitem(name_arg)
687        return arg if isinstance(arg, Epoch) else \
688               Float_.__new__(cls, arg=arg, name=name, Error=Error, low=low, high=high)
689
690    def toRepr(self, std=False, **unused):  # PYCHOK prec=3, fmt=Fmt.F, ints=True
691        '''Return a representation of this C{Epoch}.
692
693           @kwarg std: Use the standard C{repr} or the named
694                       representation (C{bool}).
695
696           @see: Function L{fstr} for more documentation.
697        '''
698        return Float_.toRepr(self, prec=-3, fmt=Fmt.F, ints=True, std=std)
699
700    def toStr(self, **unused):  # PYCHOK prec=3, fmt=Fmt.F, ints=True
701        '''Format this C{Epoch} as C{str}.
702
703           @see: Function L{fstr} for more documentation.
704        '''
705        return Float_.toStr(self, prec=-3, fmt=Fmt.F, ints=True)
706
707    __str__ = toStr  # PYCHOK default '%.3F', without trailing zeros and decimal point
708
709
710class Feet(Float):
711    '''Named C{float} representing a distance or length in C{feet}.
712    '''
713    def __new__(cls, arg=None, name=_feet_, **Error_name_arg):
714        '''See L{Float}.
715        '''
716        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
717
718
719class FIx(Float_):
720    '''A named I{Fractional Index}, an C{int} or C{float} index into
721       a C{list} or C{tuple} of C{points}, typically.  A C{float}
722       I{Fractional Index} C{fi} represents a location on the edge
723       between C{points[int(fi)]} and C{points[(int(fi) + 1) %
724       len(points)]}.
725    '''
726    _fin = 0
727
728    def __new__(cls, fi, fin=None, **name_Error):
729        '''New I{Fractional Index} in a C{list} or C{tuple} of points.
730
731           @arg fi: The fractional index (C{float} or C{int}).
732           @kwarg fin: Optional C{len}, the number of C{points}, the index
733                       C{[n]} wrapped to C{[0]} (C{int} or C{None}).
734           @kwarg name_Error: Optional keyword argument C{name=<name>}
735                              and/or C{Error=<Exception>}.
736
737           @return: The B{C{fi}} (named L{FIx}).
738
739           @note: The returned B{C{fi}} may exceed the B{C{flen}} of
740                  the original C{points} in certain open/closed cases.
741
742           @see: Method L{fractional} or function L{pygeodesy.fractional}.
743        '''
744        n = Int_(fin=fin, low=0) if fin else None
745        f = Float_.__new__(cls, fi, low=_0_0, high=n, **name_Error)
746        i = int(f)
747        r = f - float(i)
748        if r < EPS:  # see .points._fractional
749            f = Float_.__new__(cls, i)
750        elif r > EPS1:
751            f = Float_.__new__(cls, i + 1, high=n, **name_Error)
752        if n:
753            f._fin = n
754        return f
755
756    @Property_RO
757    def fin(self):
758        '''Get the given C{len}, the index C{[n]} wrapped to C{[0]} (C{int}).
759        '''
760        return self._fin
761
762    def fractional(self, points, **LatLon_and_kwds):
763        '''Return the point at this I{Fractional Index}.
764
765           @arg points: The points (C{LatLon}[], L{Numpy2LatLon}[],
766                        L{Tuple2LatLon}[] or C{other}[]).
767           @kwarg LatLon_and_kwds: Optional class to return the I{intermediate},
768                                   I{fractional} point (C{LatLon}) or C{None}
769                                   and optional B{C{LatLon}} keyword arguments
770                                   thereof.
771
772           @return: See function L{pygeodesy.fractional}.
773
774           @raise IndexError: Fractional index invalid for B{C{points}}
775                              or B{C{points}} not subscriptable or not
776                              closed.
777        '''
778        from pygeodesy.points import fractional
779        # fi = 0 if self == self.fin else self
780        return fractional(points, self, **LatLon_and_kwds)
781
782
783class Height(Float):  # here to avoid circular import
784    '''Named C{float} representing a height, conventionally in C{meter}.
785    '''
786    def __new__(cls, arg=None, name=_height_, **Error_name_arg):
787        '''See L{Float}.
788        '''
789        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
790
791
792class Lam(Radians):
793    '''Named C{float} representing a longitude in C{radians}.
794    '''
795    def __new__(cls, arg=None, name=_lam_, clip=PI, **Error_name_arg):
796        '''See L{Radians}.
797        '''
798        return Radians.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
799
800
801class Lam_(Lam):
802    '''Named C{float} representing a longitude in C{radians} converted from C{degrees}.
803    '''
804    def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):
805        '''See L{Degrees} and L{Radians}.
806        '''
807        d = Lam.__new__(cls, arg=arg, name=name, clip=clip, **Error_name_arg)
808        return Radians.__new__(cls, radians(d), name=name)
809
810
811class Lat(Degrees):
812    '''Named C{float} representing a latitude in C{degrees}.
813    '''
814    _ddd_ =  2
815    _suf_ = _N_, _S_, S_NUL  # no zero suffix
816
817    def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):
818        '''See L{Degrees}.
819        '''
820        return Degrees.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
821
822
823class Lat_(Degrees_):
824    '''Named C{float} representing a latitude in C{degrees} within limits C{low} and C{high}.
825    '''
826    _ddd_ =  2
827    _suf_ = _N_, _S_, S_NUL  # no zero suffix
828
829    def __new__(cls, arg=None, name=_lat_, low=-90, high=90, **Error_name_arg):
830        '''See L{Degrees_}.
831        '''
832        return Degrees_.__new__(cls, arg=arg, name=name, suffix=_NS_, low=low, high=high, **Error_name_arg)
833
834
835class Lon(Degrees):
836    '''Named C{float} representing a longitude in C{degrees}.
837    '''
838    _ddd_ =  3
839    _suf_ = _E_, _W_, S_NUL  # no zero suffix
840
841    def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):
842        '''See L{Degrees}.
843        '''
844        return Degrees.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
845
846
847class Lon_(Degrees_):
848    '''Named C{float} representing a longitude in C{degrees} within limits C{low} and C{high}.
849    '''
850    _ddd_ =  3
851    _suf_ = _E_, _W_, S_NUL  # no zero suffix
852
853    def __new__(cls, arg=None, name=_lon_, low=-180, high=180, **Error_name_arg):
854        '''See L{Degrees_}.
855        '''
856        return Degrees_.__new__(cls, arg=arg, name=name, suffix=_EW_, low=low, high=high, **Error_name_arg)
857
858
859class Meter(Float):
860    '''Named C{float} representing a distance or length in C{meter}.
861    '''
862    def __new__(cls, arg=None, name=_meter_, **Error_name_arg):
863        '''See L{Float}.
864        '''
865        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
866
867    def __repr__(self):
868        '''Return a representation of this C{Meter}.
869
870           @see: Method C{Str.toRepr} and property C{Str.std_repr}.
871
872           @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std}
873                  prior to C{import pygeodesy} to get the standard
874                  C{repr} or set property C{std_repr=False} to always
875                  get the named C{toRepr} representation.
876        '''
877        return self.toRepr(std=self._std_repr)
878
879
880_10um   = Meter(  _10um= 1e-5)   # PYCHOK 10 micrometer in .osgr
881_1mm    = Meter(   _1mm=_0_001)  # PYCHOK 1 millimeter in .ellipsoidal...
882_100km  = Meter( _100km= 1e+5)   # PYCHOK 100 kilometer in .formy, .mgrs, .osgr
883_2000km = Meter(_2000km= 2e+6)   # PYCHOK 2,000 kilometer in .mgrs
884
885
886class Meter_(Float_):
887    '''Named C{float} representing a distance or length in C{meter}.
888    '''
889    def __new__(cls, arg=None, name=_meter_, Error=UnitError, low=_0_0, high=None, **name_arg):
890        '''See L{Float_}.
891        '''
892        return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=low, high=high, **name_arg)
893
894
895class Meter2(Float_):
896    '''Named C{float} representing an area in C{meter squared}.
897    '''
898    def __new__(cls, arg=None, name=_meter2_, Error=UnitError, **name_arg):
899        '''See L{Float_}.
900        '''
901        return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)
902
903
904class Meter3(Float_):
905    '''Named C{float} representing a volume in C{meter cubed}.
906    '''
907    def __new__(cls, arg=None, name='meter3', Error=UnitError, **name_arg):
908        '''See L{Float_}.
909        '''
910        return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)
911
912
913class Northing(Float):
914    '''Named C{float} representing a northing, conventionally in C{meter}.
915    '''
916    def __new__(cls, arg=None, name=_northing_, Error=UnitError, falsed=False, osgr=False, **name_arg):
917        '''New named C{Northing} or C({Northing of point} instance.
918
919           @arg cls: This class (C{Northing} or sub-class).
920           @kwarg arg: The value (any C{type} convertable to C{float}).
921           @kwarg name: Optional instance name (C{str}).
922           @kwarg Error: Optional error to raise, overriding the default
923                         L{UnitError}.
924           @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
925           @kwarg osgr: Check B{C{arg}} as an OSGR northing (C{bool}).
926           @kwarg name_arg: Optional C{name=arg} keyword argument,
927                            inlieu of B{C{name}} and B{C{arg}}.
928
929           @returns: A C{Northing} instance.
930
931           @raise Error: Invalid B{C{arg}} or negative, falsed B{C{arg}}.
932        '''
933        if name_arg:
934            name, arg = _xkwds_popitem(name_arg)
935        self = Float.__new__(cls, arg=arg, name=name, Error=Error)
936        if osgr and (self < 0 or self > 1300e3):  # like Veness
937            raise _Error(cls, arg, name=name, Error=Error)
938        elif falsed and self < 0:
939            raise _Error(cls, arg, name=name, Error=Error, txt='negative, falsed')
940        return self
941
942
943class Number_(Int_):
944    '''Named C{int} representing a non-negative number.
945    '''
946    def __new__(cls, arg=None, name=_number_, low=0, **high_Error_name_arg):
947        '''See L{Int_}.
948        '''
949        return Int_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
950
951
952class Phi(Radians):
953    '''Named C{float} representing a latitude in C{radians}.
954    '''
955    def __new__(cls, arg=None, name=_phi_, clip=PI_2, **Error_name_arg):
956        '''See L{Radians}.
957        '''
958        return Radians.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
959
960
961class Phi_(Phi):
962    '''Named C{float} representing a latitude in C{radians} converted from C{degrees}.
963    '''
964    def __new__(cls, arg=None, name=_lat_, Error=UnitError, clip=90, **name_arg):
965        '''See L{Degrees} and L{Radians}.
966        '''
967        if name_arg:
968            name, arg = _xkwds_popitem(name_arg)
969        d = Phi.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
970        return Radians.__new__(cls, arg=radians(d), name=name, Error=Error)
971
972
973class Precision_(Int_):
974    '''Named C{int} with optional C{low} and C{high} limits representing a precision.
975    '''
976    def __new__(cls, arg=None, name=_precision_, low=0, **high_Error_name_arg):
977        '''See L{Int_}.
978        '''
979        return Int_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
980
981
982class Radius(Float):
983    '''Named C{float} representing a radius, conventionally in C{meter}.
984    '''
985    def __new__(cls, arg=None, name=_radius_, **Error_name_arg):
986        '''See L{Float}.
987        '''
988        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
989
990
991class Radius_(Float_):
992    '''Named C{float} with optional C{low} and C{high} limits representing a radius, conventionally in C{meter}.
993    '''
994    def __new__(cls, arg=None, name=_radius_, low=EPS, **high_Error_name_arg):
995        '''See L{Float}.
996        '''
997        return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
998
999
1000class Scalar(Float):
1001    '''Named C{float} representing a factor, fraction, scale, etc.
1002    '''
1003    def __new__(cls, arg=None, name=_scalar_, **Error_name_arg):
1004        '''See L{Float}.
1005        '''
1006        return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
1007
1008
1009class Scalar_(Float_):
1010    '''Named C{float} with optional C{low} and C{high} limits representing a factor, fraction, scale, etc.
1011    '''
1012    def __new__(cls, arg=None, name=_scalar_, low=_0_0, **high_Error_name_arg):
1013        '''See L{Float_}.
1014        '''
1015        return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
1016
1017
1018class Zone(Int):
1019    '''Named C{int} representing a UTM/UPS zone number.
1020    '''
1021    def __new__(cls, arg=None, name=_zone_, **Error_name_arg):
1022        '''See L{Int}
1023        '''
1024        # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX
1025        return Int_.__new__(cls, arg=arg, name=name, **Error_name_arg)
1026
1027
1028def _Error(clas, arg, name=NN, Error=UnitError, txt=_invalid_):
1029    '''(INTERNAL) Return an error with explanation.
1030
1031       @arg clas: The C{units} class or sub-class.
1032       @arg arg: The original C{unit} value.
1033       @kwarg name: The instance name (C{str}).
1034       @kwarg Error: Optional error, overriding the default
1035                     L{UnitError}.
1036       @kwarg txt: Optional explanation of the error (C{str}).
1037
1038       @returns: An B{C{Error}} instance.
1039    '''
1040    n = name if name else modulename(clas).lstrip(_UNDER_)
1041    return Error(n, arg, txt=txt)
1042
1043
1044def _xStrError(*Refs, **name_value_Error):
1045    '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}.
1046    '''
1047    r = tuple(r.__name__ for r in Refs) + (Str.__name__, _LatLon_, 'LatLon*Tuple')
1048    return _IsnotError(*r, **name_value_Error)
1049
1050
1051def _xUnit(units, Base):  # in .frechet,  .hausdorff
1052    '''(INTERNAL) Get C{Unit} from C{Unit} or C{name}, ortherwise C{Base}.
1053    '''
1054    if not issubclassof(Base, _NamedUnit):
1055        raise _IsnotError(_NamedUnit.__name__, Base=Base)
1056    U = globals().get(units.capitalize(), Base) if isstr(units) else (
1057                      units if issubclassof(units, Base) else Base)
1058    return U if issubclassof(U, Base) else Base
1059
1060
1061def _xUnits(units, Base=_NamedUnit):  # in .frechet, .hausdorff
1062    '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}.
1063    '''
1064    if not issubclassof(Base, _NamedUnit):
1065        raise _IsnotError(_NamedUnit.__name__, Base=Base)
1066    elif issubclassof(units, Base):
1067        return units
1068    elif isstr(units):
1069        return Str(units, name=_units_)  # XXX Str to _Pass and for backward compatibility
1070    else:
1071        raise _IsnotError(Base.__name__, Str.__name__, str.__name__, units=units)
1072
1073
1074def _std_repr(*classes):
1075    '''(INTERNAL) Use standard C{repr} or named C{toRepr}.
1076    '''
1077    for C in classes:
1078        if hasattr(C, _std_repr.__name__):  # PYCHOK del _std_repr
1079            env = 'PYGEODESY_%s_STD_REPR' % (C.__name__.upper(),)
1080            if _getenv(env, _std_).lower() != _std_:
1081                C._std_repr = False
1082
1083_std_repr(Bool, Float, Int, Meter, Str)  # PYCHOK expected
1084del _std_repr
1085
1086__all__ += _ALL_DOCS(_NamedUnit)
1087
1088# **) MIT License
1089#
1090# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved.
1091#
1092# Permission is hereby granted, free of charge, to any person obtaining a
1093# copy of this software and associated documentation files (the "Software"),
1094# to deal in the Software without restriction, including without limitation
1095# the rights to use, copy, modify, merge, publish, distribute, sublicense,
1096# and/or sell copies of the Software, and to permit persons to whom the
1097# Software is furnished to do so, subject to the following conditions:
1098#
1099# The above copyright notice and this permission notice shall be included
1100# in all copies or substantial portions of the Software.
1101#
1102# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
1103# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1104# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
1105# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
1106# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
1107# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
1108# OTHER DEALINGS IN THE SOFTWARE.
1109