1 2# -*- coding: utf-8 -*- 3 4u'''Various utility functions. 5 6After I{(C) Chris Veness 2011-2015} published under the same MIT Licence**, see 7U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>} and 8U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}. 9''' 10# make sure int/int division yields float quotient, see .basics 11from __future__ import division 12 13from pygeodesy.basics import copysign0, isint, isnear0 14from pygeodesy.interns import EPS, EPS0, INF, PI, PI2, PI_2, R_M, \ 15 _edge_, _radians_, _semi_circular_, _SPACE_, \ 16 _0_0, _0_5, _1_0, _90_0, _180_0, _360_0, _400_0 17from pygeodesy.lazily import _ALL_LAZY 18from pygeodesy.units import Feet, Float, Lam, Lam_, Meter 19 20from math import acos, asin, atan2, cos, degrees, radians, sin, tan # pow 21 22__all__ = _ALL_LAZY.utily 23__version__ = '21.08.30' 24 25# <https://Numbers.Computation.Free.FR/Constants/Miscellaneous/digits.html> 26_1__90 = _1_0 / _90_0 # 0.01111111111111111111111111111111111111111111111111 27_2__PI = _1_0 / PI_2 # 0.63661977236758134307553505349005744813783858296182 28# sqrt(2) + 1 <https://WikiPedia.org/wiki/Square_root_of_2> 29# _1sqrt2 = 2.41421356237309504880 # _16887_24209_69807_85696_71875_37694_80731_76679_73799 30 31 32def acos1(x): 33 '''Return C{math.acos(max(-1, min(1, B{x})))}. 34 ''' 35 return acos(x) if abs(x) < _1_0 else (PI if x < 0 else _0_0) 36 37 38def acre2ha(acres): 39 '''Convert acres to hectare. 40 41 @arg acres: Value in acres (C{scalar}). 42 43 @return: Value in C{hectare} (C{float}). 44 45 @raise ValueError: Invalid B{C{acres}}. 46 ''' 47 # 0.40468564224 == acre2m2(1) / 10_000 48 return Float(acres) * 0.40468564224 49 50 51def acre2m2(acres): 52 '''Convert acres to I{square} meter. 53 54 @arg acres: Value in acres (C{scalar}). 55 56 @return: Value in C{meter^2} (C{float}). 57 58 @raise ValueError: Invalid B{C{acres}}. 59 ''' 60 # 4046.8564224 == chain2m(1) * furlong2m(1) 61 return Float(acres) * 4046.8564224 62 63 64def asin1(x): 65 '''Return C{math.asin(max(-1, min(1, B{x})))}. 66 ''' 67 return asin(x) if abs(x) < _1_0 else (-PI_2 if x < 0 else PI_2) # -PI_2, not PI3_2! 68 69 70def atand(y_x): 71 '''Return C{atan(B{y_x})} angle in C{degrees}. 72 73 @see: Function L{atan2d}. 74 ''' 75 return atan2d(y_x, _1_0) 76 77 78def atan2b(y, x): 79 '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360]}. 80 81 @see: Function L{atan2d}. 82 ''' 83 d = atan2d(y, x) 84 if d < 0: 85 d += _360_0 86 return d 87 88 89def atan2d(y, x, reverse=False): 90 '''Return C{atan2(B{y}, B{x})} in degrees M{[-180..+180]}, 91 optionally reversed (by 180 degrees for C{azi2}). 92 93 @see: I{Karney}'s C++ function U{Math.atan2d 94 <https://GeographicLib.SourceForge.io/html/classGeographicLib_1_1Math.html>}. 95 ''' 96 if abs(y) > abs(x) > 0: 97 if y < 0: # q = 3 98 d = degrees(atan2(x, -y)) - _90_0 99 else: # q = 2 100 d = _90_0 - degrees(atan2(x, y)) 101 elif x < 0: # q = 1 102 d = copysign0(_180_0, y) - degrees(atan2(y, -x)) 103 elif x > 0: # q = 0 104 d = degrees(atan2(y, x)) if y else _0_0 105 else: # x == 0 106 d = -_90_0 if y < 0 else (_90_0 if y > 0 else _0_0) 107 if reverse: 108 d += _180_0 if d < 0 else -_180_0 109 return d 110 111 112def chain2m(chains): 113 '''Convert I{UK} chains to meter. 114 115 @arg chains: Value in chains (C{scalar}). 116 117 @return: Value in C{meter} (C{float}). 118 119 @raise ValueError: Invalid B{C{chains}}. 120 ''' 121 # 20.1168 = 22 * yard2m(1) 122 return Float(chains) * 20.1168 123 124 125def circle4(earth, lat): 126 '''Get the equatorial or a parallel I{circle of latitude}. 127 128 @arg earth: The earth radius, ellipsoid or datum 129 (C{meter}, L{Ellipsoid}, L{Ellipsoid2}, 130 L{Datum} or L{a_f2Tuple}). 131 @arg lat: Geodetic latitude (C{degrees90}, C{str}). 132 133 @return: A L{Circle4Tuple}C{(radius, height, lat, beta)} 134 instance. 135 136 @raise RangeError: Latitude B{C{lat}} outside valid range 137 and L{rangerrors} set to C{True}. 138 139 @raise TypeError: Invalid B{C{earth}}. 140 141 @raise ValueError: B{C{earth}} or B{C{lat}}. 142 ''' 143 from pygeodesy.datums import _spherical_datum 144 E = _spherical_datum(earth).ellipsoid 145 return E.circle4(lat) 146 147 148def cotd(deg, **error_kwds): 149 '''Return the C{cotangent} of an angle in C{degrees}. 150 151 @arg deg: Angle (C{degrees}). 152 @kwarg error_kwds: Error to raise (C{ValueError}). 153 154 @return: C{cot(B{deg})}. 155 156 @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{deg})}. 157 ''' 158 s, c = sincos2d(deg) 159 if isnear0(s): 160 from pygeodesy.errors import _ValueError, _xkwds 161 raise _ValueError(**_xkwds(error_kwds, cotd=deg)) 162 return c / s 163 164 165def cotd_(*degs, **error_kwds): 166 '''Return the C{cotangent} of angle(s) in C{degrees}. 167 168 @arg degs: One or more angles (C{degrees}). 169 @kwarg error_kwds: Error to raise (C{ValueError}). 170 171 @return: Yield the C{cot(B{deg})} for each angle. 172 173 @raise ValueError: See L{pygeodesy.cotd}. 174 ''' 175 for d in degs: 176 yield cotd(d, **error_kwds) 177 178 179def degrees90(rad): 180 '''Convert radians to degrees and wrap M{[-270..+90]}. 181 182 @arg rad: Angle (C{radians}). 183 184 @return: Angle, wrapped (C{degrees90}). 185 ''' 186 return _wrap(degrees(rad), _90_0, _360_0) 187 188 189def degrees180(rad): 190 '''Convert radians to degrees and wrap M{[-180..+180]}. 191 192 @arg rad: Angle (C{radians}). 193 194 @return: Angle, wrapped (C{degrees180}). 195 ''' 196 return _wrap(degrees(rad), _180_0, _360_0) 197 198 199def degrees360(rad): 200 '''Convert radians to degrees and wrap M{[0..+360)}. 201 202 @arg rad: Angle (C{radians}). 203 204 @return: Angle, wrapped (C{degrees360}). 205 ''' 206 return _wrap(degrees(rad), _360_0, _360_0) 207 208 209def degrees2grades(deg): 210 '''Convert degrees to I{grades} (aka I{gradians} or I{gons}). 211 212 @arg deg: Angle (C{degrees}). 213 214 @return: Angle (C{grades}). 215 ''' 216 return deg * _400_0 / _360_0 217 218 219def degrees2m(deg, radius=R_M, lat=0): 220 '''Convert an angle to a distance along the equator or 221 along the parallel at an other (geodetic) latitude. 222 223 @arg deg: The angle (C{degrees}). 224 @kwarg radius: Mean earth radius, ellipsoid or datum 225 (C{meter}, L{Ellipsoid}, L{Ellipsoid2}, 226 L{Datum} or L{a_f2Tuple}). 227 @kwarg lat: Parallel latitude (C{degrees90}, C{str}). 228 229 @return: Distance (C{meter}, same units as B{C{radius}} 230 or ellipsoidal and polar radii) or C{0} for 231 near-polar B{C{lat}}. 232 233 @raise RangeError: Latitude B{C{lat}} outside valid range 234 and L{rangerrors} set to C{True}. 235 236 @raise TypeError: Invalid B{C{radius}}. 237 238 @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or 239 B{C{lat}}. 240 241 @see: Function L{radians2m} and L{m2degrees}. 242 ''' 243 return radians2m(Lam_(deg=deg, clip=0), radius=radius, lat=lat) 244 245 246def fathom2m(fathoms): 247 '''Convert I{UK} fathom to meter. 248 249 @arg fathoms: Value in fathoms (C{scalar}). 250 251 @return: Value in C{meter} (C{float}). 252 253 @raise ValueError: Invalid B{C{fathoms}}. 254 ''' 255 # 1.8288 == 2 * yard2m(1) 256 return Float(fathoms) * 1.8288 257 258 259def ft2m(feet, usurvey=False): 260 '''Convert I{International} or I{US Survey} feet to meter. 261 262 @arg feet: Value in feet (C{scalar}). 263 @kwarg usurvey: Convert I{US Survey} feet (C{bool}), 264 I{International} feet otherwise. 265 266 @return: Value in C{meter} (C{float}). 267 268 @raise ValueError: Invalid B{C{feet}}. 269 ''' 270 # US Survey 1200 / 3937 == 0.3048006096012192 271 # Int'l 0.3048 == 254 * 12 / 10_000 272 return Feet(feet) * (0.3048006096 if usurvey else 0.3048) 273 274 275def furlong2m(furlongs): 276 '''Convert a I{UK} furlong to meter. 277 278 @arg furlongs: Value in furlongs (C{scalar}). 279 280 @return: Value in C{meter} (C{float}). 281 282 @raise ValueError: Invalid B{C{furlongs}}. 283 ''' 284 # 201.168 = 220 * yard2m(1) 285 return Float(furlongs) * 201.168 286 287 288def grades(rad): 289 '''Convert radians to I{grades} (aka I{gradians} or I{gons}). 290 291 @arg rad: Angle (C{radians}). 292 293 @return: Angle (C{grades}). 294 ''' 295 return rad * _400_0 / PI2 296 297 298def grades400(rad): 299 '''Convert radians to I{grades} (aka I{gradians} or I{gons}) and wrap M{[0..+400)}. 300 301 @arg rad: Angle (C{radians}). 302 303 @return: Angle, wrapped (C{grades}). 304 ''' 305 return _wrap(grades(rad), _400_0, _400_0) 306 307 308def grades2degrees(gon): 309 '''Convert I{grades} (aka I{gradians} or I{gons}) to C{degrees}. 310 311 @arg gon: Angle (C{grades}). 312 313 @return: Angle (C{degrees}). 314 ''' 315 return gon * _360_0 / _400_0 316 317 318def grades2radians(gon): 319 '''Convert I{grades} (aka I{gradians} or I{gons}) to C{radians}. 320 321 @arg gon: Angle (C{grades}). 322 323 @return: Angle (C{radians}). 324 ''' 325 return gon * PI2 / _400_0 326 327 328def m2degrees(distance, radius=R_M, lat=0): 329 '''Convert a distance to an angle along the equator or 330 along the parallel at an other (geodetic) latitude. 331 332 @arg distance: Distance (C{meter}, same units as B{C{radius}}). 333 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter}, 334 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or 335 L{a_f2Tuple}). 336 @kwarg lat: Parallel latitude (C{degrees90}, C{str}). 337 338 @return: Angle (C{degrees}) or C{INF} for near-polar B{C{lat}}. 339 340 @raise RangeError: Latitude B{C{lat}} outside valid range 341 and L{rangerrors} set to C{True}. 342 343 @raise TypeError: Invalid B{C{radius}}. 344 345 @raise ValueError: Invalid B{C{distance}}, B{C{radius}} 346 or B{C{lat}}. 347 348 @see: Function L{m2radians} and L{degrees2m}. 349 ''' 350 return degrees(m2radians(distance, radius=radius, lat=lat)) 351 352 353def m2ft(meter, usurvey=False): 354 '''Convert meter to I{International} or I{US Survey} feet (C{ft}). 355 356 @arg meter: Value in meter (C{scalar}). 357 @kwarg usurvey: Convert to I{US Survey} feet (C{bool}), 358 I{International} feet otherwise. 359 360 @return: Value in C{feet} (C{float}). 361 362 @raise ValueError: Invalid B{C{meter}}. 363 ''' 364 # US Survey == 3937 / 1200 == 3.2808333333333333 365 # Int'l 10_000 / (254 * 12) == 3.2808398950131235 366 return Meter(meter) * (3.280833333 if usurvey else 3.280839895) 367 368 369def m2km(meter): 370 '''Convert meter to kilo meter (km). 371 372 @arg meter: Value in meter (C{scalar}). 373 374 @return: Value in km (C{float}). 375 376 @raise ValueError: Invalid B{C{meter}}. 377 ''' 378 return Meter(meter) * 1.0e-3 379 380 381def m2NM(meter): 382 '''Convert meter to nautical miles (NM). 383 384 @arg meter: Value in meter (C{scalar}). 385 386 @return: Value in NM (C{float}). 387 388 @raise ValueError: Invalid B{C{meter}}. 389 ''' 390 return Meter(meter) * 5.39956804e-4 # == * _1_0 / 1852 391 392 393def m2radians(distance, radius=R_M, lat=0): 394 '''Convert a distance to an angle along the equator or 395 along the parallel at an other (geodetic) latitude. 396 397 @arg distance: Distance (C{meter}, same units as B{C{radius}}). 398 @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter}, 399 an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or 400 L{a_f2Tuple}). 401 @kwarg lat: Parallel latitude (C{degrees90}, C{str}). 402 403 @return: Angle (C{radians}) or C{INF} for near-polar B{C{lat}}. 404 405 @raise RangeError: Latitude B{C{lat}} outside valid range 406 and L{rangerrors} set to C{True}. 407 408 @raise TypeError: Invalid B{C{radius}}. 409 410 @raise ValueError: Invalid B{C{distance}}, B{C{radius}} 411 or B{C{lat}}. 412 413 @see: Function L{m2degrees} and L{radians2m}. 414 ''' 415 m = circle4(radius, lat).radius 416 return INF if m < EPS0 else (Float(distance) / m) 417 418 419def m2SM(meter): 420 '''Convert meter to statute miles (SM). 421 422 @arg meter: Value in meter (C{scalar}). 423 424 @return: Value in SM (C{float}). 425 426 @raise ValueError: Invalid B{C{meter}}. 427 ''' 428 return Meter(meter) * 6.21369949e-4 # == _1_0 / 1609.344 429 430 431def m2yard(meter): 432 '''Convert meter to I{UK} yards. 433 434 @arg meter: Value in meter (C{scalar}). 435 436 @return: Value in yards (C{float}). 437 438 @raise ValueError: Invalid B{C{meter}}. 439 ''' 440 # 1.0936132983377078 == 10_000 / (254 * 12 * 3) 441 return Meter(meter) * 1.09361329833771 442 443 444def radiansPI(deg): 445 '''Convert and wrap degrees to radians M{[-PI..+PI]}. 446 447 @arg deg: Angle (C{degrees}). 448 449 @return: Radians, wrapped (C{radiansPI}) 450 ''' 451 return _wrap(radians(deg), PI, PI2) 452 453 454def radiansPI2(deg): 455 '''Convert and wrap degrees to radians M{[0..+2PI)}. 456 457 @arg deg: Angle (C{degrees}). 458 459 @return: Radians, wrapped (C{radiansPI2}) 460 ''' 461 return _wrap(radians(deg), PI2, PI2) 462 463 464def radiansPI_2(deg): 465 '''Convert and wrap degrees to radians M{[-3PI/2..+PI/2]}. 466 467 @arg deg: Angle (C{degrees}). 468 469 @return: Radians, wrapped (C{radiansPI_2}) 470 ''' 471 return _wrap(radians(deg), PI_2, PI2) 472 473 474def radians2m(rad, radius=R_M, lat=0): 475 '''Convert an angle to a distance along the equator or 476 along the parallel at an other (geodetic) latitude. 477 478 @arg rad: The angle (C{radians}). 479 @kwarg radius: Mean earth radius, ellipsoid or datum 480 (C{meter}, L{Ellipsoid}, L{Ellipsoid2}, 481 L{Datum} or L{a_f2Tuple}). 482 @kwarg lat: Parallel latitude (C{degrees90}, C{str}). 483 484 @return: Distance (C{meter}, same units as B{C{radius}} 485 or ellipsoidal and polar radii) or C{0} for 486 near-polar B{C{lat}}. 487 488 @raise RangeError: Latitude B{C{lat}} outside valid range 489 and L{rangerrors} set to C{True}. 490 491 @raise TypeError: Invalid B{C{radius}}. 492 493 @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or 494 B{C{lat}}. 495 496 @see: Function L{degrees2m} and L{m2radians}. 497 ''' 498 m = circle4(radius, lat).radius 499 return _0_0 if m < EPS0 else (Lam(rad=rad, clip=0) * m) 500 501 502def _sincos2(q, r): 503 '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant M{0 <= q <= 3}. 504 ''' 505 if r < EPS: # XXX EPS0 506 s, c = _0_0, _1_0 507 elif r < PI_2: 508 s, c = sin(r), cos(r) 509 else: # r == PI_2 510 s, c = _1_0, _0_0 511 t = s, c, -s, -c, s 512# q &= 3 513 return t[q], t[q + 1] 514 515 516def sincos2(rad): 517 '''Return the C{sine} and C{cosine} of an angle in C{radians}. 518 519 @arg rad: Angle (C{radians}). 520 521 @return: 2-Tuple (C{sin(B{rad})}, C{cos(B{rad})}). 522 523 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/html/ 524 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd 525 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/ 526 python/geographiclib/geomath.py#l155>} and C++ U{sincosd 527 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/ 528 include/GeographicLib/Math.hpp#l558>}. 529 ''' 530 q = int(rad * _2__PI) # int(math.floor) 531 if rad < 0: 532 q -= 1 533 return _sincos2(q & 3, rad - q * PI_2) 534 535 536def sincos2_(*rads): 537 '''Return the C{sine} and C{cosine} of angle(s) in {Cradians}. 538 539 @arg rads: One or more angles (C{radians}). 540 541 @return: Yield the C{sin(B{rad})} and C{cos(B{rad})} for each angle. 542 543 @see: function L{sincos2}. 544 ''' 545 for r in rads: 546 s, c = sincos2(r) 547 yield s 548 yield c 549 550 551def sincos2d(deg): 552 '''Return the C{sine} and C{cosine} of an angle in C{degrees}. 553 554 @arg deg: Angle (C{degrees}). 555 556 @return: 2-Tuple (C{sin(B{deg})}, C{cos(B{deg})}). 557 558 @see: U{GeographicLib<https://GeographicLib.SourceForge.io/html/ 559 classGeographicLib_1_1Math.html#sincosd>} function U{sincosd 560 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/ 561 python/geographiclib/geomath.py#l155>} and C++ U{sincosd 562 <https://SourceForge.net/p/geographiclib/code/ci/release/tree/ 563 include/GeographicLib/Math.hpp#l558>}. 564 ''' 565 q = int(deg * _1__90) # int(math.floor) 566 if deg < 0: 567 q -= 1 568 return _sincos2(q & 3, radians(deg - q * _90_0)) 569 570 571def sincos2d_(*degs): 572 '''Return the C{sine} and C{cosine} of angle(s) in C{degrees}. 573 574 @arg degs: One or more angles (C{degrees}). 575 576 @return: Yield the C{sin(B{deg})} and C{cos(B{deg})} for each angle. 577 578 @see: Function L{sincos2d}. 579 ''' 580 for d in degs: 581 s, c = sincos2d(d) 582 yield s 583 yield c 584 585 586def tan_2(rad, **semi): # edge=1 587 '''Compute the tangent of half angle. 588 589 @arg rad: Angle (C{radians}). 590 @kwarg semi: Angle or edge name and index 591 for semi-circular error. 592 593 @return: M{tan(rad / 2)} (C{float}). 594 595 @raise ValueError: If B{C{rad}} is semi-circular 596 and B{C{semi}} is given. 597 ''' 598 # .formy.excessKarney_, .sphericalTrigonometry.areaOf 599 if semi and isnear0(abs(rad) - PI): 600 for n, v in semi.items(): 601 break 602 if isint(v): 603 from pygeodesy.streprs import Fmt 604 n = _SPACE_(Fmt.SQUARE(**semi), _edge_) 605 else: 606 n = _SPACE_(n, _radians_) 607 from pygeodesy.errors import _ValueError 608 raise _ValueError(n, rad, txt=_semi_circular_) 609 610 return tan(rad * _0_5) 611 612 613def tand(deg, **error_kwds): 614 '''Return the C{tangent} of an angle in C{degrees}. 615 616 @arg deg: Angle (C{degrees}). 617 @kwarg error_kwds: Error to raise (C{ValueError}). 618 619 @return: C{tan(B{deg})}. 620 621 @raise ValueError: If L{pygeodesy.isnear0}C{(cos(B{deg})}. 622 ''' 623 s, c = sincos2d(deg) 624 if isnear0(c): 625 from pygeodesy.errors import _ValueError, _xkwds 626 raise _ValueError(**_xkwds(error_kwds, tand=deg)) 627 return s / c 628 629 630def tand_(*degs, **error_kwds): 631 '''Return the C{tangent} of angle(s) in C{degrees}. 632 633 @arg degs: One or more angles (C{degrees}). 634 @kwarg error_kwds: Error to raise (C{ValueError}). 635 636 @return: Yield the C{tan(B{deg})} for each angle. 637 638 @raise ValueError: See L{pygeodesy.tand}. 639 ''' 640 for d in degs: 641 yield tand(d, **error_kwds) 642 643 644def tanPI_2_2(rad): 645 '''Compute the tangent of half angle, 90 degrees rotated. 646 647 @arg rad: Angle (C{radians}). 648 649 @return: M{tan((rad + PI/2) / 2)} (C{float}). 650 ''' 651 return tan((rad + PI_2) * _0_5) 652 653 654def unroll180(lon1, lon2, wrap=True): 655 '''Unroll longitudinal delta and wrap longitude in degrees. 656 657 @arg lon1: Start longitude (C{degrees}). 658 @arg lon2: End longitude (C{degrees}). 659 @kwarg wrap: Wrap and unroll to the M{(-180..+180]} range (C{bool}). 660 661 @return: 2-Tuple C{(B{lon2}-B{lon1}, B{lon2})} unrolled (C{degrees}, 662 C{degrees}). 663 664 @see: Capability C{LONG_UNROLL} in U{GeographicLib 665 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}. 666 ''' 667 d = lon2 - lon1 668 if wrap and abs(d) > _180_0: 669 u = _wrap(d, _180_0, _360_0) 670 if u != d: 671 return u, lon1 + u 672 return d, lon2 673 674 675def unrollPI(rad1, rad2, wrap=True): 676 '''Unroll longitudinal delta and wrap longitude in radians. 677 678 @arg rad1: Start longitude (C{radians}). 679 @arg rad2: End longitude (C{radians}). 680 @kwarg wrap: Wrap and unroll to the M{(-PI..+PI]} range (C{bool}). 681 682 @return: 2-Tuple C{(B{rad2}-B{rad1}, B{rad2})} unrolled (C{radians}, 683 C{radians}). 684 685 @see: Capability C{LONG_UNROLL} in U{GeographicLib 686 <https://GeographicLib.SourceForge.io/html/python/interface.html#outmask>}. 687 ''' 688 r = rad2 - rad1 689 if wrap and abs(r) > PI: 690 u = _wrap(r, PI, PI2) 691 if u != r: 692 return u, rad1 + u 693 return r, rad2 694 695 696def _wrap(angle, wrap, modulo): 697 '''(INTERNAL) Angle wrapper M{((wrap-modulo)..+wrap]}. 698 699 @arg angle: Angle (C{degrees}, C{radians} or C{grades}). 700 @arg wrap: Range (C{degrees}, C{radians} or C{grades}). 701 @arg modulo: Upper limit (360 C{degrees}, PI2 C{radians} or 400 C{grades}). 702 703 @return: The B{C{angle}}, wrapped (C{degrees}, C{radians} or C{grades}). 704 ''' 705 angle = float(angle) 706 if not wrap > angle >= (wrap - modulo): 707 # math.fmod(-1.5, 3.14) == -1.5, but -1.5 % 3.14 == 1.64 708 # math.fmod(-1.5, 360) == -1.5, but -1.5 % 360 == 358.5 709 angle %= modulo 710 if angle > wrap: 711 angle -= modulo 712 return angle 713 714 715def wrap90(deg): 716 '''Wrap degrees to M{[-270..+90]}. 717 718 @arg deg: Angle (C{degrees}). 719 720 @return: Degrees, wrapped (C{degrees90}). 721 ''' 722 return _wrap(deg, _90_0, _360_0) 723 724 725def wrap180(deg): 726 '''Wrap degrees to M{[-180..+180]}. 727 728 @arg deg: Angle (C{degrees}). 729 730 @return: Degrees, wrapped (C{degrees180}). 731 ''' 732 return _wrap(deg, _180_0, _360_0) 733 734 735def wrap360(deg): 736 '''Wrap degrees to M{[0..+360)}. 737 738 @arg deg: Angle (C{degrees}). 739 740 @return: Degrees, wrapped (C{degrees360}). 741 ''' 742 return _wrap(deg, _360_0, _360_0) 743 744 745def wrapPI(rad): 746 '''Wrap radians to M{[-PI..+PI]}. 747 748 @arg rad: Angle (C{radians}). 749 750 @return: Radians, wrapped (C{radiansPI}). 751 ''' 752 return _wrap(rad, PI, PI2) 753 754 755def wrapPI2(rad): 756 '''Wrap radians to M{[0..+2PI)}. 757 758 @arg rad: Angle (C{radians}). 759 760 @return: Radians, wrapped (C{radiansPI2}). 761 ''' 762 return _wrap(rad, PI2, PI2) 763 764 765def wrapPI_2(rad): 766 '''Wrap radians to M{[-3PI/2..+PI/2]}. 767 768 @arg rad: Angle (C{radians}). 769 770 @return: Radians, wrapped (C{radiansPI_2}). 771 ''' 772 return _wrap(rad, PI_2, PI2) 773 774 775def yard2m(yards): 776 '''Convert I{UK} yards to meter. 777 778 @arg yards: Value in yards (C{scalar}). 779 780 @return: Value in C{meter} (C{float}). 781 782 @raise ValueError: Invalid B{C{yards}}. 783 ''' 784 # 0.9144 == 254 * 12 * 3 / 10_000 == 3 * ft2m(1) Int'l 785 return Float(yards) * 0.9144 786 787# **) MIT License 788# 789# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved. 790# 791# Permission is hereby granted, free of charge, to any person obtaining a 792# copy of this software and associated documentation files (the "Software"), 793# to deal in the Software without restriction, including without limitation 794# the rights to use, copy, modify, merge, publish, distribute, sublicense, 795# and/or sell copies of the Software, and to permit persons to whom the 796# Software is furnished to do so, subject to the following conditions: 797# 798# The above copyright notice and this permission notice shall be included 799# in all copies or substantial portions of the Software. 800# 801# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 802# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 803# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 804# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 805# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 806# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 807# OTHER DEALINGS IN THE SOFTWARE. 808