pygeodesy 24.11.11__py2.py3-none-any.whl → 25.1.5__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.
Files changed (118) hide show
  1. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/METADATA +34 -35
  2. PyGeodesy-25.1.5.dist-info/RECORD +118 -0
  3. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/WHEEL +1 -1
  4. pygeodesy/__init__.py +19 -19
  5. pygeodesy/__main__.py +1 -1
  6. pygeodesy/albers.py +5 -5
  7. pygeodesy/auxilats/_CX_4.py +1 -1
  8. pygeodesy/auxilats/_CX_6.py +1 -1
  9. pygeodesy/auxilats/_CX_8.py +1 -1
  10. pygeodesy/auxilats/_CX_Rs.py +1 -1
  11. pygeodesy/auxilats/__init__.py +1 -1
  12. pygeodesy/auxilats/__main__.py +1 -1
  13. pygeodesy/auxilats/auxAngle.py +5 -5
  14. pygeodesy/auxilats/auxDLat.py +6 -6
  15. pygeodesy/auxilats/auxDST.py +2 -2
  16. pygeodesy/auxilats/auxLat.py +5 -5
  17. pygeodesy/auxilats/auxily.py +2 -2
  18. pygeodesy/azimuthal.py +5 -5
  19. pygeodesy/basics.py +60 -8
  20. pygeodesy/booleans.py +1 -1
  21. pygeodesy/cartesianBase.py +22 -61
  22. pygeodesy/clipy.py +1 -1
  23. pygeodesy/constants.py +5 -5
  24. pygeodesy/css.py +1 -1
  25. pygeodesy/datums.py +1 -1
  26. pygeodesy/deprecated/__init__.py +2 -2
  27. pygeodesy/deprecated/bases.py +1 -1
  28. pygeodesy/deprecated/classes.py +86 -2
  29. pygeodesy/deprecated/consterns.py +1 -1
  30. pygeodesy/deprecated/datum.py +5 -5
  31. pygeodesy/deprecated/functions.py +42 -8
  32. pygeodesy/deprecated/nvector.py +1 -1
  33. pygeodesy/deprecated/rhumbBase.py +1 -1
  34. pygeodesy/deprecated/rhumbaux.py +1 -1
  35. pygeodesy/deprecated/rhumbsolve.py +1 -1
  36. pygeodesy/deprecated/rhumbx.py +1 -1
  37. pygeodesy/dms.py +1 -1
  38. pygeodesy/ecef.py +53 -56
  39. pygeodesy/elevations.py +1 -1
  40. pygeodesy/ellipsoidalBase.py +3 -3
  41. pygeodesy/ellipsoidalBaseDI.py +1 -1
  42. pygeodesy/ellipsoidalExact.py +1 -1
  43. pygeodesy/ellipsoidalGeodSolve.py +1 -1
  44. pygeodesy/ellipsoidalKarney.py +1 -1
  45. pygeodesy/ellipsoidalNvector.py +1 -1
  46. pygeodesy/ellipsoidalVincenty.py +6 -5
  47. pygeodesy/ellipsoids.py +4 -5
  48. pygeodesy/elliptic.py +6 -6
  49. pygeodesy/epsg.py +1 -1
  50. pygeodesy/errors.py +1 -1
  51. pygeodesy/etm.py +5 -5
  52. pygeodesy/fmath.py +18 -17
  53. pygeodesy/formy.py +409 -555
  54. pygeodesy/frechet.py +29 -86
  55. pygeodesy/fstats.py +1 -1
  56. pygeodesy/fsums.py +32 -33
  57. pygeodesy/gars.py +1 -1
  58. pygeodesy/geodesici.py +7 -7
  59. pygeodesy/geodesicw.py +1 -1
  60. pygeodesy/geodesicx/_C4_24.py +2 -2
  61. pygeodesy/geodesicx/_C4_27.py +2 -2
  62. pygeodesy/geodesicx/_C4_30.py +2 -2
  63. pygeodesy/geodesicx/__init__.py +2 -2
  64. pygeodesy/geodesicx/__main__.py +4 -5
  65. pygeodesy/geodesicx/gx.py +6 -5
  66. pygeodesy/geodesicx/gxarea.py +2 -2
  67. pygeodesy/geodesicx/gxbases.py +2 -2
  68. pygeodesy/geodesicx/gxline.py +16 -12
  69. pygeodesy/geodsolve.py +1 -1
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +277 -203
  72. pygeodesy/hausdorff.py +23 -81
  73. pygeodesy/heights.py +115 -150
  74. pygeodesy/internals.py +1 -1
  75. pygeodesy/interns.py +2 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +3 -3
  78. pygeodesy/ktm.py +16 -15
  79. pygeodesy/latlonBase.py +323 -409
  80. pygeodesy/lazily.py +53 -44
  81. pygeodesy/lcc.py +1 -1
  82. pygeodesy/ltp.py +46 -50
  83. pygeodesy/ltpTuples.py +147 -130
  84. pygeodesy/mgrs.py +1 -1
  85. pygeodesy/named.py +149 -3
  86. pygeodesy/namedTuples.py +58 -7
  87. pygeodesy/nvectorBase.py +122 -105
  88. pygeodesy/osgr.py +1 -1
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +1 -1
  91. pygeodesy/resections.py +18 -17
  92. pygeodesy/rhumb/__init__.py +1 -1
  93. pygeodesy/rhumb/aux_.py +2 -2
  94. pygeodesy/rhumb/bases.py +2 -2
  95. pygeodesy/rhumb/ekx.py +4 -4
  96. pygeodesy/rhumb/solve.py +1 -1
  97. pygeodesy/simplify.py +289 -401
  98. pygeodesy/solveBase.py +1 -1
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +5 -5
  101. pygeodesy/sphericalTrigonometry.py +7 -6
  102. pygeodesy/streprs.py +10 -5
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials.py +23 -16
  105. pygeodesy/units.py +16 -16
  106. pygeodesy/unitsBase.py +1 -1
  107. pygeodesy/ups.py +4 -4
  108. pygeodesy/utily.py +341 -211
  109. pygeodesy/utm.py +5 -5
  110. pygeodesy/utmups.py +1 -1
  111. pygeodesy/utmupsBase.py +1 -1
  112. pygeodesy/vector2d.py +5 -5
  113. pygeodesy/vector3d.py +14 -3
  114. pygeodesy/vector3dBase.py +5 -5
  115. pygeodesy/webmercator.py +1 -1
  116. pygeodesy/wgrs.py +1 -1
  117. PyGeodesy-24.11.11.dist-info/RECORD +0 -118
  118. {PyGeodesy-24.11.11.dist-info → PyGeodesy-25.1.5.dist-info}/top_level.txt +0 -0
pygeodesy/utily.py CHANGED
@@ -3,45 +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, _N_90_0, _180_0, \
17
- _N_180_0, _360_0, _400_0, _copysign_0_0, \
18
- _float as _F, _isfinite, isnan, isnear0, \
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
19
  _over, _umod_360, _umod_PI2
20
- from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get1, _ALL_LAZY, _MODS
21
- from pygeodesy.internals import _passargs # , _MODS?
20
+ from pygeodesy.errors import _ValueError, _xkwds, _ALL_LAZY, _MODS
21
+ from pygeodesy.internals import _passargs
22
22
  from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_
23
23
  # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .errors
24
24
  from pygeodesy.units import Degrees, Degrees_, Feet, Float, Lam, Lamd, \
25
- Meter, Meter2, Radians, Radians_
25
+ Meter, Meter2, Radians # Radians_
26
26
 
27
- from math import acos, asin, atan2, cos, degrees, fabs, radians, sin, tan # pow
27
+ from math import acos, asin, atan2 as _atan2, cos, degrees, fabs, radians, \
28
+ sin, tan as _tan # pow
28
29
 
29
30
  __all__ = _ALL_LAZY.utily
30
- __version__ = '24.10.12'
31
-
32
- # read constant name "_M_Unit" as "meter per Unit"
33
- _M_CHAIN = _F( 20.1168) # yard2m(1) * 22
34
- _M_FATHOM = _F( 1.8288) # yard2m(1) * 2 or _M_NM * 1e-3
35
- _M_FOOT = _F( 0.3048) # Int'l (1 / 3.2808398950131 = 10_000 / (254 * 12))
36
- _M_FOOT_GE = _F( 0.31608) # German Fuss (1 / 3.1637560111364)
37
- _M_FOOT_FR = _F( 0.3248406) # French Pied-du-Roi or pied (1 / 3.0784329298739)
38
- _M_FOOT_USVY = _F( 0.3048006096012192) # US Survey (1200 / 3937)
39
- _M_FURLONG = _F( 201.168) # 220 * yard2m(1) == 10 * m2chain(1)
40
- # _M_KM = _F(1000.0) # kilo meter
41
- # _M_NM = _F(1852.0) # nautical mile
42
- # _M_SM = _F(1609.344) # statute mile
43
- _M_TOISE = _F( 1.9490436) # French toise, 6 pieds (6 / 3.0784329298739)
44
- _M_YARD_UK = _F( 0.9144) # 254 * 12 * 3 / 10_000 == 3 * ft2m(1) Int'l
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
45
52
 
46
53
 
47
54
  def _abs1nan(x):
@@ -65,8 +72,7 @@ def acre2ha(acres):
65
72
 
66
73
  @raise ValueError: Invalid B{C{acres}}.
67
74
  '''
68
- # 0.40468564224 == acre2m2(1) / 10_000
69
- return Float(ha=Float(acres) * 0.40468564224)
75
+ return m2ha(acre2m2(acres))
70
76
 
71
77
 
72
78
  def acre2m2(acres):
@@ -78,8 +84,7 @@ def acre2m2(acres):
78
84
 
79
85
  @raise ValueError: Invalid B{C{acres}}.
80
86
  '''
81
- # 4046.8564224 == chain2m(1) * furlong2m(1)
82
- return Meter2(Float(acres) * 4046.8564224)
87
+ return Meter2(Float(acres=acres) * _M_ACRE)
83
88
 
84
89
 
85
90
  def asin1(x):
@@ -92,7 +97,7 @@ def atan1(y, x=_1_0):
92
97
  '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}
93
98
  using C{atan2} for consistency and to avoid C{ZeroDivisionError}.
94
99
  '''
95
- return atan2(-y, -x) if x < 0 else atan2(y, x or _0_0) # -0. to 0.
100
+ return _atan1u(y, x, atan2)
96
101
 
97
102
 
98
103
  def atan1d(y, x=_1_0):
@@ -101,18 +106,35 @@ def atan1d(y, x=_1_0):
101
106
 
102
107
  @see: Function L{pygeodesy.atan2d}.
103
108
  '''
104
- return atan2d(-y, -x) if x < 0 else atan2d(y, x or _0_0) # -0. to 0.
109
+ return _atan1u(y, x, atan2d)
110
+
111
+
112
+ def _atan1u(y, x, _2u):
113
+ '''(INTERNAL) Helper for functions C{atan1} and C{atan1d}.
114
+ '''
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
+ '''
105
127
 
106
128
 
107
129
  def atan2b(y, x):
108
- '''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}.
109
131
 
110
132
  @see: Function L{pygeodesy.atan2d}.
111
133
  '''
112
134
  b = atan2d(y, x)
113
135
  if b < 0:
114
136
  b += _360_0
115
- return b
137
+ return b or _0_0 # unsigned-0
116
138
 
117
139
 
118
140
  def atan2d(y, x, reverse=False):
@@ -122,29 +144,14 @@ def atan2d(y, x, reverse=False):
122
144
  @see: I{Karney}'s C++ function U{Math.atan2d
123
145
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
124
146
  '''
125
- if fabs(y) > fabs(x) > 0:
126
- if y < 0: # q = 3
127
- d = degrees(atan2(x, -y)) - _90_0
128
- else: # q = 2
129
- d = _90_0 - degrees(atan2(x, y))
130
- elif isnan(x) or isnan(y):
131
- return NAN
132
- elif y:
133
- if x > 0: # q = 0
134
- d = degrees(atan2(y, x))
135
- elif x < 0: # q = 1
136
- d = _copysign(_180_0, y) - degrees(atan2(y, -x))
137
- else: # x == 0
138
- d = _copysign(_90_0, y)
139
- else:
140
- d = _180_0 if x < 0 else _0_0
147
+ d = degrees(_atan2(y, x)) # preserves signed-0
141
148
  return _azireversed(d) if reverse else d
142
149
 
143
150
 
144
- def _azireversed(azimuth): # in .rhumbBase
145
- '''(INTERNAL) Return the I{reverse} B{C{azimuth}} in degrees M{[-180..+180]}.
151
+ def _azireversed(azi): # in .rhumbBase
152
+ '''(INTERNAL) Return the I{reverse} B{C{azi}} in degrees M{[-180..+180]}.
146
153
  '''
147
- return azimuth + (_N_180_0 if azimuth > 0 else _180_0)
154
+ return azi - _copysign(_180_0, azi)
148
155
 
149
156
 
150
157
  def chain2m(chains):
@@ -162,13 +169,11 @@ def chain2m(chains):
162
169
  def circle4(earth, lat):
163
170
  '''Get the equatorial or a parallel I{circle of latitude}.
164
171
 
165
- @arg earth: The earth radius, ellipsoid or datum
166
- (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
167
- L{Datum} or L{a_f2Tuple}).
172
+ @arg earth: The earth radius (C{meter}), ellipsoid or datum
173
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
168
174
  @arg lat: Geodetic latitude (C{degrees90}, C{str}).
169
175
 
170
- @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}
171
- instance.
176
+ @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}.
172
177
 
173
178
  @raise RangeError: Latitude B{C{lat}} outside valid range and
174
179
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
@@ -181,76 +186,78 @@ def circle4(earth, lat):
181
186
  return E.circle4(lat)
182
187
 
183
188
 
184
- def cot(rad, **error_kwds):
189
+ def cot(rad, **raiser_kwds):
185
190
  '''Return the C{cotangent} of an angle in C{radians}.
186
191
 
187
192
  @arg rad: Angle (C{radians}).
188
- @kwarg error_kwds: Error to raise (C{ValueError}).
193
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
194
+ ValueErrors or optionally, additional
195
+ ValueError keyword argments.
189
196
 
190
197
  @return: C{cot(B{rad})}.
191
198
 
192
- @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{rad})}.
199
+ @raise Error: If L{pygeodesy.isnear0}C{(sin(B{rad})}.
193
200
  '''
194
- s, c = sincos2(rad)
195
- if c:
196
- if isnear0(s):
197
- raise _valueError(cot, rad, **error_kwds)
198
- c = c / s # /= chokes PyChecker
199
- return c
201
+ try:
202
+ return _cotu(*sincos2(rad), **raiser_kwds)
203
+ except ZeroDivisionError:
204
+ raise _valueError(cot, rad, **raiser_kwds)
200
205
 
201
206
 
202
- def cot_(*rads, **error_kwds):
203
- '''Return the C{cotangent} of angle(s) in C{radiansresection}.
207
+ def cot_(*rads, **raiser_kwds):
208
+ '''Yield the C{cotangent} of angle(s) in C{radians}.
204
209
 
205
- @arg rads: One or more angles (C{radians}).
206
- @kwarg error_kwds: Error to raise (C{ValueError}).
210
+ @arg rads: One or more angles (each in C{radians}).
207
211
 
208
- @return: Yield the C{cot(B{rad})} for each angle.
212
+ @return: Yield C{cot(B{rad})} for each angle.
209
213
 
210
- @raise ValueError: See L{pygeodesy.cot}.
214
+ @see: Function L{pygeodesy.cot} for further details.
211
215
  '''
212
216
  try:
213
217
  for r in rads:
214
- yield cot(r)
215
- except ValueError:
216
- raise _valueError(cot_, r, **error_kwds)
218
+ yield _cotu(*sincos2(r), **raiser_kwds)
219
+ except ZeroDivisionError:
220
+ raise _valueError(cot_, r, **raiser_kwds)
217
221
 
218
222
 
219
- def cotd(deg, **error_kwds):
223
+ def cotd(deg, **raiser_kwds):
220
224
  '''Return the C{cotangent} of an angle in C{degrees}.
221
225
 
222
226
  @arg deg: Angle (C{degrees}).
223
- @kwarg error_kwds: Error to raise (C{ValueError}).
227
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
228
+ ValueErrors or optionally, additional
229
+ ValueError keyword argments.
224
230
 
225
231
  @return: C{cot(B{deg})}.
226
232
 
227
- @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{deg})}.
233
+ @raise Error: If L{pygeodesy.isnear0}C{(sin(B{deg})}.
228
234
  '''
229
- s, c = sincos2d(deg)
230
- if c:
231
- if isnear0(s):
232
- raise _valueError(cotd, deg, **error_kwds)
233
- c = c / s # /= chokes PyChecker
234
- elif s < 0:
235
- c = -c # negate-0
236
- return c
235
+ try:
236
+ return _cotu(*sincos2d(deg), **raiser_kwds)
237
+ except ZeroDivisionError:
238
+ raise _valueError(cotd, deg, **raiser_kwds)
237
239
 
238
240
 
239
- def cotd_(*degs, **error_kwds):
240
- '''Return the C{cotangent} of angle(s) in C{degrees}.
241
+ def cotd_(*degs, **raiser_kwds):
242
+ '''Yield the C{cotangent} of angle(s) in C{degrees}.
241
243
 
242
- @arg degs: One or more angles (C{degrees}).
243
- @kwarg error_kwds: Error to raise (C{ValueError}).
244
+ @arg degs: One or more angles (each in C{degrees}).
244
245
 
245
- @return: Yield the C{cot(B{deg})} for each angle.
246
+ @return: Yield C{cotd(B{deg})} for each angle.
246
247
 
247
- @raise ValueError: See L{pygeodesy.cotd}.
248
+ @see: Function L{pygeodesy.cotd} for further details.
248
249
  '''
249
250
  try:
250
251
  for d in degs:
251
- yield cotd(d)
252
- except ValueError:
253
- raise _valueError(cotd_, d, **error_kwds)
252
+ yield _cotu(*sincos2d(d), **raiser_kwds)
253
+ except ZeroDivisionError:
254
+ raise _valueError(cotd_, d, **raiser_kwds)
255
+
256
+
257
+ def _cotu(s, c, **raiser_kwds):
258
+ '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.
259
+ '''
260
+ return _tanu(c, s, **raiser_kwds)
254
261
 
255
262
 
256
263
  def degrees90(rad):
@@ -290,30 +297,27 @@ def degrees2grades(deg):
290
297
 
291
298
  @return: Angle (C{grades}).
292
299
  '''
293
- return Degrees(deg) * _400_0 / _360_0
300
+ return Float(grades=Degrees(deg) * _G_DEG)
294
301
 
295
302
 
296
303
  def degrees2m(deg, radius=R_M, lat=0):
297
- '''Convert an angle to a distance along the equator or
298
- along the parallel at an other (geodetic) latitude.
304
+ '''Convert an angle to a distance along the equator or along a parallel
305
+ at (geodetic) latitude.
299
306
 
300
307
  @arg deg: The angle (C{degrees}).
301
- @kwarg radius: Mean earth radius, ellipsoid or datum
302
- (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
303
- L{Datum} or L{a_f2Tuple}).
308
+ @kwarg radius: Mean earth radius (C{meter}), an ellipsoid or datum
309
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
304
310
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
305
311
 
306
- @return: Distance (C{meter}, same units as B{C{radius}}
307
- or equatorial and polar radii) or C{0.0} for
308
- near-polar B{C{lat}}.
312
+ @return: Distance (C{meter}, same units as B{C{radius}} or polar and
313
+ equatorial radii) or C{0.0} for near-polar B{C{lat}}.
309
314
 
310
315
  @raise RangeError: Latitude B{C{lat}} outside valid range and
311
316
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
312
317
 
313
318
  @raise TypeError: Invalid B{C{radius}}.
314
319
 
315
- @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or
316
- B{C{lat}}.
320
+ @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or B{C{lat}}.
317
321
 
318
322
  @see: Function L{radians2m} and L{m2degrees}.
319
323
  '''
@@ -349,9 +353,9 @@ def ft2m(feet, usurvey=False, pied=False, fuss=False):
349
353
 
350
354
  @raise ValueError: Invalid B{C{feet}}.
351
355
  '''
352
- return Meter(Feet(feet) * (_M_FOOT_USVY if usurvey else
353
- (_M_FOOT_FR if pied else
354
- (_M_FOOT_GE if fuss else _M_FOOT))))
356
+ return Meter(Feet(feet) * (_M_FOOT_US if usurvey else
357
+ (_M_FOOT_FR if pied else
358
+ (_M_FOOT_GE if fuss else _M_FOOT))))
355
359
 
356
360
 
357
361
  def furlong2m(furlongs):
@@ -373,7 +377,7 @@ def grades(rad):
373
377
 
374
378
  @return: Angle (C{grades}).
375
379
  '''
376
- return Float(grades=Float(rad=rad) * _400_0 / PI2)
380
+ return Float(grades=Radians(rad) * _G_RAD)
377
381
 
378
382
 
379
383
  def grades400(rad):
@@ -383,7 +387,7 @@ def grades400(rad):
383
387
 
384
388
  @return: Angle, wrapped (C{grades}).
385
389
  '''
386
- return Float(grades400=(grades(rad) % _400_0) or _0_0) # _umod_400
390
+ return Float(grades400=wrapPI2(rad) * _G_RAD)
387
391
 
388
392
 
389
393
  def grades2degrees(gon):
@@ -393,7 +397,7 @@ def grades2degrees(gon):
393
397
 
394
398
  @return: Angle (C{degrees}).
395
399
  '''
396
- return Degrees(Float(gon=gon) * _360_0 / _400_0)
400
+ return Degrees(Float(gon=gon) / _G_DEG)
397
401
 
398
402
 
399
403
  def grades2radians(gon):
@@ -403,7 +407,41 @@ def grades2radians(gon):
403
407
 
404
408
  @return: Angle (C{radians}).
405
409
  '''
406
- return Radians(Float(gon=gon) * PI2 / _400_0)
410
+ return Radians(Float(gon=gon) / _G_RAD)
411
+
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
407
445
 
408
446
 
409
447
  def km2m(km):
@@ -424,6 +462,18 @@ def _loneg(lon):
424
462
  return _180_0 - lon
425
463
 
426
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
+
427
477
  def m2chain(meter):
428
478
  '''Convert meter to I{UK} chains.
429
479
 
@@ -433,17 +483,16 @@ def m2chain(meter):
433
483
 
434
484
  @raise ValueError: Invalid B{C{meter}}.
435
485
  '''
436
- return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049709695378986715
486
+ return Float(chain=Meter(meter) / _M_CHAIN) # * 0.049_709_695_378_986_715
437
487
 
438
488
 
439
489
  def m2degrees(distance, radius=R_M, lat=0):
440
- '''Convert a distance to an angle along the equator or
441
- along the parallel at an other (geodetic) latitude.
490
+ '''Convert a distance to an angle along the equator or along a parallel
491
+ at (geodetic) latitude.
442
492
 
443
493
  @arg distance: Distance (C{meter}, same units as B{C{radius}}).
444
- @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
445
- an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or
446
- L{a_f2Tuple}).
494
+ @kwarg radius: Mean earth radius (C{meter}), an ellipsoid or datum
495
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
447
496
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
448
497
 
449
498
  @return: Angle (C{degrees}) or C{INF} for near-polar B{C{lat}}.
@@ -453,8 +502,7 @@ def m2degrees(distance, radius=R_M, lat=0):
453
502
 
454
503
  @raise TypeError: Invalid B{C{radius}}.
455
504
 
456
- @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
457
- or B{C{lat}}.
505
+ @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.
458
506
 
459
507
  @see: Function L{m2radians} and L{degrees2m}.
460
508
  '''
@@ -473,7 +521,7 @@ def m2fathom(meter):
473
521
  @see: Function L{m2toise}, U{Fathom<https://WikiPedia.org/wiki/Fathom>}
474
522
  and U{Klafter<https://WikiPedia.org/wiki/Klafter>}.
475
523
  '''
476
- return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546806649
524
+ return Float(fathom=Meter(meter) / _M_FATHOM) # * 0.546_806_649
477
525
 
478
526
 
479
527
  def m2ft(meter, usurvey=False, pied=False, fuss=False):
@@ -490,11 +538,11 @@ def m2ft(meter, usurvey=False, pied=False, fuss=False):
490
538
 
491
539
  @raise ValueError: Invalid B{C{meter}}.
492
540
  '''
493
- # * 3.2808333333333333, US Survey 3937 / 1200
494
- # * 3.2808398950131235, Int'l 10_000 / (254 * 12)
495
- return Float(feet=Meter(meter) / (_M_FOOT_USVY if usurvey else
496
- (_M_FOOT_FR if pied else
497
- (_M_FOOT_GE if fuss else _M_FOOT))))
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)
543
+ return Float(feet=Meter(meter) / (_M_FOOT_US if usurvey else
544
+ (_M_FOOT_FR if pied else
545
+ (_M_FOOT_GE if fuss else _M_FOOT))))
498
546
 
499
547
 
500
548
  def m2furlong(meter):
@@ -506,7 +554,19 @@ def m2furlong(meter):
506
554
 
507
555
  @raise ValueError: Invalid B{C{meter}}.
508
556
  '''
509
- 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)
510
570
 
511
571
 
512
572
  def m2km(meter):
@@ -530,17 +590,16 @@ def m2NM(meter):
530
590
 
531
591
  @raise ValueError: Invalid B{C{meter}}.
532
592
  '''
533
- return Float(NM=Meter(meter) / _M_NM) # * 5.39956804e-4
593
+ return Float(NM=Meter(meter) / _M_NM) # * 5.399_568_04e-4
534
594
 
535
595
 
536
596
  def m2radians(distance, radius=R_M, lat=0):
537
- '''Convert a distance to an angle along the equator or along the
538
- parallel at an other (geodetic) latitude.
597
+ '''Convert a distance to an angle along the equator or along a parallel
598
+ at (geodetic) latitude.
539
599
 
540
600
  @arg distance: Distance (C{meter}, same units as B{C{radius}}).
541
- @kwarg radius: Mean earth radius, ellipsoid or datum (C{meter},
542
- an L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or
543
- L{a_f2Tuple}).
601
+ @kwarg radius: Mean earth radius (C{meter}, an ellipsoid or datum
602
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
544
603
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
545
604
 
546
605
  @return: Angle (C{radians}) or C{INF} for near-polar B{C{lat}}.
@@ -550,8 +609,7 @@ def m2radians(distance, radius=R_M, lat=0):
550
609
 
551
610
  @raise TypeError: Invalid B{C{radius}}.
552
611
 
553
- @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
554
- or B{C{lat}}.
612
+ @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.
555
613
 
556
614
  @see: Function L{m2degrees} and L{radians2m}.
557
615
  '''
@@ -568,7 +626,7 @@ def m2SM(meter):
568
626
 
569
627
  @raise ValueError: Invalid B{C{meter}}.
570
628
  '''
571
- 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
572
630
 
573
631
 
574
632
  def m2toise(meter):
@@ -582,7 +640,7 @@ def m2toise(meter):
582
640
 
583
641
  @see: Function L{m2fathom}.
584
642
  '''
585
- return Float(toise=Meter(meter) / _M_TOISE) # * 0.513083632632119
643
+ return Float(toise=Meter(meter) / _M_TOISE) # * 0.513_083_632_632_119
586
644
 
587
645
 
588
646
  def m2yard(meter):
@@ -594,7 +652,7 @@ def m2yard(meter):
594
652
 
595
653
  @raise ValueError: Invalid B{C{meter}}.
596
654
  '''
597
- 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
598
656
 
599
657
 
600
658
  def NM2m(nm):
@@ -610,26 +668,23 @@ def NM2m(nm):
610
668
 
611
669
 
612
670
  def radians2m(rad, radius=R_M, lat=0):
613
- '''Convert an angle to a distance along the equator or
614
- along the parallel at an other (geodetic) latitude.
671
+ '''Convert an angle to a distance along the equator or along a parallel
672
+ at (geodetic) latitude.
615
673
 
616
674
  @arg rad: The angle (C{radians}).
617
- @kwarg radius: Mean earth radius, ellipsoid or datum
618
- (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
619
- L{Datum} or L{a_f2Tuple}).
675
+ @kwarg radius: Mean earth radius (C{meter}) or an ellipsoid or datum
676
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
620
677
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
621
678
 
622
- @return: Distance (C{meter}, same units as B{C{radius}}
623
- or equatorial and polar radii) or C{0.0} for
624
- near-polar B{C{lat}}.
679
+ @return: Distance (C{meter}, same units as B{C{radius}} or polar and
680
+ equatorial radii) or C{0.0} for near-polar B{C{lat}}.
625
681
 
626
682
  @raise RangeError: Latitude B{C{lat}} outside valid range and
627
683
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
628
684
 
629
685
  @raise TypeError: Invalid B{C{radius}}.
630
686
 
631
- @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or
632
- B{C{lat}}.
687
+ @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or B{C{lat}}.
633
688
 
634
689
  @see: Function L{degrees2m} and L{m2radians}.
635
690
  '''
@@ -673,20 +728,25 @@ def radiansPI_2(deg):
673
728
  return wrapPI_2(radians(deg))
674
729
 
675
730
 
676
- def _sin0cos2(q, r, sign):
677
- '''(INTERNAL) 2-tuple (C{sin(r), cos(r)}) in quadrant C{0 <= B{q} <= 3}
678
- 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>}
679
735
  '''
680
736
  if r < PI_2:
681
- s, c = sin(r), cos(r)
682
- t = s, c, -s, -c, s
683
- else: # r == PI_2
684
- t = _1_0, _0_0, _N_1_0, _0_0, _1_0
685
- # else: # r == 0, testUtility failures
686
- # 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
687
747
  # q &= 3
688
- s = t[q] or _copysign_0_0(sign)
689
- c = t[q + 1] or _0_0
748
+ s = t[q] or _copysign_0_0(sign)
749
+ c = t[q + 1] or _0_0
690
750
  return s, c
691
751
 
692
752
 
@@ -698,8 +758,8 @@ def SinCos2(x):
698
758
  @return: 2-Tuple (C{sin(B{x})}, C{cos(B{x})}).
699
759
  '''
700
760
  return sincos2d(x) if isinstanceof(x, Degrees, Degrees_) else (
701
- sincos2(x) if isinstanceof(x, Radians, Radians_) else
702
- sincos2(float(x))) # assume C{radians}
761
+ # sincos2(x) if isinstanceof(x, Radians, Radians_) else
762
+ sincos2(Radians(x))) # assume C{radians}
703
763
 
704
764
 
705
765
  def sincos2(rad):
@@ -720,20 +780,21 @@ def sincos2(rad):
720
780
  q = int(rad * _2__PI) # int(math.floor)
721
781
  if q < 0:
722
782
  q -= 1
723
- 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)
724
785
  else:
725
786
  t = NAN, NAN
726
787
  return t
727
788
 
728
789
 
729
790
  def sincos2_(*rads):
730
- '''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}.
731
792
 
732
793
  @arg rads: One or more angles (C{radians}).
733
794
 
734
- @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.
735
796
 
736
- @see: function L{sincos2}.
797
+ @see: Function L{sincos2}.
737
798
  '''
738
799
  for r in rads:
739
800
  s, c = sincos2(r)
@@ -741,7 +802,7 @@ def sincos2_(*rads):
741
802
  yield c
742
803
 
743
804
 
744
- def sincos2d(deg, **adeg):
805
+ def sincos2d(deg, adeg=_0_0):
745
806
  '''Return the C{sine} and C{cosine} of an angle in C{degrees}.
746
807
 
747
808
  @arg deg: Angle (C{degrees}).
@@ -763,20 +824,19 @@ def sincos2d(deg, **adeg):
763
824
  q -= 1
764
825
  d = deg - q * _90_0
765
826
  if adeg:
766
- t = _xkwds_get1(adeg, adeg=_0_0)
767
- d = _MODS.karney._around(d + t)
768
- 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)
769
829
  else:
770
830
  t = NAN, NAN
771
831
  return t
772
832
 
773
833
 
774
834
  def sincos2d_(*degs):
775
- '''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}.
776
836
 
777
837
  @arg degs: One or more angles (C{degrees}).
778
838
 
779
- @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.
780
840
 
781
841
  @see: Function L{sincos2d}.
782
842
  '''
@@ -795,9 +855,25 @@ def sincostan3(rad):
795
855
 
796
856
  @see: Function L{sincos2}.
797
857
  '''
798
- s, c = sincos2(float(rad))
799
- t = NAN if s is NAN else (_over(s, c) if s else neg(s, neg0=c < 0))
800
- 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)))
801
877
 
802
878
 
803
879
  def SM2m(sm):
@@ -832,41 +908,75 @@ def tan_2(rad, **semi): # edge=1
832
908
  _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)
833
909
  raise _ValueError(n, rad, txt=_semi_circular_)
834
910
 
835
- return tan(rad * _0_5) if _isfinite(rad) else NAN
911
+ return _tan(rad * _0_5) if _isfinite(rad) else NAN
912
+
913
+
914
+ def tan(rad, **raiser_kwds):
915
+ '''Return the C{tangent} of an angle in C{radians}.
916
+
917
+ @arg rad: Angle (C{radians}).
918
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
919
+ ValueErrors or optionally, additional
920
+ ValueError keyword argments.
921
+
922
+ @return: C{tan(B{rad})}.
923
+
924
+ @raise Error: If L{pygeodesy.isnear0}C{(cos(B{rad})}.
925
+ '''
926
+ try:
927
+ return _tanu(*sincos2(rad), **raiser_kwds)
928
+ except ZeroDivisionError:
929
+ raise _valueError(tan, rad, **raiser_kwds)
930
+
931
+
932
+ def tan_(*rads, **raiser_kwds):
933
+ '''Yield the C{tangent} of angle(s) in C{radians}.
836
934
 
935
+ @arg rads: One or more angles (each in C{radians}).
837
936
 
838
- def tand(deg, **error_kwds):
937
+ @return: Yield C{tan(B{rad})} for each angle.
938
+
939
+ @see: Function L{pygeodesy.tan} for futher details.
940
+ '''
941
+ try:
942
+ for r in rads:
943
+ yield _tanu(*sincos2(r), **raiser_kwds)
944
+ except ZeroDivisionError:
945
+ raise _valueError(tan_, r, **raiser_kwds)
946
+
947
+
948
+ def tand(deg, **raiser_kwds):
839
949
  '''Return the C{tangent} of an angle in C{degrees}.
840
950
 
841
951
  @arg deg: Angle (C{degrees}).
842
- @kwarg error_kwds: Error to raise (C{ValueError}).
952
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
953
+ ValueErrors or optionally, additional
954
+ ValueError keyword argments.
843
955
 
844
956
  @return: C{tan(B{deg})}.
845
957
 
846
- @raise ValueError: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
958
+ @raise Error: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
847
959
  '''
848
- s, c = sincos2d(deg)
849
- if s:
850
- if isnear0(c):
851
- raise _valueError(tand, deg, **error_kwds)
852
- s = s / c # /= chokes PyChecker
853
- elif c < 0:
854
- s = -s # negate-0
855
- return s
960
+ try:
961
+ return _tanu(*sincos2d(deg), **raiser_kwds)
962
+ except ZeroDivisionError:
963
+ raise _valueError(tand, deg, **raiser_kwds)
856
964
 
857
965
 
858
- def tand_(*degs, **error_kwds):
859
- '''Return the C{tangent} of angle(s) in C{degrees}.
966
+ def tand_(*degs, **raiser_kwds):
967
+ '''Yield the C{tangent} of angle(s) in C{degrees}.
860
968
 
861
- @arg degs: One or more angles (C{degrees}).
862
- @kwarg error_kwds: Error to raise (C{ValueError}).
969
+ @arg degs: One or more angles (each in C{degrees}).
863
970
 
864
- @return: Yield the C{tan(B{deg})} for each angle.
971
+ @return: Yield C{tand(B{deg})} for each angle.
865
972
 
866
- @raise ValueError: See L{pygeodesy.tand}.
973
+ @see: Function L{pygeodesy.tand} for futher details.
867
974
  '''
868
- for d in degs:
869
- yield tand(d, **error_kwds)
975
+ try:
976
+ for d in degs:
977
+ yield _tanu(*sincos2d(d), **raiser_kwds)
978
+ except ZeroDivisionError:
979
+ raise _valueError(tand_, d, **raiser_kwds)
870
980
 
871
981
 
872
982
  def tanPI_2_2(rad):
@@ -876,8 +986,24 @@ def tanPI_2_2(rad):
876
986
 
877
987
  @return: M{tan((rad + PI/2) / 2)} (C{float}).
878
988
  '''
879
- return tan((rad + PI_2) * _0_5) if _isfinite(rad) else (
880
- NAN if isnan(rad) else (_N_90_0 if rad < 0 else _90_0))
989
+ return _tan((rad + PI_2) * _0_5) if _isfinite(rad) else (
990
+ NAN if isnan(rad) else _copysign(_90_0, rad))
991
+
992
+
993
+ def _tanu(s, c, raiser=True, **unused):
994
+ '''(INTERNAL) Helper for functions C{_cotu}, C{sincostan3},
995
+ C{sincostan3d}, C{tan}, C{tan_}, C{tand} and C{tand_}.
996
+ '''
997
+ if s is NAN or isnan(s):
998
+ s = NAN
999
+ elif s:
1000
+ if raiser and isnear0(c):
1001
+ raise ZeroDivisionError()
1002
+ s = _over(s, c) if fabs(s) != fabs(c) else \
1003
+ _copysign(_1_0, (-s) if c < 0 else s)
1004
+ elif c < 0:
1005
+ s = -s # negate-0
1006
+ return s
881
1007
 
882
1008
 
883
1009
  def toise2m(toises):
@@ -980,11 +1106,11 @@ def unrollPI(rad1, rad2, wrap=True):
980
1106
  return r, rad2
981
1107
 
982
1108
 
983
- def _valueError(where, x, **kwds):
984
- '''(INTERNAL) Return a C{_ValueError}.
1109
+ def _valueError(where, x, raiser=True, **kwds):
1110
+ '''(INTERNAL) Return a C{_ValueError} or C{None}.
985
1111
  '''
986
1112
  t = _MODS.streprs.Fmt.PAREN(where.__name__, x)
987
- return _ValueError(t, **kwds)
1113
+ return _ValueError(t, **kwds) if raiser else None
988
1114
 
989
1115
 
990
1116
  class _Wrap(object):
@@ -1065,7 +1191,7 @@ class _Wrap(object):
1065
1191
  '''
1066
1192
  if wrap and self._normal is not None:
1067
1193
  lat, lon = ll.latlon
1068
- if fabs(lon) > 180 or fabs(lat) > 90:
1194
+ if fabs(lon) > _180_0 or fabs(lat) > _90_0:
1069
1195
  _n = self.latlon
1070
1196
  ll = ll.copy(name=_n.__name__)
1071
1197
  ll.latlon = _n(lat, lon)
@@ -1100,8 +1226,7 @@ def wrap90(deg):
1100
1226
 
1101
1227
  @return: Degrees, wrapped (C{degrees90}).
1102
1228
  '''
1103
- w = wrap180(deg)
1104
- return (w - _180_0) if w > 90 else ((w + _180_0) if w < -90 else w)
1229
+ return _wrapu(wrap180(deg), _180_0, _90_0)
1105
1230
 
1106
1231
 
1107
1232
  def wrap180(deg):
@@ -1113,9 +1238,9 @@ def wrap180(deg):
1113
1238
  '''
1114
1239
  d = float(deg)
1115
1240
  w = _umod_360(d)
1116
- if w > 180:
1241
+ if w > _180_0:
1117
1242
  w -= _360_0
1118
- elif d < 0 and w == 180:
1243
+ elif d < 0 and w == _180_0:
1119
1244
  w = -w
1120
1245
  return w
1121
1246
 
@@ -1163,8 +1288,7 @@ def wrapPI_2(rad):
1163
1288
 
1164
1289
  @return: Radians, wrapped (C{radiansPI_2}).
1165
1290
  '''
1166
- w = wrapPI(rad)
1167
- return (w - PI) if w > PI_2 else ((w + PI) if w < (-PI_2) else w)
1291
+ return _wrapu(wrapPI(rad), PI, PI_2)
1168
1292
 
1169
1293
 
1170
1294
  # def wraplatlon(lat, lon):
@@ -1183,7 +1307,7 @@ def wrap_normal(*normal):
1183
1307
  lat- and longitude individually by L{wrap90} or
1184
1308
  L{wrapPI_2} respectively L{wrap180}, L{wrapPI} or
1185
1309
  if C{None}, leave lat- and longitude I{unchanged}.
1186
- Do not supply any value to get the current setting.
1310
+ To get the current setting, do not specify.
1187
1311
 
1188
1312
  @return: The previous L{wrap_normal} setting (C{bool} or C{None}).
1189
1313
  '''
@@ -1199,6 +1323,12 @@ def wrap_normal(*normal):
1199
1323
  # return wrapPI_2(phi), wrapPI(lam)
1200
1324
 
1201
1325
 
1326
+ def _wrapu(w, H, Q):
1327
+ '''(INTERNAL) Helper for functions C{wrap180} and C{wrapPI}.
1328
+ '''
1329
+ return (w - H) if w > Q else ((w + H) if w < (-Q) else w)
1330
+
1331
+
1202
1332
  def yard2m(yards):
1203
1333
  '''Convert I{UK} yards to meter.
1204
1334
 
@@ -1212,7 +1342,7 @@ def yard2m(yards):
1212
1342
 
1213
1343
  # **) MIT License
1214
1344
  #
1215
- # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1345
+ # Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1216
1346
  #
1217
1347
  # Permission is hereby granted, free of charge, to any person obtaining a
1218
1348
  # copy of this software and associated documentation files (the "Software"),