Coverage for pygeodesy/units.py: 94%
297 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-04-12 11:45 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2023-04-12 11:45 -0400
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.03.18'
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: Wrap and unroll longitudes (C{bool}) or C{None} for
551 backward compatible L{LatLon2Tuple} or B{C{LatLon}}
552 with averaged lat- and longitudes.
553 @kwarg LatLon: Optional class to return the I{intermediate},
554 I{fractional} point (C{LatLon}) or C{None}.
555 @kwarg Vector: Optional class to return the I{intermediate},
556 I{fractional} point (C{Cartesian}, C{Vector3d})
557 or C{None}.
558 @kwarg kwds: Optional, additional B{C{LatLon}} I{or} B{C{Vector}}
559 keyword arguments, ignored if both C{B{LatLon}} and
560 C{B{Vector}} are C{None}.
562 @return: See function L{pygeodesy.fractional}.
564 @raise IndexError: This fractional index invalid or B{C{points}}
565 not subscriptable or not closed.
567 @raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}}
568 argument.
570 @see: Function L{pygeodesy.fractional}.
571 '''
572 # fi = 0 if self == self.fin else self
573 return _MODS.points.fractional(points, self, wrap=wrap,
574 LatLon=LatLon, Vector=Vector, **kwds)
577def _fi_j2(f, n): # PYCHOK in .ellipsoidalBaseDI, .vector3d
578 # Get 2-tuple (C{fi}, C{j})
579 i = int(f) # like L{FIx}
580 if not 0 <= i < n:
581 raise _AssertionError(i=i, n=n, f=f, r=f - float(i))
582 return FIx(fi=f, fin=n), (i + 1) % n
585class Height(Float): # here to avoid circular import
586 '''Named C{float} representing a height, conventionally in C{meter}.
587 '''
588 def __new__(cls, arg=None, name=_height_, **Error_name_arg):
589 '''New L{Height} instance, see L{Float}.
590 '''
591 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
594class Height_(Float_): # here to avoid circular import
595 '''Named C{float} with optional C{low} and C{high} limits representing a height, conventionally in C{meter}.
596 '''
597 def __new__(cls, arg=None, name=_height_, **low_high_Error_name_arg):
598 '''New L{Height} instance, see L{Float}.
599 '''
600 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
603class HeightX(Height):
604 '''Like L{Height} but to distinguish the interpolated height
605 at a clip intersection from an original L{Height}.
606 '''
607 pass
610class Lam(Radians):
611 '''Named C{float} representing a longitude in C{radians}.
612 '''
613 def __new__(cls, arg=None, name=_lam_, clip=PI, **Error_name_arg):
614 '''New L{Lam} instance, see L{Radians}.
615 '''
616 return Radians.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
619class Lam_(Lam):
620 '''Named C{float} representing a longitude in C{radians} converted from C{degrees}.
621 '''
622 def __new__(cls, arg=None, name=_lon_, Error=UnitError, clip=180, **name_arg):
623 '''New L{Lam_} instance, see L{Lam} and L{Radians}.
624 '''
625 if name_arg:
626 name, arg = _xkwds_popitem(name_arg)
627 d = Lam.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
628 return Radians.__new__(cls, radians(d), name=name, Error=Error)
631class Lat(Degrees):
632 '''Named C{float} representing a latitude in C{degrees}.
633 '''
634 _ddd_ = 2
635 _suf_ = _S_, S_NUL, _N_ # no zero suffix
637 def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):
638 '''New L{Lat} instnace, see L{Degrees}.
639 '''
640 return Degrees.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
643class Lat_(Degrees_):
644 '''Named C{float} representing a latitude in C{degrees} within limits C{low} and C{high}.
645 '''
646 _ddd_ = 2
647 _suf_ = _S_, S_NUL, _N_ # no zero suffix
649 def __new__(cls, arg=None, name=_lat_, low=-90, high=90, **Error_name_arg):
650 '''See L{Degrees_}.
651 '''
652 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_NS_, low=low, high=high, **Error_name_arg)
655class Lon(Degrees):
656 '''Named C{float} representing a longitude in C{degrees}.
657 '''
658 _ddd_ = 3
659 _suf_ = _W_, S_NUL, _E_ # no zero suffix
661 def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):
662 '''New L{Lon} instance, see L{Degrees}.
663 '''
664 return Degrees.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
667class Lon_(Degrees_):
668 '''Named C{float} representing a longitude in C{degrees} within limits C{low} and C{high}.
669 '''
670 _ddd_ = 3
671 _suf_ = _W_, S_NUL, _E_ # no zero suffix
673 def __new__(cls, arg=None, name=_lon_, low=-180, high=180, **Error_name_arg):
674 '''New L{Lon_} instance, see L{Lon} and L{Degrees_}.
675 '''
676 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_EW_, low=low, high=high, **Error_name_arg)
679class Meter(Float):
680 '''Named C{float} representing a distance or length in C{meter}.
681 '''
682 def __new__(cls, arg=None, name=_meter_, **Error_name_arg):
683 '''New L{Meter} instance, see L{Float}.
684 '''
685 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
687 def __repr__(self):
688 '''Return a representation of this C{Meter}.
690 @see: Method C{Str.toRepr} and property C{Str.std_repr}.
692 @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std}
693 prior to C{import pygeodesy} to get the standard
694 C{repr} or set property C{std_repr=False} to always
695 get the named C{toRepr} representation.
696 '''
697 return self.toRepr(std=self._std_repr)
700# _1Å = Meter( _Å= 1e-10) # PyCHOK 1 Ångstrōm
701_1um = Meter( _1um= 1.e-6) # PYCHOK 1 micrometer in .mgrs
702_10um = Meter( _10um= 1.e-5) # PYCHOK 10 micrometer in .osgr
703_1mm = Meter( _1mm=_0_001) # PYCHOK 1 millimeter in .ellipsoidal...
704_100km = Meter( _100km= 1.e+5) # PYCHOK 100 kilometer in .formy, .mgrs, .osgr
705_2000km = Meter(_2000km= 2.e+6) # PYCHOK 2,000 kilometer in .mgrs
708class Meter_(Float_):
709 '''Named C{float} representing a distance or length in C{meter}.
710 '''
711 def __new__(cls, arg=None, name=_meter_, low=_0_0, **high_Error_name_arg):
712 '''New L{Meter_} instance, see L{Meter} and L{Float_}.
713 '''
714 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
717class Meter2(Float_):
718 '''Named C{float} representing an area in C{meter squared}.
719 '''
720 def __new__(cls, arg=None, name=_meter2_, Error=UnitError, **name_arg):
721 '''New L{Meter2} instance, see L{Float_}.
722 '''
723 return Float_.__new__(cls, arg=arg, name=name, Error=Error, low=_0_0, **name_arg)
726class Meter3(Float_):
727 '''Named C{float} representing a volume in C{meter cubed}.
728 '''
729 def __new__(cls, arg=None, name='meter3', **Error_name_arg):
730 '''New L{Meter3} instance, see L{Float_}.
731 '''
732 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)
735class Northing(Float):
736 '''Named C{float} representing a northing, conventionally in C{meter}.
737 '''
738 def __new__(cls, arg=None, name=_northing_, Error=UnitError, falsed=False, high=None, **name_arg):
739 '''New C{Northing} or C{Northing of point} instance.
741 @arg cls: This class (C{Northing} or sub-class).
742 @kwarg arg: The value (any C{type} convertable to C{float}).
743 @kwarg name: Optional instance name (C{str}).
744 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
745 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
746 @kwarg high: Optional upper B{C{arg}} northing limit (C{scalar} or C{None}).
747 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of B{C{name}}
748 and B{C{arg}}.
750 @returns: A C{Northing} instance.
752 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.
753 '''
754 if name_arg:
755 name, arg = _xkwds_popitem(name_arg)
756 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
757 if high and (self < 0 or self > high):
758 raise _Error(cls, arg, name, Error)
759 elif falsed and self < 0:
760 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)
761 return self
764class Number_(Int_):
765 '''Named C{int} representing a non-negative number.
766 '''
767 def __new__(cls, arg=None, name=_number_, **low_high_Error_name_arg):
768 '''New L{Number_} instance, see L{Int_}.
769 '''
770 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
773class Phi(Radians):
774 '''Named C{float} representing a latitude in C{radians}.
775 '''
776 def __new__(cls, arg=None, name=_phi_, clip=PI_2, **Error_name_arg):
777 '''New L{Phi} instance, see L{Radians}.
778 '''
779 return Radians.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
782class Phi_(Phi):
783 '''Named C{float} representing a latitude in C{radians} converted from C{degrees}.
784 '''
785 def __new__(cls, arg=None, name=_lat_, Error=UnitError, clip=90, **name_arg):
786 '''New L{Phi_} instance, see L{Phi} and L{Radians}.
787 '''
788 if name_arg:
789 name, arg = _xkwds_popitem(name_arg)
790 d = Phi.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
791 return Radians.__new__(cls, arg=radians(d), name=name, Error=Error)
794class Precision_(Int_):
795 '''Named C{int} with optional C{low} and C{high} limits representing a precision.
796 '''
797 def __new__(cls, arg=None, name=_precision_, **low_high_Error_name_arg):
798 '''New L{Precision_} instance, see L{Int_}.
799 '''
800 return Int_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
803class Radius_(Float_):
804 '''Named C{float} with optional C{low} and C{high} limits representing a radius, conventionally in C{meter}.
805 '''
806 def __new__(cls, arg=None, name=_radius_, **low_high_Error_name_arg):
807 '''New L{Radius_} instance, see L{Radius} and L{Float}.
808 '''
809 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
812class Scalar(Float):
813 '''Named C{float} representing a factor, fraction, scale, etc.
814 '''
815 def __new__(cls, arg=None, name=_scalar_, **Error_name_arg):
816 '''New L{Scalar} instance, see L{Float}.
817 '''
818 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
821class Scalar_(Float_):
822 '''Named C{float} with optional C{low} and C{high} limits representing a factor, fraction, scale, etc.
823 '''
824 def __new__(cls, arg=None, name=_scalar_, low=_0_0, **high_Error_name_arg):
825 '''New L{Scalar_} instance, see L{Scalar} and L{Float_}.
826 '''
827 return Float_.__new__(cls, arg=arg, name=name, low=low, **high_Error_name_arg)
830class Zone(Int):
831 '''Named C{int} representing a UTM/UPS zone number.
832 '''
833 def __new__(cls, arg=None, name=_zone_, **Error_name_arg):
834 '''New L{Zone} instance, see L{Int}
835 '''
836 # usually low=_UTMUPS_ZONE_MIN, high=_UTMUPS_ZONE_MAX
837 return Int_.__new__(cls, arg=arg, name=name, **Error_name_arg)
840def _xStrError(*Refs, **name_value_Error):
841 '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}.
842 '''
843 r = tuple(r.__name__ for r in Refs) + (Str.__name__, _LatLon_, 'LatLon*Tuple')
844 return _IsnotError(*r, **name_value_Error)
847def _xUnit(units, Base): # in .frechet, .hausdorff
848 '''(INTERNAL) Get C{Unit} from C{Unit} or C{name}, ortherwise C{Base}.
849 '''
850 if not issubclassof(Base, _NamedUnit):
851 raise _IsnotError(_NamedUnit.__name__, Base=Base)
852 U = globals().get(units.capitalize(), Base) if isstr(units) else (
853 units if issubclassof(units, Base) else Base)
854 return U if issubclassof(U, Base) else Base
857def _xUnits(units, Base=_NamedUnit): # in .frechet, .hausdorff
858 '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}.
859 '''
860 if not issubclassof(Base, _NamedUnit):
861 raise _IsnotError(_NamedUnit.__name__, Base=Base)
862 elif issubclassof(units, Base):
863 return units
864 elif isstr(units):
865 return Str(units, name=_units_) # XXX Str to _Pass and for backward compatibility
866 else:
867 raise _IsnotError(Base.__name__, Str.__name__, str.__name__, units=units)
870def _std_repr(*Classes):
871 '''(INTERNAL) Use standard C{repr} or named C{toRepr}.
872 '''
873 for C in Classes:
874 if hasattr(C, _std_repr.__name__): # PYCHOK del _std_repr
875 env = 'PYGEODESY_%s_STD_REPR' % (C.__name__.upper(),)
876 if _getenv(env, _std_).lower() != _std_:
877 C._std_repr = False
879_std_repr(Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected
880del _std_repr
882__all__ += _ALL_DOCS(_NamedUnit)
884# **) MIT License
885#
886# Copyright (C) 2016-2023 -- mrJean1 at Gmail -- All Rights Reserved.
887#
888# Permission is hereby granted, free of charge, to any person obtaining a
889# copy of this software and associated documentation files (the "Software"),
890# to deal in the Software without restriction, including without limitation
891# the rights to use, copy, modify, merge, publish, distribute, sublicense,
892# and/or sell copies of the Software, and to permit persons to whom the
893# Software is furnished to do so, subject to the following conditions:
894#
895# The above copyright notice and this permission notice shall be included
896# in all copies or substantial portions of the Software.
897#
898# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
899# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
900# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
901# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
902# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
903# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
904# OTHER DEALINGS IN THE SOFTWARE.