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