Coverage for pygeodesy/units.py: 94%
299 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-11-12 13:23 -0500
« prev ^ index » next coverage.py v7.2.2, created at 2023-11-12 13:23 -0500
2# -*- coding: utf-8 -*-
4u'''Various units, all sub-classes of C{Float}, C{Int} and
5C{Str} from basic C{float}, C{int} respectively C{str} to
6named units as L{Degrees}, L{Feet}, L{Meter}, L{Radians}, etc.
7'''
9from pygeodesy.basics import isstr, issubclassof, signOf
10from pygeodesy.constants import EPS, EPS1, PI, PI2, PI_2, \
11 _umod_360, _0_0, _0_001, \
12 _0_5, INT0 # PYCHOK for .mgrs, .namedTuples
13from pygeodesy.dms import F__F, F__F_, parseDMS, parseRad, \
14 S_NUL, S_SEP, _toDMS
15from pygeodesy.errors import _AssertionError, _IsnotError, TRFError, \
16 UnitError, _xkwds_popitem
17from pygeodesy.interns import NN, _band_, _bearing_, _degrees_, _degrees2_, \
18 _distance_, _E_, _easting_, _epoch_, _EW_, \
19 _feet_, _height_, _lam_, _lat_, \
20 _LatLon_, _lon_, _meter_, _meter2_, _N_, \
21 _northing_, _NS_, _NSEW_, _number_, _PERCENT_, \
22 _phi_, _precision_, _radians_, _radians2_, \
23 _radius_, _S_, _scalar_, _units_, \
24 _W_, _zone_, _std_ # PYCHOK used!
25from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv
26from pygeodesy.props import Property_RO
27# from pygeodesy.streprs import Fmt, fstr # from .unitsBase
28from pygeodesy.unitsBase import _Error, Float, Fmt, fstr, Int, _NamedUnit, \
29 Radius, Str # PYCHOK shared .namedTuples
30from math import degrees, radians
32__all__ = _ALL_LAZY.units
33__version__ = '23.05.12'
35_negative_falsed_ = 'negative, falsed'
38class Float_(Float):
39 '''Named C{float} with optional C{low} and C{high} limit.
40 '''
41 def __new__(cls, arg=None, name=NN, Error=UnitError, low=EPS, high=None, **name_arg):
42 '''New C{Float_} instance.
44 @arg cls: This class (C{Float_} or sub-class).
45 @kwarg arg: The value (any C{type} convertable to C{float}).
46 @kwarg name: Optional instance name (C{str}).
47 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
48 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
49 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
50 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
51 and B{C{arg}}.
53 @returns: A C{Float_} instance.
55 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
56 '''
57 if name_arg:
58 name, arg = _xkwds_popitem(name_arg)
59 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
60 if (low is not None) and self < low:
61 txt = Fmt.limit(below=Fmt.g(low, prec=6, ints=isinstance(self, Epoch)))
62 elif (high is not None) and self > high:
63 txt = Fmt.limit(above=Fmt.g(high, prec=6, ints=isinstance(self, Epoch)))
64 else:
65 return self
66 raise _Error(cls, arg, name, Error, txt=txt)
69class Int_(Int):
70 '''Named C{int} with optional limits C{low} and C{high}.
71 '''
72 def __new__(cls, arg=None, name=NN, Error=UnitError, low=0, high=None, **name_arg):
73 '''New named C{int} instance with limits.
75 @kwarg cls: This class (C{Int_} or sub-class).
76 @arg arg: The value (any C{type} convertable to C{int}).
77 @kwarg name: Optional instance name (C{str}).
78 @kwarg Error: Optional error to raise, overriding the default C{UnitError}.
79 @kwarg low: Optional lower B{C{arg}} limit (C{int} or C{None}).
80 @kwarg high: Optional upper B{C{arg}} limit (C{int} or C{None}).
81 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
82 and B{C{arg}}.
84 @returns: An L{Int_} instance.
86 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
87 '''
88 if name_arg:
89 name, arg = _xkwds_popitem(name_arg)
90 self = Int.__new__(cls, arg=arg, name=name, Error=Error)
91 if (low is not None) and self < low:
92 txt = Fmt.limit(below=low)
93 elif (high is not None) and self > high:
94 txt = Fmt.limit(above=high)
95 else:
96 return self
97 raise _Error(cls, arg, name, Error, txt=txt)
100class Bool(Int, _NamedUnit):
101 '''Named C{bool}, a sub-class of C{int} like Python's C{bool}.
102 '''
103 # _std_repr = True # set below
104 _bool_True_or_False = None
106 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
107 '''New C{Bool} instance.
109 @kwarg cls: This class (C{Bool} or sub-class).
110 @kwarg arg: The value (any C{type} convertable to C{bool}).
111 @kwarg name: Optional instance name (C{str}).
112 @kwarg Error: Optional error to raise, overriding the default
113 L{UnitError}.
114 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu
115 of B{C{name}} and B{C{arg}}.
117 @returns: A L{Bool}, a C{bool}-like instance.
119 @raise Error: Invalid B{C{arg}}.
120 '''
121 if name_arg:
122 name, arg = _xkwds_popitem(name_arg)
123 try:
124 b = bool(arg)
125 except Exception as x: # XXX not ... as x:
126 raise _Error(cls, arg, name, Error, x=x)
128 self = Int.__new__(cls, b, name=name, Error=Error)
129 self._bool_True_or_False = b
130 return self
132 # <https://StackOverflow.com/questions/9787890/assign-class-boolean-value-in-python>
133 def __bool__(self): # PYCHOK Python 3+
134 return self._bool_True_or_False
136 __nonzero__ = __bool__ # PYCHOK Python 2-
138 def toRepr(self, std=False, **unused): # PYCHOK **unused
139 '''Return a representation of this C{Bool}.
141 @kwarg std: Use the standard C{repr} or the named
142 representation (C{bool}).
144 @note: Use C{env} variable C{PYGEODESY_BOOL_STD_REPR=std}
145 prior to C{import pygeodesy} to get the standard
146 C{repr} or set property C{std_repr=False} to always
147 get the named C{toRepr} representation.
148 '''
149 r = repr(self._bool_True_or_False)
150 return r if std else self._toRepr(r)
152 def toStr(self, **unused): # PYCHOK **unused
153 '''Return this C{Bool} as standard C{str}.
154 '''
155 return str(self._bool_True_or_False)
158class Band(Str):
159 '''Named C{str} representing a UTM/UPS band letter, unchecked.
160 '''
161 def __new__(cls, arg=None, name=_band_, **Error_name_arg):
162 '''New L{Band} instance, see L{Str}.
163 '''
164 return Str.__new__(cls, arg=arg, name=name, **Error_name_arg)
167class Degrees(Float):
168 '''Named C{float} representing a coordinate in C{degrees}, optionally clipped.
169 '''
170 _ddd_ = 1 # default for .dms._toDMS
171 _sep_ = S_SEP
172 _suf_ = (S_NUL,) * 3
174 def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, clip=0, wrap=None, **name_arg):
175 '''New C{Degrees} instance, see L{Float}.
177 @arg cls: This class (C{Degrees} or sub-class).
178 @kwarg arg: The value (any scalar C{type} convertable to C{float} or
179 parsable by L{pygeodesy.parseDMS}).
180 @kwarg name: Optional instance name (C{str}).
181 @kwarg Error: Optional error to raise, overriding the default
182 L{UnitError}.
183 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
184 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}}
185 (C{degrees} or C{0} or C{None} for unclipped).
186 @kwarg wrap: Optionally adjust the B{C{arg}} value (L{pygeodesy.wrap90},
187 L{pygeodesy.wrap180} or L{pygeodesy.wrap360}).
188 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of
189 B{C{name}} and B{C{arg}}.
191 @returns: A C{Degrees} instance.
193 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}
194 range and L{pygeodesy.rangerrors} set to C{True}.
195 '''
196 if name_arg:
197 name, arg = _xkwds_popitem(name_arg)
198 try:
199 d = Float.__new__(cls, parseDMS(arg, suffix=suffix, clip=clip),
200 Error=Error, name=name)
201 if wrap:
202 w = wrap(d)
203 if w != d:
204 d = Float.__new__(cls, arg=w, name=name, Error=Error)
205 except Exception as x:
206 raise _Error(cls, arg, name, Error, x=x)
207 return d
209 def toDegrees(self):
210 '''Convert C{Degrees} to C{Degrees}.
211 '''
212 return self
214 def toRadians(self):
215 '''Convert C{Degrees} to C{Radians}.
216 '''
217 return Radians(radians(self), name=self.name)
219 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...
220 '''Return a representation of this C{Degrees}.
222 @kwarg std: If C{True} return the standard C{repr},
223 otherwise the named representation (C{bool}).
225 @see: Methods L{Degrees.toStr}, L{Float.toRepr} and function
226 L{pygeodesy.toDMS} for more documentation.
227 '''
228 return Float.toRepr(self, std=std, **prec_fmt_ints)
230 def toStr(self, prec=None, fmt=F__F_, ints=False, **s_D_M_S): # PYCHOK prec=8, ...
231 '''Return this C{Degrees} as standard C{str}.
233 @see: Function L{pygeodesy.toDMS} for keyword argument details.
234 '''
235 if fmt.startswith(_PERCENT_): # use regular formatting
236 p = 8 if prec is None else prec
237 return fstr(self, prec=p, fmt=fmt, ints=ints, sep=self._sep_)
238 else:
239 s = self._suf_[signOf(self) + 1]
240 return _toDMS(self, fmt, prec, self._sep_, self._ddd_, s, s_D_M_S)
243class Degrees_(Degrees):
244 '''Named C{Degrees} representing a coordinate in C{degrees} with optional limits C{low} and C{high}.
245 '''
246 def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, low=None, high=None, **name_arg):
247 '''New C{Degrees_} instance, see L{Degrees} and L{Float}..
249 @arg cls: This class (C{Degrees_} or sub-class).
250 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by
251 L{pygeodesy.parseDMS}).
252 @kwarg name: Optional instance name (C{str}).
253 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
254 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
255 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
256 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
257 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
258 and B{C{arg}}.
260 @returns: A C{Degrees} instance.
262 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
263 '''
264 if name_arg:
265 name, arg = _xkwds_popitem(name_arg)
266 self = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
267 if (low is not None) and self < low:
268 txt = Fmt.limit(below=low)
269 elif (high is not None) and self > high:
270 txt = Fmt.limit(above=high)
271 else:
272 return self
273 raise _Error(cls, arg, name, Error, txt=txt)
276class Degrees2(Float):
277 '''Named C{float} representing a distance in C{degrees squared}.
278 '''
279 def __new__(cls, arg=None, name=_degrees2_, **Error_name_arg):
280 '''See L{Float}.
281 '''
282 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
285class Radians(Float):
286 '''Named C{float} representing a coordinate in C{radians}, optionally clipped.
287 '''
288 def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, clip=0, **name_arg):
289 '''New C{Radians} instance, see L{Float}.
291 @arg cls: This class (C{Radians} or sub-class).
292 @kwarg arg: The value (any C{type} convertable to C{float} or parsable
293 by L{pygeodesy.parseRad}).
294 @kwarg name: Optional instance name (C{str}).
295 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
296 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
297 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}} (C{radians} or C{0}
298 or C{None} for unclipped).
299 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
300 and B{C{arg}}.
302 @returns: A C{Radians} instance.
304 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}
305 range and L{pygeodesy.rangerrors} set to C{True}.
306 '''
307 if name_arg:
308 name, arg = _xkwds_popitem(name_arg)
309 try:
310 return Float.__new__(cls, parseRad(arg, suffix=suffix, clip=clip),
311 Error=Error, name=name)
312 except Exception as x:
313 raise _Error(cls, arg, name, Error, x=x)
315 def toDegrees(self):
316 '''Convert C{Radians} to C{Degrees}.
317 '''
318 return Degrees(degrees(self), name=self.name)
320 def toRadians(self):
321 '''Convert C{Radians} to C{Radians}.
322 '''
323 return self
325 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...
326 '''Return a representation of this C{Radians}.
328 @kwarg std: If C{True} return the standard C{repr},
329 otherwise the named representation (C{bool}).
331 @see: Methods L{Radians.toStr}, L{Float.toRepr} and function
332 L{pygeodesy.toDMS} for more documentation.
333 '''
334 return Float.toRepr(self, std=std, **prec_fmt_ints)
336 def toStr(self, prec=8, fmt=F__F, ints=False): # PYCHOK prec=8, ...
337 '''Return this C{Radians} as standard C{str}.
339 @see: Function L{pygeodesy.fstr} for keyword argument details.
340 '''
341 return fstr(self, prec=prec, fmt=fmt, ints=ints)
344class Radians_(Radians):
345 '''Named C{float} representing a coordinate in C{radians} with optional limits C{low} and C{high}.
346 '''
347 def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, low=_0_0, high=PI2, **name_arg):
348 '''New C{Radians_} instance.
350 @arg cls: This class (C{Radians_} or sub-class).
351 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by
352 L{pygeodesy.parseRad}).
353 @kwarg name: Optional instance name (C{str}).
354 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
355 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
356 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
357 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
358 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
359 and B{C{arg}}.
361 @returns: A C{Radians_} instance.
363 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
364 '''
365 if name_arg:
366 name, arg = _xkwds_popitem(name_arg)
367 self = Radians.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
368 if (low is not None) and self < low:
369 txt = Fmt.limit(below=low)
370 elif (high is not None) and self > high:
371 txt = Fmt.limit(above=high)
372 else:
373 return self
374 raise _Error(cls, arg, name, Error, txt=txt)
377class Radians2(Float_):
378 '''Named C{float} representing a distance in C{radians squared}.
379 '''
380 def __new__(cls, arg=None, name=_radians2_, **Error_name_arg):
381 '''New L{Radians2} instance, see L{Float_}.
382 '''
383 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)
386class Bearing(Degrees):
387 '''Named C{float} representing a bearing in compass C{degrees} from (true) North.
388 '''
389 _ddd_ = 1
390 _suf_ = _N_ * 3 # always suffix N
392 def __new__(cls, arg=None, name=_bearing_, Error=UnitError, clip=0, **name_arg):
393 '''New L{Bearing} instance, see L{Degrees}.
394 '''
395 if name_arg:
396 name, arg = _xkwds_popitem(name_arg)
397 d = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=_N_, clip=clip)
398 b = _umod_360(d) # 0 <= b < 360
399 return d if b == d else Degrees.__new__(cls, arg=b, name=name, Error=Error)
402class Bearing_(Radians):
403 '''Named C{float} representing a bearing in C{radians} from compass C{degrees} from (true) North.
404 '''
405 def __new__(cls, arg=None, name=_bearing_, clip=0, **Error_name_arg):
406 '''New L{Bearing_} instance, see L{Bearing} and L{Radians}.
407 '''
408 d = Bearing.__new__(cls, arg=arg, name=name, clip=clip, **Error_name_arg)
409 return Radians.__new__(cls, radians(d), name=name)
412class Distance(Float):
413 '''Named C{float} representing a distance, conventionally in C{meter}.
414 '''
415 def __new__(cls, arg=None, name=_distance_, **Error_name_arg):
416 '''New L{Distance} instance, see L{Float}.
417 '''
418 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
421class Distance_(Float_):
422 '''Named C{float} with optional C{low} and C{high} limits representing a distance, conventionally in C{meter}.
423 '''
424 def __new__(cls, arg=None, name=_distance_, **low_high_Error_name_arg):
425 '''New L{Distance_} instance, see L{Float}.
426 '''
427 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
430class Easting(Float):
431 '''Named C{float} representing an easting, conventionally in C{meter}.
432 '''
433 def __new__(cls, arg=None, name=_easting_, Error=UnitError, falsed=False, high=None, **name_arg):
434 '''New named C{Easting} or C{Easting of Point} instance.
436 @arg cls: This class (C{Easting} or sub-class).
437 @kwarg arg: The value (any C{type} convertable to C{float}).
438 @kwarg name: Optional instance name (C{str}).
439 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
440 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
441 @kwarg high: Optional upper B{C{arg}} easting limit (C{scalar} or C{None}).
442 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
443 and B{C{arg}}.
445 @returns: An C{Easting} instance.
447 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.
448 '''
449 if name_arg:
450 name, arg = _xkwds_popitem(name_arg)
451 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
452 if high and (self < 0 or self > high): # like Veness
453 raise _Error(cls, arg, name, Error)
454 elif falsed and self < 0:
455 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)
456 return self
459class Epoch(Float_): # in .ellipsoidalBase, .trf
460 '''Named C{epoch} with optional C{low} and C{high} limits representing a fractional
461 calendar year.
462 '''
463 _std_repr = False
465 def __new__(cls, arg=None, name=_epoch_, Error=TRFError, low=1900, high=9999, **name_arg):
466 '''New L{Epoch} instance, see L{Float_}.
467 '''
468 if name_arg:
469 name, arg = _xkwds_popitem(name_arg)
470 return arg if isinstance(arg, Epoch) else Float_.__new__(cls,
471 arg=arg, name=name, Error=Error, low=low, high=high)
473 def toRepr(self, std=False, **unused): # PYCHOK prec=3, fmt=Fmt.F, ints=True
474 '''Return a representation of this C{Epoch}.
476 @kwarg std: Use the standard C{repr} or the named
477 representation (C{bool}).
479 @see: Method L{Float.toRepr} for more documentation.
480 '''
481 return Float_.toRepr(self, std=std) # prec=-3, fmt=Fmt.F, ints=True
483 def toStr(self, **unused): # PYCHOK prec=3, fmt=Fmt.F, ints=True
484 '''Format this C{Epoch} as C{str}.
486 @see: Function L{pygeodesy.fstr} for more documentation.
487 '''
488 return fstr(self, prec=-3, fmt=Fmt.F, ints=True)
490 __str__ = toStr # PYCHOK default '%.3F', with trailing zeros and decimal point
493class Feet(Float):
494 '''Named C{float} representing a distance or length in C{feet}.
495 '''
496 def __new__(cls, arg=None, name=_feet_, **Error_name_arg):
497 '''New L{Feet} instance, see L{Float}.
498 '''
499 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
502class FIx(Float_):
503 '''A named I{Fractional Index}, an C{int} or C{float} index into
504 a C{list} or C{tuple} of C{points}, typically. A C{float}
505 I{Fractional Index} C{fi} represents a location on the edge
506 between C{points[int(fi)]} and C{points[(int(fi) + 1) %
507 len(points)]}.
508 '''
509 _fin = 0
511 def __new__(cls, fi, fin=None, **name_Error):
512 '''New I{Fractional Index} in a C{list} or C{tuple} of points.
514 @arg fi: The fractional index (C{float} or C{int}).
515 @kwarg fin: Optional C{len}, the number of C{points}, the index
516 C{[n]} wrapped to C{[0]} (C{int} or C{None}).
517 @kwarg name_Error: Optional keyword argument C{B{name}=NN}
518 and C{B{Error}=UnitError}.
520 @return: The B{C{fi}} (named L{FIx}).
522 @note: The returned B{C{fi}} may exceed the B{C{flen}} of
523 the original C{points} in certain open/closed cases.
525 @see: Method L{fractional} or function L{pygeodesy.fractional}.
526 '''
527 n = Int_(fin=fin, low=0) if fin else None
528 f = Float_.__new__(cls, fi, low=_0_0, high=n, **name_Error)
529 i = int(f)
530 r = f - float(i)
531 if r < EPS: # see .points._fractional
532 f = Float_.__new__(cls, i, low=_0_0)
533 elif r > EPS1:
534 f = Float_.__new__(cls, i + 1, high=n, **name_Error)
535 if n: # non-zero and non-None
536 f._fin = n
537 return f
539 @Property_RO
540 def fin(self):
541 '''Get the given C{len}, the index C{[n]} wrapped to C{[0]} (C{int}).
542 '''
543 return self._fin
545 def fractional(self, points, wrap=None, LatLon=None, Vector=None, **kwds):
546 '''Return the point at this I{Fractional Index}.
548 @arg points: The points (C{LatLon}[], L{Numpy2LatLon}[],
549 L{Tuple2LatLon}[] or C{other}[]).
550 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
551 B{C{points}} (C{bool}) C{None} for backward
552 compatible L{LatLon2Tuple} or B{C{LatLon}} with
553 I{averaged} lat- and longitudes.
554 @kwarg LatLon: Optional class to return the I{intermediate},
555 I{fractional} point (C{LatLon}) or C{None}.
556 @kwarg Vector: Optional class to return the I{intermediate},
557 I{fractional} point (C{Cartesian}, C{Vector3d})
558 or C{None}.
559 @kwarg kwds: Optional, additional B{C{LatLon}} I{or} B{C{Vector}}
560 keyword arguments, ignored if both C{B{LatLon}} and
561 C{B{Vector}} are C{None}.
563 @return: See function L{pygeodesy.fractional}.
565 @raise IndexError: This fractional index invalid or B{C{points}}
566 not subscriptable or not closed.
568 @raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}}
569 argument.
571 @see: Function L{pygeodesy.fractional}.
572 '''
573 # fi = 0 if self == self.fin else self
574 return _MODS.points.fractional(points, self, wrap=wrap,
575 LatLon=LatLon, Vector=Vector, **kwds)
578def _fi_j2(f, n): # PYCHOK in .ellipsoidalBaseDI, .vector3d
579 # Get 2-tuple (C{fi}, C{j})
580 i = int(f) # like L{FIx}
581 if not 0 <= i < n:
582 raise _AssertionError(i=i, n=n, f=f, r=f - float(i))
583 return FIx(fi=f, fin=n), (i + 1) % n
586class Height(Float): # here to avoid circular import
587 '''Named C{float} representing a height, conventionally in C{meter}.
588 '''
589 def __new__(cls, arg=None, name=_height_, **Error_name_arg):
590 '''New L{Height} instance, see L{Float}.
591 '''
592 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
595class Height_(Float_): # here to avoid circular import
596 '''Named C{float} with optional C{low} and C{high} limits representing a height, conventionally in C{meter}.
597 '''
598 def __new__(cls, arg=None, name=_height_, **low_high_Error_name_arg):
599 '''New L{Height} instance, see L{Float}.
600 '''
601 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
604class HeightX(Height):
605 '''Like L{Height} but to distinguish the interpolated height
606 at a clip intersection from an original L{Height}.
607 '''
608 pass
611def _heigHt(inst, height):
612 '''(INTERNAL) Override the C{inst}ance' height.
613 '''
614 return inst.height if height is None else Height(height)
617class Lam(Radians):
618 '''Named C{float} representing a longitude in C{radians}.
619 '''
620 def __new__(cls, arg=None, name=_lam_, clip=PI, **Error_name_arg):
621 '''New L{Lam} instance, see L{Radians}.
622 '''
623 return Radians.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
626class Lam_(Lam):
627 '''Named C{float} representing a longitude in C{radians} converted from C{degrees}.
628 '''
629 def __new__(cls, arg=None, name=_lon_, Error=UnitError, clip=180, **name_arg):
630 '''New L{Lam_} instance, see L{Lam} and L{Radians}.
631 '''
632 if name_arg:
633 name, arg = _xkwds_popitem(name_arg)
634 d = Lam.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
635 return Radians.__new__(cls, radians(d), name=name, Error=Error)
638class Lat(Degrees):
639 '''Named C{float} representing a latitude in C{degrees}.
640 '''
641 _ddd_ = 2
642 _suf_ = _S_, S_NUL, _N_ # no zero suffix
644 def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):
645 '''New L{Lat} instnace, see L{Degrees}.
646 '''
647 return Degrees.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
650class Lat_(Degrees_):
651 '''Named C{float} representing a latitude in C{degrees} within limits C{low} and C{high}.
652 '''
653 _ddd_ = 2
654 _suf_ = _S_, S_NUL, _N_ # no zero suffix
656 def __new__(cls, arg=None, name=_lat_, low=-90, high=90, **Error_name_arg):
657 '''See L{Degrees_}.
658 '''
659 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_NS_, low=low, high=high, **Error_name_arg)
662class Lon(Degrees):
663 '''Named C{float} representing a longitude in C{degrees}.
664 '''
665 _ddd_ = 3
666 _suf_ = _W_, S_NUL, _E_ # no zero suffix
668 def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):
669 '''New L{Lon} instance, see L{Degrees}.
670 '''
671 return Degrees.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
674class Lon_(Degrees_):
675 '''Named C{float} representing a longitude in C{degrees} within limits C{low} and C{high}.
676 '''
677 _ddd_ = 3
678 _suf_ = _W_, S_NUL, _E_ # no zero suffix
680 def __new__(cls, arg=None, name=_lon_, low=-180, high=180, **Error_name_arg):
681 '''New L{Lon_} instance, see L{Lon} and L{Degrees_}.
682 '''
683 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_EW_, low=low, high=high, **Error_name_arg)
686class Meter(Float):
687 '''Named C{float} representing a distance or length in C{meter}.
688 '''
689 def __new__(cls, arg=None, name=_meter_, **Error_name_arg):
690 '''New L{Meter} instance, see L{Float}.
691 '''
692 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
694 def __repr__(self):
695 '''Return a representation of this C{Meter}.
697 @see: Method C{Str.toRepr} and property C{Str.std_repr}.
699 @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std}
700 prior to C{import pygeodesy} to get the standard
701 C{repr} or set property C{std_repr=False} to always
702 get the named C{toRepr} representation.
703 '''
704 return self.toRepr(std=self._std_repr)
707# _1Å = Meter( _Å= 1e-10) # PyCHOK 1 Ångstrōm
708_1um = Meter( _1um= 1.e-6) # PYCHOK 1 micrometer in .mgrs
709_10um = Meter( _10um= 1.e-5) # PYCHOK 10 micrometer in .osgr
710_1mm = Meter( _1mm=_0_001) # PYCHOK 1 millimeter in .ellipsoidal...
711_100km = Meter( _100km= 1.e+5) # PYCHOK 100 kilometer in .formy, .mgrs, .osgr, .sphericalBase
712_2000km = Meter(_2000km= 2.e+6) # PYCHOK 2,000 kilometer in .mgrs
715class Meter_(Float_):
716 '''Named C{float} representing a distance or length in C{meter}.
717 '''
718 def __new__(cls, arg=None, name=_meter_, low=_0_0, **high_Error_name_arg):
719 '''New L{Meter_} instance, see L{Meter} and L{Float_}.
720 '''
721 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
724class Meter2(Float_):
725 '''Named C{float} representing an area in C{meter squared}.
726 '''
727 def __new__(cls, arg=None, name=_meter2_, Error=UnitError, **name_arg):
728 '''New L{Meter2} instance, see L{Float_}.
729 '''
730 return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)
733class Meter3(Float_):
734 '''Named C{float} representing a volume in C{meter cubed}.
735 '''
736 def __new__(cls, arg=None, name='meter3', **Error_name_arg):
737 '''New L{Meter3} instance, see L{Float_}.
738 '''
739 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)
742class Northing(Float):
743 '''Named C{float} representing a northing, conventionally in C{meter}.
744 '''
745 def __new__(cls, arg=None, name=_northing_, Error=UnitError, falsed=False, high=None, **name_arg):
746 '''New C{Northing} or C{Northing of point} instance.
748 @arg cls: This class (C{Northing} or sub-class).
749 @kwarg arg: The value (any C{type} convertable to C{float}).
750 @kwarg name: Optional instance name (C{str}).
751 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
752 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
753 @kwarg high: Optional upper B{C{arg}} northing limit (C{scalar} or C{None}).
754 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
755 and B{C{arg}}.
757 @returns: A C{Northing} instance.
759 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.
760 '''
761 if name_arg:
762 name, arg = _xkwds_popitem(name_arg)
763 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
764 if high and (self < 0 or self > high):
765 raise _Error(cls, arg, name, Error)
766 elif falsed and self < 0:
767 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)
768 return self
771class Number_(Int_):
772 '''Named C{int} representing a non-negative number.
773 '''
774 def __new__(cls, arg=None, name=_number_, **low_high_Error_name_arg):
775 '''New L{Number_} instance, see L{Int_}.
776 '''
777 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
780class Phi(Radians):
781 '''Named C{float} representing a latitude in C{radians}.
782 '''
783 def __new__(cls, arg=None, name=_phi_, clip=PI_2, **Error_name_arg):
784 '''New L{Phi} instance, see L{Radians}.
785 '''
786 return Radians.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
789class Phi_(Phi):
790 '''Named C{float} representing a latitude in C{radians} converted from C{degrees}.
791 '''
792 def __new__(cls, arg=None, name=_lat_, Error=UnitError, clip=90, **name_arg):
793 '''New L{Phi_} instance, see L{Phi} and L{Radians}.
794 '''
795 if name_arg:
796 name, arg = _xkwds_popitem(name_arg)
797 d = Phi.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
798 return Radians.__new__(cls, arg=radians(d), name=name, Error=Error)
801class Precision_(Int_):
802 '''Named C{int} with optional C{low} and C{high} limits representing a precision.
803 '''
804 def __new__(cls, arg=None, name=_precision_, **low_high_Error_name_arg):
805 '''New L{Precision_} instance, see L{Int_}.
806 '''
807 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
810class Radius_(Float_):
811 '''Named C{float} with optional C{low} and C{high} limits representing a radius, conventionally in C{meter}.
812 '''
813 def __new__(cls, arg=None, name=_radius_, **low_high_Error_name_arg):
814 '''New L{Radius_} instance, see L{Radius} and L{Float}.
815 '''
816 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
819class Scalar(Float):
820 '''Named C{float} representing a factor, fraction, scale, etc.
821 '''
822 def __new__(cls, arg=None, name=_scalar_, **Error_name_arg):
823 '''New L{Scalar} instance, see L{Float}.
824 '''
825 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
828class Scalar_(Float_):
829 '''Named C{float} with optional C{low} and C{high} limits representing a factor, fraction, scale, etc.
830 '''
831 def __new__(cls, arg=None, name=_scalar_, low=_0_0, **high_Error_name_arg):
832 '''New L{Scalar_} instance, see L{Scalar} and L{Float_}.
833 '''
834 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
837class Zone(Int):
838 '''Named C{int} representing a UTM/UPS zone number.
839 '''
840 def __new__(cls, arg=None, name=_zone_, **Error_name_arg):
841 '''New L{Zone} instance, see L{Int}
842 '''
843 # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX
844 return Int_.__new__(cls, arg=arg, name=name, **Error_name_arg)
847def _xStrError(*Refs, **name_value_Error):
848 '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}.
849 '''
850 r = tuple(r.__name__ for r in Refs) + (Str.__name__, _LatLon_, 'LatLon*Tuple')
851 return _IsnotError(*r, **name_value_Error)
854def _xUnit(units, Base): # in .frechet, .hausdorff
855 '''(INTERNAL) Get C{Unit} from C{Unit} or C{name}, ortherwise C{Base}.
856 '''
857 if not issubclassof(Base, _NamedUnit):
858 raise _IsnotError(_NamedUnit.__name__, Base=Base)
859 U = globals().get(units.capitalize(), Base) if isstr(units) else (
860 units if issubclassof(units, Base) else Base)
861 return U if issubclassof(U, Base) else Base
864def _xUnits(units, Base=_NamedUnit): # in .frechet, .hausdorff
865 '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}.
866 '''
867 if not issubclassof(Base, _NamedUnit):
868 raise _IsnotError(_NamedUnit.__name__, Base=Base)
869 elif issubclassof(units, Base):
870 return units
871 elif isstr(units):
872 return Str(units, name=_units_) # XXX Str to _Pass and for backward compatibility
873 else:
874 raise _IsnotError(Base.__name__, Str.__name__, str.__name__, units=units)
877def _std_repr(*Classes):
878 '''(INTERNAL) Use standard C{repr} or named C{toRepr}.
879 '''
880 for C in Classes:
881 if hasattr(C, _std_repr.__name__): # PYCHOK del _std_repr
882 env = 'PYGEODESY_%s_STD_REPR' % (C.__name__.upper(),)
883 if _getenv(env, _std_).lower() != _std_:
884 C._std_repr = False
886_std_repr(Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected
887del _std_repr
889__all__ += _ALL_DOCS(_NamedUnit)
891# **) MIT License
892#
893# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
894#
895# Permission is hereby granted, free of charge, to any person obtaining a
896# copy of this software and associated documentation files (the "Software"),
897# to deal in the Software without restriction, including without limitation
898# the rights to use, copy, modify, merge, publish, distribute, sublicense,
899# and/or sell copies of the Software, and to permit persons to whom the
900# Software is furnished to do so, subject to the following conditions:
901#
902# The above copyright notice and this permission notice shall be included
903# in all copies or substantial portions of the Software.
904#
905# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
906# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
907# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
908# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
909# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
910# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
911# OTHER DEALINGS IN THE SOFTWARE.