pygeodesy 24.12.12__py2.py3-none-any.whl → 25.1.9__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pygeodesy/utily.py CHANGED
@@ -3,46 +3,52 @@
3
3
 
4
4
  u'''Various utility functions.
5
5
 
6
- After I{(C) Chris Veness 2011-2024} published under the same MIT Licence**, see
7
- U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>} and
8
- U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}.
6
+ After I{Karney}'s C++ U{Math<https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}
7
+ class and I{Veness}' U{Latitude/Longitude<https://www.Movable-Type.co.UK/scripts/latlong.html>}
8
+ and U{Vector-based geodesy<https://www.Movable-Type.co.UK/scripts/latlong-vectors.html>}
9
+ and published under the same MIT Licence**.
9
10
  '''
10
11
  # make sure int/int division yields float quotient, see .basics
11
12
  from __future__ import division as _; del _ # PYCHOK semicolon
12
13
 
13
- from pygeodesy.basics import _copysign, isinstanceof, isint, isstr, neg
14
+ from pygeodesy.basics import _copysign, isinstanceof, isint, isstr
14
15
  from pygeodesy.constants import EPS, EPS0, INF, NAN, PI, PI2, PI_2, R_M, \
15
- _M_KM, _M_NM, _M_SM, _0_0, _1__90, _0_5, _1_0, \
16
- _N_1_0, _2__PI, _10_0, _90_0, _180_0, _N_180_0, \
17
- _360_0, _400_0, isnan, isnear0, _copysign_0_0, \
18
- _float, _isfinite, _over, _umod_360, _umod_PI2
19
- from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get1, _ALL_LAZY, _MODS
20
- from pygeodesy.internals import _passarg, _passargs # , _MODS?
16
+ _M_KM, _M_NM, _M_SM, _0_0, _1__90, _0_5, _2__PI, \
17
+ _1_0, _N_1_0, _10_0, _90_0, _180_0, _360_0, \
18
+ _copysign_0_0, _float, _isfinite, isnan, isnear0, \
19
+ _over, _umod_360, _umod_PI2
20
+ from pygeodesy.errors import _ValueError, _xkwds, _ALL_LAZY, _MODS
21
+ from pygeodesy.internals import _passargs
21
22
  from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_
22
23
  # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .errors
23
24
  from pygeodesy.units import Degrees, Degrees_, Feet, Float, Lam, Lamd, \
24
- Meter, Meter2, Radians, Radians_
25
+ Meter, Meter2, Radians # Radians_
25
26
 
26
27
  from math import acos, asin, atan2 as _atan2, cos, degrees, fabs, radians, \
27
28
  sin, tan as _tan # pow
28
29
 
29
30
  __all__ = _ALL_LAZY.utily
30
- __version__ = '24.11.26'
31
-
32
- _G_DEG = _float(_400_0 / _360_0) # grades per degree
33
- _G_RAD = _float(_400_0 / PI2) # grades per radian
34
- _M_CHAIN = _float( 20.1168) # meter per yard2m(1) * 22
35
- _M_FATHOM = _float( 1.8288) # meter per yard2m(1) * 2 or _M_NM * 1e-3
36
- _M_FOOT = _float( 0.3048) # meter per Int'l foot, 1 / 3.2808398950131 = 10_000 / (254 * 12)
37
- _M_FOOT_GE = _float( 0.31608) # meter per German Fuss, 1 / 3.1637560111364
38
- _M_FOOT_FR = _float( 0.3248406) # meter per French Pied-du-Roi or pied, 1 / 3.0784329298739
39
- _M_FOOT_US = _float( 0.3048006096012192) # meter per US Survey foot, 1200 / 3937
40
- _M_FURLONG = _float( 201.168) # meter per furlong, 220 * yard2m(1) = 10 * m2chain(1)
41
- # _M_KM = _float(1000.0) # meter per kilo meter
42
- # _M_NM = _float(1852.0) # meter per nautical mile
43
- # _M_SM = _float(1609.344) # meter per statute mile
44
- _M_TOISE = _float( 1.9490436) # meter per French toise, 6 pieds = 6 / 3.0784329298739
45
- _M_YARD_UK = _float( 0.9144) # meter per yard, 254 * 12 * 3 / 10_000 = 3 * _M_FOOT
31
+ __version__ = '25.01.05'
32
+
33
+ _G_DEG = _float( 400.0 / _360_0) # grades per degree
34
+ _G_RAD = _float( 400.0 / PI2) # grades per radian
35
+ _M_ACRE = _float( 4046.8564224) # square meter per acre, chain2m(1) * furlong2m(1)
36
+ _M_CHAIN = _float( 20.1168) # meter per yard2m(1) * 22
37
+ _M_FATHOM = _float( 1.8288) # meter per yard2m(1) * 2 or _M_NM * 1e-3
38
+ _M_FOOT = _float( 0.3048) # meter per Int'l foot, 1 / 3.280_839_895_0131 = 10_000 / (254 * 12)
39
+ _M_FOOT_GE = _float( 0.31608) # meter per German Fuss, 1 / 3.163_756_011_1364
40
+ _M_FOOT_FR = _float( 0.3248406) # meter per French Pied-du-Roi or pied, 1 / 3.078_432_929_8739
41
+ _M_FOOT_US = _float( 0.3048006096012192) # meter per US Survey foot, 1_200 / 3_937
42
+ _M_FURLONG = _float( 201.168) # meter per furlong, 220 * yard2m(1) = 10 * m2chain(1)
43
+ _M_HA = _float(10000.0) # square meter per hectare, 100 * 100
44
+ # _M_KM = _float( 1000.0) # meter per kilo meter
45
+ # _M_NM = _float( 1852.0) # meter per nautical mile
46
+ # _M_SM = _float( 1609.344) # meter per statute mile
47
+ _M_TOISE = _float( 1.9490436) # meter per French toise, 6 pieds = 6 / 3.078_432_929_8739
48
+ _M_YARD_UK = _float( 0.9144) # meter per yard, 254 * 12 * 3 / 10_000 = 3 * _M_FOOT
49
+ # sqrt(3) <https://WikiPedia.org/wiki/Square_root_of_3>
50
+ _COS_30, _SIN_30 = 0.86602540378443864676, _0_5 # sqrt(3) / 2
51
+ _COS_45 = _SIN_45 = 0.70710678118654752440 # sqrt(2) / 2
46
52
 
47
53
 
48
54
  def _abs1nan(x):
@@ -66,8 +72,7 @@ def acre2ha(acres):
66
72
 
67
73
  @raise ValueError: Invalid B{C{acres}}.
68
74
  '''
69
- # 0.40468564224 == acre2m2(1) / 10_000
70
- return Float(ha=Float(acres) * 0.40468564224)
75
+ return m2ha(acre2m2(acres))
71
76
 
72
77
 
73
78
  def acre2m2(acres):
@@ -79,8 +84,7 @@ def acre2m2(acres):
79
84
 
80
85
  @raise ValueError: Invalid B{C{acres}}.
81
86
  '''
82
- # 4046.8564224 == chain2m(1) * furlong2m(1)
83
- return Meter2(Float(acres) * 4046.8564224)
87
+ return Meter2(Float(acres=acres) * _M_ACRE)
84
88
 
85
89
 
86
90
  def asin1(x):
@@ -93,7 +97,7 @@ def atan1(y, x=_1_0):
93
97
  '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}
94
98
  using C{atan2} for consistency and to avoid C{ZeroDivisionError}.
95
99
  '''
96
- return _atan2(-y, -x) if x < 0 else _atan2(y, x or _0_0) # -0. to 0.
100
+ return _atan1u(y, x, atan2)
97
101
 
98
102
 
99
103
  def atan1d(y, x=_1_0):
@@ -102,27 +106,35 @@ def atan1d(y, x=_1_0):
102
106
 
103
107
  @see: Function L{pygeodesy.atan2d}.
104
108
  '''
105
- return atan2d(-y, -x) if x < 0 else atan2d(y, x or _0_0) # -0. to 0.
109
+ return _atan1u(y, x, atan2d)
106
110
 
107
111
 
108
- def atan2(y, x):
109
- '''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.
110
-
111
- @see: I{Karney}'s C++ function U{Math.atan2d
112
- <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
112
+ def _atan1u(y, x, _2u):
113
+ '''(INTERNAL) Helper for functions C{atan1} and C{atan1d}.
113
114
  '''
114
- return _atan2u(y, x, _passarg, PI, PI_2)
115
+ if x < 0:
116
+ x = -x
117
+ y = -y
118
+ return _2u(y, x or _0_0)
119
+
120
+
121
+ atan2 = _atan2
122
+ '''Return C{atan2(B{y}, B{x})} in radians M{[-PI..+PI]}.
123
+
124
+ @see: I{Karney}'s C++ function U{Math.atan2d
125
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
126
+ '''
115
127
 
116
128
 
117
129
  def atan2b(y, x):
118
- '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360]}.
130
+ '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360], counter-clockwise}.
119
131
 
120
132
  @see: Function L{pygeodesy.atan2d}.
121
133
  '''
122
134
  b = atan2d(y, x)
123
135
  if b < 0:
124
136
  b += _360_0
125
- return b
137
+ return b or _0_0 # unsigned-0
126
138
 
127
139
 
128
140
  def atan2d(y, x, reverse=False):
@@ -132,36 +144,14 @@ def atan2d(y, x, reverse=False):
132
144
  @see: I{Karney}'s C++ function U{Math.atan2d
133
145
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
134
146
  '''
135
- d = _atan2u(y, x, degrees, _180_0, _90_0)
147
+ d = degrees(_atan2(y, x)) # preserves signed-0
136
148
  return _azireversed(d) if reverse else d
137
149
 
138
150
 
139
- def _atan2u(y, x, _2u, H, Q): # Half, Quarter turn in units
140
- '''(INTERNAL) Helper for functions C{atan2} and C{atan2d}.
151
+ def _azireversed(azi): # in .rhumbBase
152
+ '''(INTERNAL) Return the I{reverse} B{C{azi}} in degrees M{[-180..+180]}.
141
153
  '''
142
- if fabs(y) > fabs(x) > 0:
143
- if y < 0: # q = 3
144
- r = _2u(_atan2(x, -y)) - Q
145
- else: # q = 2
146
- r = Q - _2u(_atan2(x, y))
147
- elif isnan(x) or isnan(y):
148
- return NAN
149
- elif y:
150
- if x > 0: # q = 0
151
- r = _2u(_atan2(y, x))
152
- elif x < 0: # q = 1
153
- r = _copysign(H, y) - _2u(_atan2(y, -x))
154
- else: # x == 0
155
- r = _copysign(Q, y)
156
- else: # preserve signBit(y) like Python's math.atan2
157
- r = _copysign(H, y) if x < 0 else _0_0
158
- return r
159
-
160
-
161
- def _azireversed(azimuth): # in .rhumbBase
162
- '''(INTERNAL) Return the I{reverse} B{C{azimuth}} in degrees M{[-180..+180]}.
163
- '''
164
- return azimuth + (_N_180_0 if azimuth > 0 else _180_0)
154
+ return azi - _copysign(_180_0, azi)
165
155
 
166
156
 
167
157
  def chain2m(chains):
@@ -215,11 +205,11 @@ def cot(rad, **raiser_kwds):
215
205
 
216
206
 
217
207
  def cot_(*rads, **raiser_kwds):
218
- '''Return the C{cotangent} of angle(s) in C{radians}.
208
+ '''Yield the C{cotangent} of angle(s) in C{radians}.
219
209
 
220
210
  @arg rads: One or more angles (each in C{radians}).
221
211
 
222
- @return: Yield the C{cot(B{rad})} for each angle.
212
+ @return: Yield C{cot(B{rad})} for each angle.
223
213
 
224
214
  @see: Function L{pygeodesy.cot} for further details.
225
215
  '''
@@ -249,11 +239,11 @@ def cotd(deg, **raiser_kwds):
249
239
 
250
240
 
251
241
  def cotd_(*degs, **raiser_kwds):
252
- '''Return the C{cotangent} of angle(s) in C{degrees}.
242
+ '''Yield the C{cotangent} of angle(s) in C{degrees}.
253
243
 
254
244
  @arg degs: One or more angles (each in C{degrees}).
255
245
 
256
- @return: Yield the C{cot(B{deg})} for each angle.
246
+ @return: Yield C{cotd(B{deg})} for each angle.
257
247
 
258
248
  @see: Function L{pygeodesy.cotd} for further details.
259
249
  '''
@@ -420,6 +410,40 @@ def grades2radians(gon):
420
410
  return Radians(Float(gon=gon) / _G_RAD)
421
411
 
422
412
 
413
+ def ha2acre(ha):
414
+ '''Convert hectare to acre.
415
+
416
+ @arg ha: Value in hectare (C{scalar}).
417
+
418
+ @return: Value in acres (C{float}).
419
+
420
+ @raise ValueError: Invalid B{C{ha}}.
421
+ '''
422
+ return m2acre(ha2m2(ha))
423
+
424
+
425
+ def ha2m2(ha):
426
+ '''Convert hectare to I{square} meter.
427
+
428
+ @arg ha: Value in hectare (C{scalar}).
429
+
430
+ @return: Value in C{meter^2} (C{float}).
431
+
432
+ @raise ValueError: Invalid B{C{ha}}.
433
+ '''
434
+ return Meter2(Float(ha=ha) * _M_HA)
435
+
436
+
437
+ def hav(rad):
438
+ '''Return the U{haversine<https://WikiPedia.org/wiki/Haversine_formula>} of an angle.
439
+
440
+ @arg rad: Angle (C{radians}).
441
+
442
+ @return: C{sin(B{rad} / 2)**2}.
443
+ '''
444
+ return sin(rad * _0_5)**2
445
+
446
+
423
447
  def km2m(km):
424
448
  '''Convert kilo meter to meter (m).
425
449
 
@@ -438,6 +462,18 @@ def _loneg(lon):
438
462
  return _180_0 - lon
439
463
 
440
464
 
465
+ def m2acre(meter2):
466
+ '''Convert I{square} meter to acres.
467
+
468
+ @arg meter2: Value in C{meter^2} (C{scalar}).
469
+
470
+ @return: Value in acres (C{float}).
471
+
472
+ @raise ValueError: Invalid B{C{meter2}}.
473
+ '''
474
+ return Float(acre=Meter2(meter2) / _M_ACRE)
475
+
476
+
441
477
  def m2chain(meter):
442
478
  '''Convert meter to I{UK} chains.
443
479
 
@@ -447,7 +483,7 @@ def m2chain(meter):
447
483
 
448
484
  @raise ValueError: Invalid B{C{meter}}.
449
485
  '''
450
- return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049709695378986715
486
+ return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049_709_695_378_986_715
451
487
 
452
488
 
453
489
  def m2degrees(distance, radius=R_M, lat=0):
@@ -485,7 +521,7 @@ def m2fathom(meter):
485
521
  @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
486
522
  and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
487
523
  '''
488
- return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546806649
524
+ return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546_806_649
489
525
 
490
526
 
491
527
  def m2ft(meter, usurvey=False, pied=False, fuss=False):
@@ -502,8 +538,8 @@ def m2ft(meter, usurvey=False, pied=False, fuss=False):
502
538
 
503
539
  @raise ValueError: Invalid B{C{meter}}.
504
540
  '''
505
- # * 3.2808333333333333, US Survey 3937 / 1200
506
- # * 3.2808398950131235, Int'l 10_000 / (254 * 12)
541
+ # * 3.280_833_333_333_3333, US Survey 3_937 / 1_200
542
+ # * 3.280_839_895_013_1235, Int'l 10_000 / (254 * 12)
507
543
  return Float(feet=Meter(meter) / (_M_FOOT_US if usurvey else
508
544
  (_M_FOOT_FR if pied else
509
545
  (_M_FOOT_GE if fuss else _M_FOOT))))
@@ -518,7 +554,19 @@ def m2furlong(meter):
518
554
 
519
555
  @raise ValueError: Invalid B{C{meter}}.
520
556
  '''
521
- return Float(furlong=Meter(meter) / _M_FURLONG) # * 0.00497096954
557
+ return Float(furlong=Meter(meter) / _M_FURLONG) # * 0.004_970_969_54
558
+
559
+
560
+ def m2ha(meter2):
561
+ '''Convert I{square} meter to hectare.
562
+
563
+ @arg meter2: Value in C{meter^2} (C{scalar}).
564
+
565
+ @return: Value in hectare (C{float}).
566
+
567
+ @raise ValueError: Invalid B{C{meter2}}.
568
+ '''
569
+ return Float(ha=Meter2(meter2) / _M_HA)
522
570
 
523
571
 
524
572
  def m2km(meter):
@@ -542,7 +590,7 @@ def m2NM(meter):
542
590
 
543
591
  @raise ValueError: Invalid B{C{meter}}.
544
592
  '''
545
- return Float(NM=Meter(meter) / _M_NM) # * 5.39956804e-4
593
+ return Float(NM=Meter(meter) / _M_NM) # * 5.399_568_04e-4
546
594
 
547
595
 
548
596
  def m2radians(distance, radius=R_M, lat=0):
@@ -578,7 +626,7 @@ def m2SM(meter):
578
626
 
579
627
  @raise ValueError: Invalid B{C{meter}}.
580
628
  '''
581
- return Float(SM=Meter(meter) / _M_SM) # * 6.21369949e-4 == 1 / 1609.344
629
+ return Float(SM=Meter(meter) / _M_SM) # * 6.213_699_49e-4 == 1 / 1_609.344
582
630
 
583
631
 
584
632
  def m2toise(meter):
@@ -592,7 +640,7 @@ def m2toise(meter):
592
640
 
593
641
  @see: Function L{m2fathom}.
594
642
  '''
595
- return Float(toise=Meter(meter) / _M_TOISE) # * 0.513083632632119
643
+ return Float(toise=Meter(meter) / _M_TOISE) # * 0.513_083_632_632_119
596
644
 
597
645
 
598
646
  def m2yard(meter):
@@ -604,7 +652,7 @@ def m2yard(meter):
604
652
 
605
653
  @raise ValueError: Invalid B{C{meter}}.
606
654
  '''
607
- return Float(yard=Meter(meter) / _M_YARD_UK) # * 1.0936132983377078
655
+ return Float(yard=Meter(meter) / _M_YARD_UK) # * 1.093_613_298_337_707_8
608
656
 
609
657
 
610
658
  def NM2m(nm):
@@ -680,20 +728,25 @@ def radiansPI_2(deg):
680
728
  return wrapPI_2(radians(deg))
681
729
 
682
730
 
683
- def _sin0cos2(q, r, sign):
684
- '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3}
685
- and C{sin} zero I{signed} with B{C{sign}}.
731
+ def _sin0cos2(q, r, sign, a, Q): # Quarter turn
732
+ '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3} and
733
+ C{sin} zero I{signed} with B{C{sign}}, like Karney's U{Math.sind and .cosd
734
+ <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}
686
735
  '''
687
736
  if r < PI_2:
688
- s, c = sin(r), cos(r)
689
- t = s, c, -s, -c, s
690
- else: # r == PI_2
691
- t = _1_0, _0_0, _N_1_0, _0_0, _1_0
692
- # else: # r == 0, testUtility failures
693
- # t = _0_0, _1_0, _0_0, _N_1_0, _0_0
737
+ s = sin(r)
738
+ if (a * 2) == Q:
739
+ s, c = _copysign(_SIN_45, s), _COS_45
740
+ elif (a * 3) == Q:
741
+ s, c = _copysign(_SIN_30, s), _COS_30
742
+ else:
743
+ c = cos(r)
744
+ else:
745
+ s, c = _1_0, _0_0
746
+ t = s, c, -s, -c, s
694
747
  # q &= 3
695
- s = t[q] or _copysign_0_0(sign)
696
- c = t[q + 1] or _0_0
748
+ s = t[q] or _copysign_0_0(sign)
749
+ c = t[q + 1] or _0_0
697
750
  return s, c
698
751
 
699
752
 
@@ -705,8 +758,8 @@ def SinCos2(x):
705
758
  @return: 2-Tuple (C{sin(B{x})}, C{cos(B{x})}).
706
759
  '''
707
760
  return sincos2d(x) if isinstanceof(x, Degrees, Degrees_) else (
708
- sincos2(x) if isinstanceof(x, Radians, Radians_) else
709
- sincos2(float(x))) # assume C{radians}
761
+ # sincos2(x) if isinstanceof(x, Radians, Radians_) else
762
+ sincos2(Radians(x))) # assume C{radians}
710
763
 
711
764
 
712
765
  def sincos2(rad):
@@ -727,20 +780,21 @@ def sincos2(rad):
727
780
  q = int(rad * _2__PI) # int(math.floor)
728
781
  if q < 0:
729
782
  q -= 1
730
- t = _sin0cos2(q & 3, rad - q * PI_2, rad)
783
+ r = rad - q * PI_2
784
+ t = _sin0cos2(q & 3, r, rad, fabs(r), PI_2)
731
785
  else:
732
786
  t = NAN, NAN
733
787
  return t
734
788
 
735
789
 
736
790
  def sincos2_(*rads):
737
- '''Return the C{sine} and C{cosine} of angle(s) in C{radians}.
791
+ '''Yield the C{sine} and C{cosine} of angle(s) in C{radians}.
738
792
 
739
793
  @arg rads: One or more angles (C{radians}).
740
794
 
741
- @return: Yield the C{sin(B{rad})} and C{cos(B{rad})} for each angle.
795
+ @return: Yield C{sin(B{rad})} and C{cos(B{rad})} for each angle.
742
796
 
743
- @see: function L{sincos2}.
797
+ @see: Function L{sincos2}.
744
798
  '''
745
799
  for r in rads:
746
800
  s, c = sincos2(r)
@@ -748,7 +802,7 @@ def sincos2_(*rads):
748
802
  yield c
749
803
 
750
804
 
751
- def sincos2d(deg, **adeg):
805
+ def sincos2d(deg, adeg=_0_0):
752
806
  '''Return the C{sine} and C{cosine} of an angle in C{degrees}.
753
807
 
754
808
  @arg deg: Angle (C{degrees}).
@@ -770,20 +824,19 @@ def sincos2d(deg, **adeg):
770
824
  q -= 1
771
825
  d = deg - q * _90_0
772
826
  if adeg:
773
- t = _xkwds_get1(adeg, adeg=_0_0)
774
- d = _MODS.karney._around(d + t)
775
- t = _sin0cos2(q & 3, radians(d), deg)
827
+ d = _MODS.karney._around(d + adeg)
828
+ t = _sin0cos2(q & 3, radians(d), deg, fabs(d), _90_0)
776
829
  else:
777
830
  t = NAN, NAN
778
831
  return t
779
832
 
780
833
 
781
834
  def sincos2d_(*degs):
782
- '''Return the C{sine} and C{cosine} of angle(s) in C{degrees}.
835
+ '''Yield the C{sine} and C{cosine} of angle(s) in C{degrees}.
783
836
 
784
837
  @arg degs: One or more angles (C{degrees}).
785
838
 
786
- @return: Yield the C{sin(B{deg})} and C{cos(B{deg})} for each angle.
839
+ @return: Yield C{sind(B{deg})} and C{cosd(B{deg})} for each angle.
787
840
 
788
841
  @see: Function L{sincos2d}.
789
842
  '''
@@ -802,9 +855,25 @@ def sincostan3(rad):
802
855
 
803
856
  @see: Function L{sincos2}.
804
857
  '''
805
- s, c = sincos2(float(rad))
806
- t = NAN if s is NAN else (_over(s, c) if s else neg(s, neg0=c < 0))
807
- return s, c, t
858
+ return _sincostan3(*sincos2(float(rad)))
859
+
860
+
861
+ def _sincostan3(s, c):
862
+ '''(INTERNAL) Helper for C{sincostan3} and C{sincostan3d}.
863
+ '''
864
+ return s, c, _tanu(s, c, raiser=False)
865
+
866
+
867
+ def sincostan3d(deg):
868
+ '''Return the C{sine}, C{cosine} and C{tangent} of an angle in C{degrees}.
869
+
870
+ @arg deg: Angle (C{degrees}).
871
+
872
+ @return: 3-Tuple (C{sind(B{deg})}, C{cosd(B{deg})}, C{tand(B{deg})}).
873
+
874
+ @see: Function L{sincos2d}.
875
+ '''
876
+ return _sincostan3(*sincos2d(float(deg)))
808
877
 
809
878
 
810
879
  def SM2m(sm):
@@ -861,11 +930,11 @@ def tan(rad, **raiser_kwds):
861
930
 
862
931
 
863
932
  def tan_(*rads, **raiser_kwds):
864
- '''Return the C{tangent} of angle(s) in C{radians}.
933
+ '''Yield the C{tangent} of angle(s) in C{radians}.
865
934
 
866
935
  @arg rads: One or more angles (each in C{radians}).
867
936
 
868
- @return: Yield the C{tan(B{rad})} for each angle.
937
+ @return: Yield C{tan(B{rad})} for each angle.
869
938
 
870
939
  @see: Function L{pygeodesy.tan} for futher details.
871
940
  '''
@@ -895,11 +964,11 @@ def tand(deg, **raiser_kwds):
895
964
 
896
965
 
897
966
  def tand_(*degs, **raiser_kwds):
898
- '''Return the C{tangent} of angle(s) in C{degrees}.
967
+ '''Yield the C{tangent} of angle(s) in C{degrees}.
899
968
 
900
969
  @arg degs: One or more angles (each in C{degrees}).
901
970
 
902
- @return: Yield the C{tan(B{deg})} for each angle.
971
+ @return: Yield C{tand(B{deg})} for each angle.
903
972
 
904
973
  @see: Function L{pygeodesy.tand} for futher details.
905
974
  '''
@@ -922,12 +991,16 @@ def tanPI_2_2(rad):
922
991
 
923
992
 
924
993
  def _tanu(s, c, raiser=True, **unused):
925
- '''(INTERNAL) Helper for functions C{_cotu}, C{tan}, C{tan_}, C{tand} and C{tand_}.
994
+ '''(INTERNAL) Helper for functions C{_cotu}, C{sincostan3},
995
+ C{sincostan3d}, C{tan}, C{tan_}, C{tand} and C{tand_}.
926
996
  '''
927
- if s:
997
+ if s is NAN or isnan(s):
998
+ s = NAN
999
+ elif s:
928
1000
  if raiser and isnear0(c):
929
1001
  raise ZeroDivisionError()
930
- s = _over(s, c)
1002
+ s = _over(s, c) if fabs(s) != fabs(c) else \
1003
+ _copysign(_1_0, (-s) if c < 0 else s)
931
1004
  elif c < 0:
932
1005
  s = -s # negate-0
933
1006
  return s
@@ -1118,7 +1191,7 @@ class _Wrap(object):
1118
1191
  '''
1119
1192
  if wrap and self._normal is not None:
1120
1193
  lat, lon = ll.latlon
1121
- if fabs(lon) > 180 or fabs(lat) > 90:
1194
+ if fabs(lon) > _180_0 or fabs(lat) > _90_0:
1122
1195
  _n = self.latlon
1123
1196
  ll = ll.copy(name=_n.__name__)
1124
1197
  ll.latlon = _n(lat, lon)
@@ -1234,7 +1307,7 @@ def wrap_normal(*normal):
1234
1307
  lat- and longitude individually by L{wrap90} or
1235
1308
  L{wrapPI_2} respectively L{wrap180}, L{wrapPI} or
1236
1309
  if C{None}, leave lat- and longitude I{unchanged}.
1237
- Do not supply any value to get the current setting.
1310
+ To get the current setting, do not specify.
1238
1311
 
1239
1312
  @return: The previous L{wrap_normal} setting (C{bool} or C{None}).
1240
1313
  '''