Coverage for pygeodesy/units.py: 94%
318 statements
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
« prev ^ index » next coverage.py v7.2.2, created at 2024-06-10 14:08 -0400
2# -*- coding: utf-8 -*-
4u'''Various named units, all sub-classes of C{Float}, C{Int} or C{Str} from
5basic C{float}, C{int} respectively C{str} to named units as L{Degrees},
6L{Feet}, L{Meter}, L{Radians}, etc.
7'''
9from pygeodesy.basics import isscalar, isstr, issubclassof, signOf, _xsubclassof
10from pygeodesy.constants import EPS, EPS1, PI, PI2, PI_2, _umod_360, _0_0, \
11 _0_001, _0_5, INT0 # PYCHOK for .mgrs, .namedTuples
12from pygeodesy.dms import F__F, F__F_, S_NUL, S_SEP, parseDMS, parseRad, _toDMS
13from pygeodesy.errors import _AssertionError, _IsnotError, TRFError, UnitError, \
14 _xattr
15from pygeodesy.interns import NN, _band_, _bearing_, _degrees_, _degrees2_, \
16 _distance_, _E_, _easting_, _epoch_, _EW_, _feet_, \
17 _height_, _lam_, _lat_, _LatLon_, _lon_, _meter_, \
18 _meter2_, _N_, _northing_, _NS_, _NSEW_, _number_, \
19 _PERCENT_, _phi_, _precision_, _radians_, _radians2_, \
20 _radius_, _S_, _scalar_, _units_, _W_, _zone_, \
21 _std_ # PYCHOK used!
22from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS, _getenv
23from pygeodesy.props import Property_RO
24# from pygeodesy.streprs import Fmt, fstr # from .unitsBase
25from pygeodesy.unitsBase import _Error, Float, Fmt, fstr, Int, _arg_name_arg2, \
26 _NamedUnit, Radius, Str # PYCHOK shared .namedTuples
28from math import degrees, radians
30__all__ = _ALL_LAZY.units
31__version__ = '24.06.10'
33_negative_falsed_ = 'negative, falsed'
36class Float_(Float):
37 '''Named C{float} with optional C{low} and C{high} limit.
38 '''
39 def __new__(cls, arg=None, name=NN, Error=UnitError, low=EPS, high=None, **name_arg):
40 '''New C{Float_} instance.
42 @arg cls: This class (C{Float_} or sub-class).
43 @kwarg arg: The value (any C{type} convertable to C{float}).
44 @kwarg name: Optional instance name (C{str}).
45 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
46 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
47 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
48 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
49 B{C{arg}} and B{C{name}} ones.
51 @returns: A C{Float_} instance.
53 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
54 '''
55 if name_arg:
56 name, arg = _arg_name_arg2(arg, **name_arg)
57 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
58 if (low is not None) and self < low:
59 txt = Fmt.limit(below=Fmt.g(low, prec=6, ints=isinstance(self, Epoch)))
60 elif (high is not None) and self > high:
61 txt = Fmt.limit(above=Fmt.g(high, prec=6, ints=isinstance(self, Epoch)))
62 else:
63 return self
64 raise _Error(cls, arg, name, Error, txt=txt)
67class Int_(Int):
68 '''Named C{int} with optional limits C{low} and C{high}.
69 '''
70 def __new__(cls, arg=None, name=NN, Error=UnitError, low=0, high=None, **name_arg):
71 '''New named C{int} instance with limits.
73 @kwarg cls: This class (C{Int_} or sub-class).
74 @arg arg: The value (any C{type} convertable to C{int}).
75 @kwarg name: Optional instance name (C{str}).
76 @kwarg Error: Optional error to raise, overriding the default C{UnitError}.
77 @kwarg low: Optional lower B{C{arg}} limit (C{int} or C{None}).
78 @kwarg high: Optional upper B{C{arg}} limit (C{int} or C{None}).
79 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
80 B{C{arg}} and B{C{name}} ones.
82 @returns: An L{Int_} instance.
84 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
85 '''
86 if name_arg:
87 name, arg = _arg_name_arg2(arg, **name_arg)
88 self = Int.__new__(cls, arg=arg, name=name, Error=Error)
89 if (low is not None) and self < low:
90 txt = Fmt.limit(below=low)
91 elif (high is not None) and self > high:
92 txt = Fmt.limit(above=high)
93 else:
94 return self
95 raise _Error(cls, arg, name, Error, txt=txt)
98class Bool(Int, _NamedUnit):
99 '''Named C{bool}, a sub-class of C{int} like Python's C{bool}.
100 '''
101 # _std_repr = True # set below
102 _bool_True_or_False = None
104 def __new__(cls, arg=None, name=NN, Error=UnitError, **name_arg):
105 '''New C{Bool} instance.
107 @kwarg cls: This class (C{Bool} or sub-class).
108 @kwarg arg: The value (any C{type} convertable to C{bool}).
109 @kwarg name: Optional instance name (C{str}).
110 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
111 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
112 B{C{name}} and B{C{arg}}.
114 @returns: A L{Bool}, a C{bool}-like instance.
116 @raise Error: Invalid B{C{arg}}.
117 '''
118 if name_arg:
119 name, arg = _arg_name_arg2(arg, **name_arg)
120 try:
121 b = bool(arg)
122 except Exception as x: # XXX not ... as x:
123 raise _Error(cls, arg, name, Error, x=x)
125 self = Int.__new__(cls, b, name=name, Error=Error)
126 self._bool_True_or_False = b
127 return self
129 # <https://StackOverflow.com/questions/9787890/assign-class-boolean-value-in-python>
130 def __bool__(self): # PYCHOK Python 3+
131 return self._bool_True_or_False
133 __nonzero__ = __bool__ # PYCHOK Python 2-
135 def toRepr(self, std=False, **unused): # PYCHOK **unused
136 '''Return a representation of this C{Bool}.
138 @kwarg std: Use the standard C{repr} or the named representation (C{bool}).
140 @note: Use C{env} variable C{PYGEODESY_BOOL_STD_REPR=std} prior to C{import
141 pygeodesy} to get the standard C{repr} or set property C{std_repr=False}
142 to always get the named C{toRepr} representation.
143 '''
144 r = repr(self._bool_True_or_False)
145 return r if std else self._toRepr(r)
147 def toStr(self, **unused): # PYCHOK **unused
148 '''Return this C{Bool} as standard C{str}.
149 '''
150 return str(self._bool_True_or_False)
153class Band(Str):
154 '''Named C{str} representing a UTM/UPS band letter, unchecked.
155 '''
156 def __new__(cls, arg=None, name=_band_, **Error_name_arg):
157 '''New L{Band} instance, see L{Str}.
158 '''
159 return Str.__new__(cls, arg=arg, name=name, **Error_name_arg)
162class Degrees(Float):
163 '''Named C{float} representing a coordinate in C{degrees}, optionally clipped.
164 '''
165 _ddd_ = 1 # default for .dms._toDMS
166 _sep_ = S_SEP
167 _suf_ = (S_NUL,) * 3
169 def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, clip=0, wrap=None, **name_arg):
170 '''New C{Degrees} instance, see L{Float}.
172 @arg cls: This class (C{Degrees} or sub-class).
173 @kwarg arg: The value (any scalar C{type} convertable to C{float} or parsable
174 by L{pygeodesy.parseDMS}).
175 @kwarg name: Optional instance name (C{str}).
176 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
177 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
178 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}} (C{degrees} or C{0}
179 or C{None} for unclipped).
180 @kwarg wrap: Optionally adjust the B{C{arg}} value (L{pygeodesy.wrap90},
181 L{pygeodesy.wrap180} or L{pygeodesy.wrap360}).
182 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
183 B{C{name}} and B{C{arg}}.
185 @returns: A C{Degrees} instance.
187 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}
188 range and L{pygeodesy.rangerrors} set to C{True}.
189 '''
190 if name_arg:
191 name, arg = _arg_name_arg2(arg, **name_arg)
192 try:
193 d = Float.__new__(cls, parseDMS(arg, suffix=suffix, clip=clip),
194 Error=Error, name=name)
195 if wrap:
196 w = wrap(d)
197 if w != d:
198 d = Float.__new__(cls, arg=w, name=name, Error=Error)
199 except Exception as x:
200 raise _Error(cls, arg, name, Error, x=x)
201 return d
203 def toDegrees(self):
204 '''Convert C{Degrees} to C{Degrees}.
205 '''
206 return self
208 def toRadians(self):
209 '''Convert C{Degrees} to C{Radians}.
210 '''
211 return Radians(radians(self), name=self.name)
213 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...
214 '''Return a representation of this C{Degrees}.
216 @kwarg std: If C{True} return the standard C{repr}, otherwise
217 the named representation (C{bool}).
219 @see: Methods L{Degrees.toStr}, L{Float.toRepr} and function
220 L{pygeodesy.toDMS} for futher C{prec_fmt_ints} details.
221 '''
222 return Float.toRepr(self, std=std, **prec_fmt_ints)
224 def toStr(self, prec=None, fmt=F__F_, ints=False, **s_D_M_S): # PYCHOK prec=8, ...
225 '''Return this C{Degrees} as standard C{str}.
227 @see: Function L{pygeodesy.toDMS} for futher details.
228 '''
229 if fmt.startswith(_PERCENT_): # use regular formatting
230 p = 8 if prec is None else prec
231 return fstr(self, prec=p, fmt=fmt, ints=ints, sep=self._sep_)
232 else:
233 s = self._suf_[signOf(self) + 1]
234 return _toDMS(self, fmt, prec, self._sep_, self._ddd_, s, s_D_M_S)
237class Degrees_(Degrees):
238 '''Named C{Degrees} representing a coordinate in C{degrees} with optional limits C{low} and C{high}.
239 '''
240 def __new__(cls, arg=None, name=_degrees_, Error=UnitError, suffix=_NSEW_, low=None, high=None, **name_arg):
241 '''New C{Degrees_} instance, see L{Degrees} and L{Float}..
243 @arg cls: This class (C{Degrees_} or sub-class).
244 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by
245 L{pygeodesy.parseDMS}).
246 @kwarg name: Optional instance name (C{str}).
247 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
248 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
249 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
250 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
251 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
252 B{C{arg}} and B{C{name}} ones.
254 @returns: A C{Degrees} instance.
256 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
257 '''
258 if name_arg:
259 name, arg = _arg_name_arg2(arg, **name_arg)
260 self = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
261 if (low is not None) and self < low:
262 txt = Fmt.limit(below=low)
263 elif (high is not None) and self > high:
264 txt = Fmt.limit(above=high)
265 else:
266 return self
267 raise _Error(cls, arg, name, Error, txt=txt)
270class Degrees2(Float):
271 '''Named C{float} representing a distance in C{degrees squared}.
272 '''
273 def __new__(cls, arg=None, name=_degrees2_, **Error_name_arg):
274 '''See L{Float}.
275 '''
276 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
279class Radians(Float):
280 '''Named C{float} representing a coordinate in C{radians}, optionally clipped.
281 '''
282 def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, clip=0, **name_arg):
283 '''New C{Radians} instance, see L{Float}.
285 @arg cls: This class (C{Radians} or sub-class).
286 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by
287 L{pygeodesy.parseRad}).
288 @kwarg name: Optional instance name (C{str}).
289 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
290 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
291 @kwarg clip: Optional B{C{arg}} range B{C{-clip..+clip}} (C{radians} or C{0}
292 or C{None} for unclipped).
293 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
294 B{C{arg}} and B{C{name}} ones.
296 @returns: A C{Radians} instance.
298 @raise Error: Invalid B{C{arg}} or B{C{abs(arg)}} outside the B{C{clip}}
299 range and L{pygeodesy.rangerrors} set to C{True}.
300 '''
301 if name_arg:
302 name, arg = _arg_name_arg2(arg, **name_arg)
303 try:
304 return Float.__new__(cls, parseRad(arg, suffix=suffix, clip=clip),
305 Error=Error, name=name)
306 except Exception as x:
307 raise _Error(cls, arg, name, Error, x=x)
309 def toDegrees(self):
310 '''Convert C{Radians} to C{Degrees}.
311 '''
312 return Degrees(degrees(self), name=self.name)
314 def toRadians(self):
315 '''Convert C{Radians} to C{Radians}.
316 '''
317 return self
319 def toRepr(self, std=False, **prec_fmt_ints): # PYCHOK prec=8, ...
320 '''Return a representation of this C{Radians}.
322 @kwarg std: If C{True} return the standard C{repr}, otherwise
323 the named representation (C{bool}).
325 @see: Methods L{Radians.toStr}, L{Float.toRepr} and function
326 L{pygeodesy.toDMS} for more documentation.
327 '''
328 return Float.toRepr(self, std=std, **prec_fmt_ints)
330 def toStr(self, prec=8, fmt=F__F, ints=False): # PYCHOK prec=8, ...
331 '''Return this C{Radians} as standard C{str}.
333 @see: Function L{pygeodesy.fstr} for keyword argument details.
334 '''
335 return fstr(self, prec=prec, fmt=fmt, ints=ints)
338class Radians_(Radians):
339 '''Named C{float} representing a coordinate in C{radians} with optional limits C{low} and C{high}.
340 '''
341 def __new__(cls, arg=None, name=_radians_, Error=UnitError, suffix=_NSEW_, low=_0_0, high=PI2, **name_arg):
342 '''New C{Radians_} instance.
344 @arg cls: This class (C{Radians_} or sub-class).
345 @kwarg arg: The value (any C{type} convertable to C{float} or parsable by
346 L{pygeodesy.parseRad}).
347 @kwarg name: Optional instance name (C{str}).
348 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
349 @kwarg suffix: Optional, valid compass direction suffixes (C{NSEW}).
350 @kwarg low: Optional lower B{C{arg}} limit (C{float} or C{None}).
351 @kwarg high: Optional upper B{C{arg}} limit (C{float} or C{None}).
352 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
353 B{C{arg}} and B{C{name}} ones.
355 @returns: A C{Radians_} instance.
357 @raise Error: Invalid B{C{arg}} or B{C{arg}} below B{C{low}} or above B{C{high}}.
358 '''
359 if name_arg:
360 name, arg = _arg_name_arg2(arg, **name_arg)
361 self = Radians.__new__(cls, arg=arg, name=name, Error=Error, suffix=suffix, clip=0)
362 if (low is not None) and self < low:
363 txt = Fmt.limit(below=low)
364 elif (high is not None) and self > high:
365 txt = Fmt.limit(above=high)
366 else:
367 return self
368 raise _Error(cls, arg, name, Error, txt=txt)
371class Radians2(Float_):
372 '''Named C{float} representing a distance in C{radians squared}.
373 '''
374 def __new__(cls, arg=None, name=_radians2_, **Error_name_arg):
375 '''New L{Radians2} instance, see L{Float_}.
376 '''
377 return Float_.__new__(cls, arg=arg, name=name, low=_0_0, **Error_name_arg)
380class Bearing(Degrees):
381 '''Named C{float} representing a bearing in compass C{degrees} from (true) North.
382 '''
383 _ddd_ = 1
384 _suf_ = _N_ * 3 # always suffix N
386 def __new__(cls, arg=None, name=_bearing_, Error=UnitError, clip=0, **name_arg):
387 '''New L{Bearing} instance, see L{Degrees}.
388 '''
389 if name_arg:
390 name, arg = _arg_name_arg2(arg, **name_arg)
391 d = Degrees.__new__(cls, arg=arg, name=name, Error=Error, suffix=_N_, clip=clip)
392 b = _umod_360(d) # 0 <= b < 360
393 return d if b == d else Degrees.__new__(cls, arg=b, name=name, Error=Error)
396class Bearing_(Radians):
397 '''Named C{float} representing a bearing in C{radians} from compass C{degrees} from (true) North.
398 '''
399 def __new__(cls, arg=None, name=_bearing_, clip=0, **Error_name_arg):
400 '''New L{Bearing_} instance, see L{Bearing} and L{Radians}.
401 '''
402 d = Bearing.__new__(cls, arg=arg, name=name, clip=clip, **Error_name_arg)
403 return Radians.__new__(cls, radians(d), name=name)
406class Distance(Float):
407 '''Named C{float} representing a distance, conventionally in C{meter}.
408 '''
409 def __new__(cls, arg=None, name=_distance_, **Error_name_arg):
410 '''New L{Distance} instance, see L{Float}.
411 '''
412 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
415class Distance_(Float_):
416 '''Named C{float} with optional C{low} and C{high} limits representing a distance, conventionally in C{meter}.
417 '''
418 def __new__(cls, arg=None, name=_distance_, **low_high_Error_name_arg):
419 '''New L{Distance_} instance, see L{Float}.
420 '''
421 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
424class Easting(Float):
425 '''Named C{float} representing an easting, conventionally in C{meter}.
426 '''
427 def __new__(cls, arg=None, name=_easting_, Error=UnitError, falsed=False, high=None, **name_arg):
428 '''New named C{Easting} or C{Easting of Point} instance.
430 @arg cls: This class (C{Easting} or sub-class).
431 @kwarg arg: The value (any C{type} convertable to C{float}).
432 @kwarg name: Optional instance name (C{str}).
433 @kwarg Error: Optional error to raise, overriding the default L{UnitError}.
434 @kwarg falsed: The B{C{arg}} value includes false origin (C{bool}).
435 @kwarg high: Optional upper B{C{arg}} easting limit (C{scalar} or C{None}).
436 @kwarg name_arg: Optional C{name=arg} keyword argument, inlieu of separate
437 B{C{arg}} and B{C{name}} ones.
439 @returns: An C{Easting} instance.
441 @raise Error: Invalid B{C{arg}}, above B{C{high}} or negative, falsed B{C{arg}}.
442 '''
443 if name_arg:
444 name, arg = _arg_name_arg2(arg, **name_arg)
445 self = Float.__new__(cls, arg=arg, name=name, Error=Error)
446 if high and (self < 0 or self > high): # like Veness
447 raise _Error(cls, arg, name, Error)
448 elif falsed and self < 0:
449 raise _Error(cls, arg, name, Error, txt=_negative_falsed_)
450 return self
453class Epoch(Float_): # in .ellipsoidalBase, .trf
454 '''Named C{epoch} with optional C{low} and C{high} limits representing a fractional
455 calendar year.
456 '''
457 _std_repr = False
459 def __new__(cls, arg=None, name=_epoch_, Error=TRFError, low=1900, high=9999, **name_arg):
460 '''New L{Epoch} instance, see L{Float_}.
461 '''
462 if name_arg:
463 name, arg = _arg_name_arg2(arg, **name_arg)
464 return arg if isinstance(arg, Epoch) else Float_.__new__(cls,
465 arg=arg, name=name, Error=Error, low=low, high=high)
467 def toRepr(self, prec=3, std=False, **unused): # PYCHOK fmt=Fmt.F, ints=True
468 '''Return a representation of this C{Epoch}.
470 @kwarg std: Use the standard C{repr} or the named
471 representation (C{bool}).
473 @see: Method L{Float.toRepr} for more documentation.
474 '''
475 return Float_.toRepr(self, prec=prec, std=std) # fmt=Fmt.F, ints=True
477 def toStr(self, prec=3, **unused): # PYCHOK fmt=Fmt.F, ints=True
478 '''Format this C{Epoch} as C{str}.
480 @see: Function L{pygeodesy.fstr} for more documentation.
481 '''
482 return fstr(self, prec=prec, fmt=Fmt.F, ints=True)
484 __str__ = toStr # PYCHOK default '%.3F', with trailing zeros and decimal point
487class Feet(Float):
488 '''Named C{float} representing a distance or length in C{feet}.
489 '''
490 def __new__(cls, arg=None, name=_feet_, **Error_name_arg):
491 '''New L{Feet} instance, see L{Float}.
492 '''
493 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
496class FIx(Float_):
497 '''A named I{Fractional Index}, an C{int} or C{float} index into
498 a C{list} or C{tuple} of C{points}, typically. A C{float}
499 I{Fractional Index} C{fi} represents a location on the edge
500 between C{points[int(fi)]} and C{points[(int(fi) + 1) %
501 len(points)]}.
502 '''
503 _fin = 0
505 def __new__(cls, fi, fin=None, **name_Error):
506 '''New I{Fractional Index} in a C{list} or C{tuple} of points.
508 @arg fi: The fractional index (C{float} or C{int}).
509 @kwarg fin: Optional C{len}, the number of C{points}, the index
510 C{[n]} wrapped to C{[0]} (C{int} or C{None}).
511 @kwarg name_Error: Optional C{B{name}=NN} (C{str}) and keyword
512 argument C{B{Error}=UnitError}.
514 @return: The B{C{fi}} (named L{FIx}).
516 @note: The returned B{C{fi}} may exceed the B{C{flen}} of
517 the original C{points} in certain open/closed cases.
519 @see: Method L{fractional} or function L{pygeodesy.fractional}.
520 '''
521 n = Int_(fin=fin, low=0) if fin else None
522 f = Float_.__new__(cls, fi, low=_0_0, high=n, **name_Error)
523 i = int(f)
524 r = f - float(i)
525 if r < EPS: # see .points._fractional
526 f = Float_.__new__(cls, i, low=_0_0)
527 elif r > EPS1:
528 f = Float_.__new__(cls, i + 1, high=n, **name_Error)
529 if n: # non-zero and non-None
530 f._fin = n
531 return f
533 @Property_RO
534 def fin(self):
535 '''Get the given C{len}, the index C{[n]} wrapped to C{[0]} (C{int}).
536 '''
537 return self._fin
539 def fractional(self, points, wrap=None, LatLon=None, Vector=None, **kwds):
540 '''Return the point at this I{Fractional Index}.
542 @arg points: The points (C{LatLon}[], L{Numpy2LatLon}[],
543 L{Tuple2LatLon}[] or C{other}[]).
544 @kwarg wrap: If C{True}, wrap or I{normalize} and unroll the
545 B{C{points}} (C{bool}) or C{None} for backward
546 compatible L{LatLon2Tuple} or B{C{LatLon}} with
547 I{averaged} lat- and longitudes.
548 @kwarg LatLon: Optional class to return the I{intermediate},
549 I{fractional} point (C{LatLon}) or C{None}.
550 @kwarg Vector: Optional class to return the I{intermediate},
551 I{fractional} point (C{Cartesian}, C{Vector3d})
552 or C{None}.
553 @kwarg kwds: Optional, additional B{C{LatLon}} I{or} B{C{Vector}}
554 keyword arguments, ignored if both C{B{LatLon}} and
555 C{B{Vector}} are C{None}.
557 @return: See function L{pygeodesy.fractional}.
559 @raise IndexError: This fractional index invalid or B{C{points}}
560 not subscriptable or not closed.
562 @raise TypeError: Invalid B{C{LatLon}}, B{C{Vector}} or B{C{kwds}}
563 argument.
565 @see: Function L{pygeodesy.fractional}.
566 '''
567 # fi = 0 if self == self.fin else self
568 return _MODS.points.fractional(points, self, wrap=wrap,
569 LatLon=LatLon, Vector=Vector, **kwds)
572def _fi_j2(f, n): # PYCHOK in .ellipsoidalBaseDI, .vector3d
573 # Get 2-tuple (C{fi}, C{j})
574 i = int(f) # like L{FIx}
575 if not 0 <= i < n:
576 raise _AssertionError(i=i, n=n, f=f, r=f - float(i))
577 return FIx(fi=f, fin=n), (i + 1) % n
580class Height(Float): # here to avoid circular import
581 '''Named C{float} representing a height, conventionally in C{meter}.
582 '''
583 def __new__(cls, arg=None, name=_height_, **Error_name_arg):
584 '''New L{Height} instance, see L{Float}.
585 '''
586 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
589class Height_(Float_): # here to avoid circular import
590 '''Named C{float} with optional C{low} and C{high} limits representing a height, conventionally in C{meter}.
591 '''
592 def __new__(cls, arg=None, name=_height_, **low_high_Error_name_arg):
593 '''New L{Height} instance, see L{Float}.
594 '''
595 return Float_.__new__(cls, arg=arg, name=name, **low_high_Error_name_arg)
598class HeightX(Height):
599 '''Like L{Height}, used to distinguish the interpolated height
600 from an original L{Height} at a clip intersection.
601 '''
602 pass
605def _heigHt(inst, height):
606 '''(INTERNAL) Override the C{inst}ance' height.
607 '''
608 return inst.height if height is None else Height(height)
611class Lam(Radians):
612 '''Named C{float} representing a longitude in C{radians}.
613 '''
614 def __new__(cls, arg=None, name=_lam_, clip=PI, **Error_name_arg):
615 '''New L{Lam} instance, see L{Radians}.
616 '''
617 return Radians.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
620class Lam_(Lam):
621 '''Named C{float} representing a longitude in C{radians} converted from C{degrees}.
622 '''
623 def __new__(cls, arg=None, name=_lon_, Error=UnitError, clip=180, **name_arg):
624 '''New L{Lam_} instance, see L{Lam} and L{Radians}.
625 '''
626 if name_arg:
627 name, arg = _arg_name_arg2(arg, **name_arg)
628 d = Lam.__new__(cls, arg=arg, name=name, Error=Error, clip=clip)
629 return Radians.__new__(cls, radians(d), name=name, Error=Error)
632class Lat(Degrees):
633 '''Named C{float} representing a latitude in C{degrees}.
634 '''
635 _ddd_ = 2
636 _suf_ = _S_, S_NUL, _N_ # no zero suffix
638 def __new__(cls, arg=None, name=_lat_, clip=90, **Error_name_arg):
639 '''New L{Lat} instnace, see L{Degrees}.
640 '''
641 return Degrees.__new__(cls, arg=arg, name=name, suffix=_NS_, clip=clip, **Error_name_arg)
644class Lat_(Degrees_):
645 '''Named C{float} representing a latitude in C{degrees} within limits C{low} and C{high}.
646 '''
647 _ddd_ = 2
648 _suf_ = _S_, S_NUL, _N_ # no zero suffix
650 def __new__(cls, arg=None, name=_lat_, low=-90, high=90, **Error_name_arg):
651 '''See L{Degrees_}.
652 '''
653 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_NS_, low=low, high=high, **Error_name_arg)
656class Lon(Degrees):
657 '''Named C{float} representing a longitude in C{degrees}.
658 '''
659 _ddd_ = 3
660 _suf_ = _W_, S_NUL, _E_ # no zero suffix
662 def __new__(cls, arg=None, name=_lon_, clip=180, **Error_name_arg):
663 '''New L{Lon} instance, see L{Degrees}.
664 '''
665 return Degrees.__new__(cls, arg=arg, name=name, suffix=_EW_, clip=clip, **Error_name_arg)
668class Lon_(Degrees_):
669 '''Named C{float} representing a longitude in C{degrees} within limits C{low} and C{high}.
670 '''
671 _ddd_ = 3
672 _suf_ = _W_, S_NUL, _E_ # no zero suffix
674 def __new__(cls, arg=None, name=_lon_, low=-180, high=180, **Error_name_arg):
675 '''New L{Lon_} instance, see L{Lon} and L{Degrees_}.
676 '''
677 return Degrees_.__new__(cls, arg=arg, name=name, suffix=_EW_, low=low, high=high, **Error_name_arg)
680class Meter(Float):
681 '''Named C{float} representing a distance or length in C{meter}.
682 '''
683 def __new__(cls, arg=None, name=_meter_, **Error_name_arg):
684 '''New L{Meter} instance, see L{Float}.
685 '''
686 return Float.__new__(cls, arg=arg, name=name, **Error_name_arg)
688 def __repr__(self):
689 '''Return a representation of this C{Meter}.
691 @see: Method C{Str.toRepr} and property C{Str.std_repr}.
693 @note: Use C{env} variable C{PYGEODESY_METER_STD_REPR=std} prior to C{import
694 pygeodesy} to get the standard C{repr} or set property C{std_repr=False}
695 to always 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, .sphericalBase
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 separate
748 B{C{arg}} and B{C{name}} ones.
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 = _arg_name_arg2(arg, **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 = _arg_name_arg2(arg, **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)
840_Scalars = Float, Float_, Scalar, Scalar_
841_Degrees = (Bearing, Bearing_, Degrees, Degrees_) + _Scalars
842_Meters = (Distance, Distance_, Meter, Meter_) + _Scalars
843_Radians = (Radians, Radians_) + _Scalars # PYCHOK unused
844_Radii = _Meters + (Radius, Radius_)
847def _isDegrees(obj):
848 # Check for valid degrees types.
849 return isinstance(obj, _Degrees) or _isScalar(obj)
852def _isHeight(obj):
853 # Check for valid heigth types.
854 return isinstance(obj, _Meters) or _isScalar(obj)
857def _isMeter(obj):
858 # Check for valid meter types.
859 return isinstance(obj, _Meters) or _isScalar(obj)
862def _isRadius(obj):
863 # Check for valid earth radius types.
864 return isinstance(obj, _Radii) or _isScalar(obj)
867def _isScalar(obj):
868 # Check for pure scalar types.
869 return isscalar(obj) and not isinstance(obj, _NamedUnit)
872def _toUnit(Unit, arg, name=NN, **Error):
873 '''(INTERNAL) Wrap C{arg} in a C{name}d C{Unit}.
874 '''
875 if not (issubclassof(Unit, _NamedUnit) and isinstance(arg, Unit) and
876 _xattr(arg, name=NN) == name):
877 arg = Unit(arg, name=name, **Error)
878 return arg
881def _xStrError(*Refs, **name_value_Error):
882 '''(INTERNAL) Create a C{TypeError} for C{Garef}, C{Geohash}, C{Wgrs}.
883 '''
884 r = tuple(r.__name__ for r in Refs) + (Str.__name__, _LatLon_, 'LatLon*Tuple')
885 return _IsnotError(*r, **name_value_Error)
888def _xUnit(units, Base): # in .frechet, .hausdorff
889 '''(INTERNAL) Get C{Unit} from C{Unit} or C{name}, ortherwise C{Base}.
890 '''
891 _xsubclassof(_NamedUnit, Base=Base)
892 U = globals().get(units.capitalize(), Base) if isstr(units) else (
893 units if issubclassof(units, Base) else Base)
894 return U if issubclassof(U, Base) else Base
897def _xUnits(units, Base=_NamedUnit): # in .frechet, .hausdorff
898 '''(INTERNAL) Set property C{units} as C{Unit} or C{Str}.
899 '''
900 _xsubclassof(_NamedUnit, Base=Base)
901 if issubclassof(units, Base):
902 return units
903 elif isstr(units):
904 return Str(units, name=_units_) # XXX Str to _Pass and for backward compatibility
905 raise _IsnotError(*(_.__name__ for _ in (Base, Str, str)), units=units)
908def _std_repr(*Classes):
909 '''(INTERNAL) Use standard C{repr} or named C{toRepr}.
910 '''
911 for C in Classes:
912 if hasattr(C, _std_repr.__name__): # PYCHOK del _std_repr
913 env = 'PYGEODESY_%s_STD_REPR' % (C.__name__.upper(),)
914 if _getenv(env, _std_).lower() != _std_:
915 C._std_repr = False
917_std_repr(Bearing, Bool, Degrees, Float, Int, Meter, Radians, Str) # PYCHOK expected
918del _std_repr
920__all__ += _ALL_DOCS(_NamedUnit)
922# **) MIT License
923#
924# Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
925#
926# Permission is hereby granted, free of charge, to any person obtaining a
927# copy of this software and associated documentation files (the "Software"),
928# to deal in the Software without restriction, including without limitation
929# the rights to use, copy, modify, merge, publish, distribute, sublicense,
930# and/or sell copies of the Software, and to permit persons to whom the
931# Software is furnished to do so, subject to the following conditions:
932#
933# The above copyright notice and this permission notice shall be included
934# in all copies or substantial portions of the Software.
935#
936# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
937# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
938# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
939# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
940# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
941# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
942# OTHER DEALINGS IN THE SOFTWARE.