pygeodesy 24.10.24__py2.py3-none-any.whl → 24.12.12__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.10.24.dist-info → PyGeodesy-24.12.12.dist-info}/METADATA +6 -6
  2. PyGeodesy-24.12.12.dist-info/RECORD +118 -0
  3. {PyGeodesy-24.10.24.dist-info → PyGeodesy-24.12.12.dist-info}/WHEEL +1 -1
  4. pygeodesy/__init__.py +5 -5
  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 +55 -65
  19. pygeodesy/basics.py +35 -34
  20. pygeodesy/booleans.py +37 -37
  21. pygeodesy/cartesianBase.py +26 -65
  22. pygeodesy/clipy.py +1 -1
  23. pygeodesy/constants.py +7 -7
  24. pygeodesy/css.py +8 -9
  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 +10 -10
  29. pygeodesy/deprecated/consterns.py +1 -1
  30. pygeodesy/deprecated/datum.py +1 -1
  31. pygeodesy/deprecated/functions.py +23 -13
  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 +63 -69
  39. pygeodesy/elevations.py +1 -1
  40. pygeodesy/ellipsoidalBase.py +106 -121
  41. pygeodesy/ellipsoidalBaseDI.py +115 -119
  42. pygeodesy/ellipsoidalExact.py +36 -38
  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 +7 -8
  48. pygeodesy/elliptic.py +6 -6
  49. pygeodesy/epsg.py +1 -1
  50. pygeodesy/errors.py +25 -25
  51. pygeodesy/etm.py +84 -76
  52. pygeodesy/fmath.py +54 -51
  53. pygeodesy/formy.py +74 -106
  54. pygeodesy/frechet.py +1 -1
  55. pygeodesy/fstats.py +1 -1
  56. pygeodesy/fsums.py +82 -72
  57. pygeodesy/gars.py +1 -1
  58. pygeodesy/geodesici.py +4 -4
  59. pygeodesy/geodesicw.py +16 -15
  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 +3 -3
  64. pygeodesy/geodesicx/__main__.py +1 -1
  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 +8 -17
  70. pygeodesy/geohash.py +1 -1
  71. pygeodesy/geoids.py +6 -6
  72. pygeodesy/hausdorff.py +1 -1
  73. pygeodesy/heights.py +3 -3
  74. pygeodesy/internals.py +64 -80
  75. pygeodesy/interns.py +2 -3
  76. pygeodesy/iters.py +1 -1
  77. pygeodesy/karney.py +4 -4
  78. pygeodesy/ktm.py +20 -21
  79. pygeodesy/latlonBase.py +296 -346
  80. pygeodesy/lazily.py +15 -15
  81. pygeodesy/lcc.py +5 -5
  82. pygeodesy/ltp.py +55 -59
  83. pygeodesy/ltpTuples.py +208 -192
  84. pygeodesy/mgrs.py +9 -10
  85. pygeodesy/named.py +153 -3
  86. pygeodesy/namedTuples.py +58 -7
  87. pygeodesy/nvectorBase.py +122 -105
  88. pygeodesy/osgr.py +10 -13
  89. pygeodesy/points.py +1 -1
  90. pygeodesy/props.py +3 -3
  91. pygeodesy/resections.py +26 -26
  92. pygeodesy/rhumb/__init__.py +2 -2
  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 +4 -4
  97. pygeodesy/simplify.py +291 -403
  98. pygeodesy/solveBase.py +1 -1
  99. pygeodesy/sphericalBase.py +1 -1
  100. pygeodesy/sphericalNvector.py +84 -127
  101. pygeodesy/sphericalTrigonometry.py +66 -71
  102. pygeodesy/streprs.py +10 -5
  103. pygeodesy/trf.py +1 -1
  104. pygeodesy/triaxials.py +23 -16
  105. pygeodesy/units.py +17 -17
  106. pygeodesy/unitsBase.py +1 -1
  107. pygeodesy/ups.py +4 -4
  108. pygeodesy/utily.py +202 -145
  109. pygeodesy/utm.py +10 -10
  110. pygeodesy/utmups.py +1 -1
  111. pygeodesy/utmupsBase.py +1 -1
  112. pygeodesy/vector2d.py +17 -17
  113. pygeodesy/vector3d.py +32 -23
  114. pygeodesy/vector3dBase.py +22 -19
  115. pygeodesy/webmercator.py +5 -5
  116. pygeodesy/wgrs.py +5 -5
  117. PyGeodesy-24.10.24.dist-info/RECORD +0 -118
  118. {PyGeodesy-24.10.24.dist-info → PyGeodesy-24.12.12.dist-info}/top_level.txt +0 -0
pygeodesy/utily.py CHANGED
@@ -13,35 +13,36 @@ from __future__ import division as _; del _ # PYCHOK semicolon
13
13
  from pygeodesy.basics import _copysign, isinstanceof, isint, isstr, neg
14
14
  from pygeodesy.constants import EPS, EPS0, INF, NAN, PI, PI2, PI_2, R_M, \
15
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, \
19
- _over, _umod_360, _umod_PI2
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
20
19
  from pygeodesy.errors import _ValueError, _xkwds, _xkwds_get1, _ALL_LAZY, _MODS
21
- from pygeodesy.internals import _passargs # , _MODS?
20
+ from pygeodesy.internals import _passarg, _passargs # , _MODS?
22
21
  from pygeodesy.interns import _edge_, _radians_, _semi_circular_, _SPACE_
23
22
  # from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS # from .errors
24
23
  from pygeodesy.units import Degrees, Degrees_, Feet, Float, Lam, Lamd, \
25
24
  Meter, Meter2, Radians, Radians_
26
25
 
27
- from math import acos, asin, atan2, cos, degrees, fabs, radians, sin, tan # pow
26
+ from math import acos, asin, atan2 as _atan2, cos, degrees, fabs, radians, \
27
+ sin, tan as _tan # pow
28
28
 
29
29
  __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
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
45
46
 
46
47
 
47
48
  def _abs1nan(x):
@@ -92,7 +93,7 @@ def atan1(y, x=_1_0):
92
93
  '''Return C{atan(B{y} / B{x})} angle in C{radians} M{[-PI/2..+PI/2]}
93
94
  using C{atan2} for consistency and to avoid C{ZeroDivisionError}.
94
95
  '''
95
- return atan2(-y, -x) if x < 0 else atan2(y, x or _0_0) # -0. to 0.
96
+ return _atan2(-y, -x) if x < 0 else _atan2(y, x or _0_0) # -0. to 0.
96
97
 
97
98
 
98
99
  def atan1d(y, x=_1_0):
@@ -104,6 +105,15 @@ def atan1d(y, x=_1_0):
104
105
  return atan2d(-y, -x) if x < 0 else atan2d(y, x or _0_0) # -0. to 0.
105
106
 
106
107
 
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>}.
113
+ '''
114
+ return _atan2u(y, x, _passarg, PI, PI_2)
115
+
116
+
107
117
  def atan2b(y, x):
108
118
  '''Return C{atan2(B{y}, B{x})} in degrees M{[0..+360]}.
109
119
 
@@ -122,23 +132,30 @@ def atan2d(y, x, reverse=False):
122
132
  @see: I{Karney}'s C++ function U{Math.atan2d
123
133
  <https://GeographicLib.SourceForge.io/C++/doc/classGeographicLib_1_1Math.html>}.
124
134
  '''
135
+ d = _atan2u(y, x, degrees, _180_0, _90_0)
136
+ return _azireversed(d) if reverse else d
137
+
138
+
139
+ def _atan2u(y, x, _2u, H, Q): # Half, Quarter turn in units
140
+ '''(INTERNAL) Helper for functions C{atan2} and C{atan2d}.
141
+ '''
125
142
  if fabs(y) > fabs(x) > 0:
126
143
  if y < 0: # q = 3
127
- d = degrees(atan2(x, -y)) - _90_0
144
+ r = _2u(_atan2(x, -y)) - Q
128
145
  else: # q = 2
129
- d = _90_0 - degrees(atan2(x, y))
146
+ r = Q - _2u(_atan2(x, y))
130
147
  elif isnan(x) or isnan(y):
131
148
  return NAN
132
149
  elif y:
133
150
  if x > 0: # q = 0
134
- d = degrees(atan2(y, x))
151
+ r = _2u(_atan2(y, x))
135
152
  elif x < 0: # q = 1
136
- d = _copysign(_180_0, y) - degrees(atan2(y, -x))
153
+ r = _copysign(H, y) - _2u(_atan2(y, -x))
137
154
  else: # x == 0
138
- d = _copysign(_90_0, y)
139
- else:
140
- d = _180_0 if x < 0 else _0_0
141
- return _azireversed(d) if reverse else d
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
142
159
 
143
160
 
144
161
  def _azireversed(azimuth): # in .rhumbBase
@@ -162,13 +179,11 @@ def chain2m(chains):
162
179
  def circle4(earth, lat):
163
180
  '''Get the equatorial or a parallel I{circle of latitude}.
164
181
 
165
- @arg earth: The earth radius, ellipsoid or datum
166
- (C{meter}, L{Ellipsoid}, L{Ellipsoid2},
167
- L{Datum} or L{a_f2Tuple}).
182
+ @arg earth: The earth radius (C{meter}), ellipsoid or datum
183
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
168
184
  @arg lat: Geodetic latitude (C{degrees90}, C{str}).
169
185
 
170
- @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}
171
- instance.
186
+ @return: A L{Circle4Tuple}C{(radius, height, lat, beta)}.
172
187
 
173
188
  @raise RangeError: Latitude B{C{lat}} outside valid range and
174
189
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
@@ -181,76 +196,78 @@ def circle4(earth, lat):
181
196
  return E.circle4(lat)
182
197
 
183
198
 
184
- def cot(rad, **error_kwds):
199
+ def cot(rad, **raiser_kwds):
185
200
  '''Return the C{cotangent} of an angle in C{radians}.
186
201
 
187
202
  @arg rad: Angle (C{radians}).
188
- @kwarg error_kwds: Error to raise (C{ValueError}).
203
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
204
+ ValueErrors or optionally, additional
205
+ ValueError keyword argments.
189
206
 
190
207
  @return: C{cot(B{rad})}.
191
208
 
192
- @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{rad})}.
209
+ @raise Error: If L{pygeodesy.isnear0}C{(sin(B{rad})}.
193
210
  '''
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
211
+ try:
212
+ return _cotu(*sincos2(rad), **raiser_kwds)
213
+ except ZeroDivisionError:
214
+ raise _valueError(cot, rad, **raiser_kwds)
200
215
 
201
216
 
202
- def cot_(*rads, **error_kwds):
203
- '''Return the C{cotangent} of angle(s) in C{radiansresection}.
217
+ def cot_(*rads, **raiser_kwds):
218
+ '''Return the C{cotangent} of angle(s) in C{radians}.
204
219
 
205
- @arg rads: One or more angles (C{radians}).
206
- @kwarg error_kwds: Error to raise (C{ValueError}).
220
+ @arg rads: One or more angles (each in C{radians}).
207
221
 
208
222
  @return: Yield the C{cot(B{rad})} for each angle.
209
223
 
210
- @raise ValueError: See L{pygeodesy.cot}.
224
+ @see: Function L{pygeodesy.cot} for further details.
211
225
  '''
212
226
  try:
213
227
  for r in rads:
214
- yield cot(r)
215
- except ValueError:
216
- raise _valueError(cot_, r, **error_kwds)
228
+ yield _cotu(*sincos2(r), **raiser_kwds)
229
+ except ZeroDivisionError:
230
+ raise _valueError(cot_, r, **raiser_kwds)
217
231
 
218
232
 
219
- def cotd(deg, **error_kwds):
233
+ def cotd(deg, **raiser_kwds):
220
234
  '''Return the C{cotangent} of an angle in C{degrees}.
221
235
 
222
236
  @arg deg: Angle (C{degrees}).
223
- @kwarg error_kwds: Error to raise (C{ValueError}).
237
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
238
+ ValueErrors or optionally, additional
239
+ ValueError keyword argments.
224
240
 
225
241
  @return: C{cot(B{deg})}.
226
242
 
227
- @raise ValueError: L{pygeodesy.isnear0}C{(sin(B{deg})}.
243
+ @raise Error: If L{pygeodesy.isnear0}C{(sin(B{deg})}.
228
244
  '''
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
245
+ try:
246
+ return _cotu(*sincos2d(deg), **raiser_kwds)
247
+ except ZeroDivisionError:
248
+ raise _valueError(cotd, deg, **raiser_kwds)
237
249
 
238
250
 
239
- def cotd_(*degs, **error_kwds):
251
+ def cotd_(*degs, **raiser_kwds):
240
252
  '''Return the C{cotangent} of angle(s) in C{degrees}.
241
253
 
242
- @arg degs: One or more angles (C{degrees}).
243
- @kwarg error_kwds: Error to raise (C{ValueError}).
254
+ @arg degs: One or more angles (each in C{degrees}).
244
255
 
245
256
  @return: Yield the C{cot(B{deg})} for each angle.
246
257
 
247
- @raise ValueError: See L{pygeodesy.cotd}.
258
+ @see: Function L{pygeodesy.cotd} for further details.
248
259
  '''
249
260
  try:
250
261
  for d in degs:
251
- yield cotd(d)
252
- except ValueError:
253
- raise _valueError(cotd_, d, **error_kwds)
262
+ yield _cotu(*sincos2d(d), **raiser_kwds)
263
+ except ZeroDivisionError:
264
+ raise _valueError(cotd_, d, **raiser_kwds)
265
+
266
+
267
+ def _cotu(s, c, **raiser_kwds):
268
+ '''(INTERNAL) Helper for functions C{cot}, C{cotd}, C{cot_} and C{cotd_}.
269
+ '''
270
+ return _tanu(c, s, **raiser_kwds)
254
271
 
255
272
 
256
273
  def degrees90(rad):
@@ -290,30 +307,27 @@ def degrees2grades(deg):
290
307
 
291
308
  @return: Angle (C{grades}).
292
309
  '''
293
- return Degrees(deg) * _400_0 / _360_0
310
+ return Float(grades=Degrees(deg) * _G_DEG)
294
311
 
295
312
 
296
313
  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.
314
+ '''Convert an angle to a distance along the equator or along a parallel
315
+ at (geodetic) latitude.
299
316
 
300
317
  @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}).
318
+ @kwarg radius: Mean earth radius (C{meter}), an ellipsoid or datum
319
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
304
320
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
305
321
 
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}}.
322
+ @return: Distance (C{meter}, same units as B{C{radius}} or polar and
323
+ equatorial radii) or C{0.0} for near-polar B{C{lat}}.
309
324
 
310
325
  @raise RangeError: Latitude B{C{lat}} outside valid range and
311
326
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
312
327
 
313
328
  @raise TypeError: Invalid B{C{radius}}.
314
329
 
315
- @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or
316
- B{C{lat}}.
330
+ @raise ValueError: Invalid B{C{deg}}, B{C{radius}} or B{C{lat}}.
317
331
 
318
332
  @see: Function L{radians2m} and L{m2degrees}.
319
333
  '''
@@ -349,9 +363,9 @@ def ft2m(feet, usurvey=False, pied=False, fuss=False):
349
363
 
350
364
  @raise ValueError: Invalid B{C{feet}}.
351
365
  '''
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))))
366
+ return Meter(Feet(feet) * (_M_FOOT_US if usurvey else
367
+ (_M_FOOT_FR if pied else
368
+ (_M_FOOT_GE if fuss else _M_FOOT))))
355
369
 
356
370
 
357
371
  def furlong2m(furlongs):
@@ -373,7 +387,7 @@ def grades(rad):
373
387
 
374
388
  @return: Angle (C{grades}).
375
389
  '''
376
- return Float(grades=Float(rad=rad) * _400_0 / PI2)
390
+ return Float(grades=Radians(rad) * _G_RAD)
377
391
 
378
392
 
379
393
  def grades400(rad):
@@ -383,7 +397,7 @@ def grades400(rad):
383
397
 
384
398
  @return: Angle, wrapped (C{grades}).
385
399
  '''
386
- return Float(grades400=(grades(rad) % _400_0) or _0_0) # _umod_400
400
+ return Float(grades400=wrapPI2(rad) * _G_RAD)
387
401
 
388
402
 
389
403
  def grades2degrees(gon):
@@ -393,7 +407,7 @@ def grades2degrees(gon):
393
407
 
394
408
  @return: Angle (C{degrees}).
395
409
  '''
396
- return Degrees(Float(gon=gon) * _360_0 / _400_0)
410
+ return Degrees(Float(gon=gon) / _G_DEG)
397
411
 
398
412
 
399
413
  def grades2radians(gon):
@@ -403,7 +417,7 @@ def grades2radians(gon):
403
417
 
404
418
  @return: Angle (C{radians}).
405
419
  '''
406
- return Radians(Float(gon=gon) * PI2 / _400_0)
420
+ return Radians(Float(gon=gon) / _G_RAD)
407
421
 
408
422
 
409
423
  def km2m(km):
@@ -437,13 +451,12 @@ def m2chain(meter):
437
451
 
438
452
 
439
453
  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.
454
+ '''Convert a distance to an angle along the equator or along a parallel
455
+ at (geodetic) latitude.
442
456
 
443
457
  @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}).
458
+ @kwarg radius: Mean earth radius (C{meter}), an ellipsoid or datum
459
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
447
460
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
448
461
 
449
462
  @return: Angle (C{degrees}) or C{INF} for near-polar B{C{lat}}.
@@ -453,8 +466,7 @@ def m2degrees(distance, radius=R_M, lat=0):
453
466
 
454
467
  @raise TypeError: Invalid B{C{radius}}.
455
468
 
456
- @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
457
- or B{C{lat}}.
469
+ @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.
458
470
 
459
471
  @see: Function L{m2radians} and L{degrees2m}.
460
472
  '''
@@ -492,9 +504,9 @@ def m2ft(meter, usurvey=False, pied=False, fuss=False):
492
504
  '''
493
505
  # * 3.2808333333333333, US Survey 3937 / 1200
494
506
  # * 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))))
507
+ return Float(feet=Meter(meter) / (_M_FOOT_US if usurvey else
508
+ (_M_FOOT_FR if pied else
509
+ (_M_FOOT_GE if fuss else _M_FOOT))))
498
510
 
499
511
 
500
512
  def m2furlong(meter):
@@ -534,13 +546,12 @@ def m2NM(meter):
534
546
 
535
547
 
536
548
  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.
549
+ '''Convert a distance to an angle along the equator or along a parallel
550
+ at (geodetic) latitude.
539
551
 
540
552
  @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}).
553
+ @kwarg radius: Mean earth radius (C{meter}, an ellipsoid or datum
554
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
544
555
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
545
556
 
546
557
  @return: Angle (C{radians}) or C{INF} for near-polar B{C{lat}}.
@@ -550,8 +561,7 @@ def m2radians(distance, radius=R_M, lat=0):
550
561
 
551
562
  @raise TypeError: Invalid B{C{radius}}.
552
563
 
553
- @raise ValueError: Invalid B{C{distance}}, B{C{radius}}
554
- or B{C{lat}}.
564
+ @raise ValueError: Invalid B{C{distance}}, B{C{radius}} or B{C{lat}}.
555
565
 
556
566
  @see: Function L{m2degrees} and L{radians2m}.
557
567
  '''
@@ -610,26 +620,23 @@ def NM2m(nm):
610
620
 
611
621
 
612
622
  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.
623
+ '''Convert an angle to a distance along the equator or along a parallel
624
+ at (geodetic) latitude.
615
625
 
616
626
  @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}).
627
+ @kwarg radius: Mean earth radius (C{meter}) or an ellipsoid or datum
628
+ (L{Ellipsoid}, L{Ellipsoid2}, L{Datum} or L{a_f2Tuple}).
620
629
  @kwarg lat: Parallel latitude (C{degrees90}, C{str}).
621
630
 
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}}.
631
+ @return: Distance (C{meter}, same units as B{C{radius}} or polar and
632
+ equatorial radii) or C{0.0} for near-polar B{C{lat}}.
625
633
 
626
634
  @raise RangeError: Latitude B{C{lat}} outside valid range and
627
635
  L{rangerrors<pygeodesy.rangerrors>} is C{True}.
628
636
 
629
637
  @raise TypeError: Invalid B{C{radius}}.
630
638
 
631
- @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or
632
- B{C{lat}}.
639
+ @raise ValueError: Invalid B{C{rad}}, B{C{radius}} or B{C{lat}}.
633
640
 
634
641
  @see: Function L{degrees2m} and L{m2radians}.
635
642
  '''
@@ -832,41 +839,75 @@ def tan_2(rad, **semi): # edge=1
832
839
  _SPACE_(_MODS.streprs.Fmt.SQUARE(**semi), _edge_)
833
840
  raise _ValueError(n, rad, txt=_semi_circular_)
834
841
 
835
- return tan(rad * _0_5) if _isfinite(rad) else NAN
842
+ return _tan(rad * _0_5) if _isfinite(rad) else NAN
843
+
844
+
845
+ def tan(rad, **raiser_kwds):
846
+ '''Return the C{tangent} of an angle in C{radians}.
847
+
848
+ @arg rad: Angle (C{radians}).
849
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
850
+ ValueErrors or optionally, additional
851
+ ValueError keyword argments.
852
+
853
+ @return: C{tan(B{rad})}.
854
+
855
+ @raise Error: If L{pygeodesy.isnear0}C{(cos(B{rad})}.
856
+ '''
857
+ try:
858
+ return _tanu(*sincos2(rad), **raiser_kwds)
859
+ except ZeroDivisionError:
860
+ raise _valueError(tan, rad, **raiser_kwds)
861
+
862
+
863
+ def tan_(*rads, **raiser_kwds):
864
+ '''Return the C{tangent} of angle(s) in C{radians}.
865
+
866
+ @arg rads: One or more angles (each in C{radians}).
867
+
868
+ @return: Yield the C{tan(B{rad})} for each angle.
869
+
870
+ @see: Function L{pygeodesy.tan} for futher details.
871
+ '''
872
+ try:
873
+ for r in rads:
874
+ yield _tanu(*sincos2(r), **raiser_kwds)
875
+ except ZeroDivisionError:
876
+ raise _valueError(tan_, r, **raiser_kwds)
836
877
 
837
878
 
838
- def tand(deg, **error_kwds):
879
+ def tand(deg, **raiser_kwds):
839
880
  '''Return the C{tangent} of an angle in C{degrees}.
840
881
 
841
882
  @arg deg: Angle (C{degrees}).
842
- @kwarg error_kwds: Error to raise (C{ValueError}).
883
+ @kwarg raiser_kwds: Use C{B{raiser}=False} to avoid
884
+ ValueErrors or optionally, additional
885
+ ValueError keyword argments.
843
886
 
844
887
  @return: C{tan(B{deg})}.
845
888
 
846
- @raise ValueError: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
889
+ @raise Error: If L{pygeodesy.isnear0}C{(cos(B{deg})}.
847
890
  '''
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
891
+ try:
892
+ return _tanu(*sincos2d(deg), **raiser_kwds)
893
+ except ZeroDivisionError:
894
+ raise _valueError(tand, deg, **raiser_kwds)
856
895
 
857
896
 
858
- def tand_(*degs, **error_kwds):
897
+ def tand_(*degs, **raiser_kwds):
859
898
  '''Return the C{tangent} of angle(s) in C{degrees}.
860
899
 
861
- @arg degs: One or more angles (C{degrees}).
862
- @kwarg error_kwds: Error to raise (C{ValueError}).
900
+ @arg degs: One or more angles (each in C{degrees}).
863
901
 
864
902
  @return: Yield the C{tan(B{deg})} for each angle.
865
903
 
866
- @raise ValueError: See L{pygeodesy.tand}.
904
+ @see: Function L{pygeodesy.tand} for futher details.
867
905
  '''
868
- for d in degs:
869
- yield tand(d, **error_kwds)
906
+ try:
907
+ for d in degs:
908
+ yield _tanu(*sincos2d(d), **raiser_kwds)
909
+ except ZeroDivisionError:
910
+ raise _valueError(tand_, d, **raiser_kwds)
870
911
 
871
912
 
872
913
  def tanPI_2_2(rad):
@@ -876,8 +917,20 @@ def tanPI_2_2(rad):
876
917
 
877
918
  @return: M{tan((rad + PI/2) / 2)} (C{float}).
878
919
  '''
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))
920
+ return _tan((rad + PI_2) * _0_5) if _isfinite(rad) else (
921
+ NAN if isnan(rad) else _copysign(_90_0, rad))
922
+
923
+
924
+ def _tanu(s, c, raiser=True, **unused):
925
+ '''(INTERNAL) Helper for functions C{_cotu}, C{tan}, C{tan_}, C{tand} and C{tand_}.
926
+ '''
927
+ if s:
928
+ if raiser and isnear0(c):
929
+ raise ZeroDivisionError()
930
+ s = _over(s, c)
931
+ elif c < 0:
932
+ s = -s # negate-0
933
+ return s
881
934
 
882
935
 
883
936
  def toise2m(toises):
@@ -980,11 +1033,11 @@ def unrollPI(rad1, rad2, wrap=True):
980
1033
  return r, rad2
981
1034
 
982
1035
 
983
- def _valueError(where, x, **kwds):
984
- '''(INTERNAL) Return a C{_ValueError}.
1036
+ def _valueError(where, x, raiser=True, **kwds):
1037
+ '''(INTERNAL) Return a C{_ValueError} or C{None}.
985
1038
  '''
986
1039
  t = _MODS.streprs.Fmt.PAREN(where.__name__, x)
987
- return _ValueError(t, **kwds)
1040
+ return _ValueError(t, **kwds) if raiser else None
988
1041
 
989
1042
 
990
1043
  class _Wrap(object):
@@ -1100,8 +1153,7 @@ def wrap90(deg):
1100
1153
 
1101
1154
  @return: Degrees, wrapped (C{degrees90}).
1102
1155
  '''
1103
- w = wrap180(deg)
1104
- return (w - _180_0) if w > 90 else ((w + _180_0) if w < -90 else w)
1156
+ return _wrapu(wrap180(deg), _180_0, _90_0)
1105
1157
 
1106
1158
 
1107
1159
  def wrap180(deg):
@@ -1113,9 +1165,9 @@ def wrap180(deg):
1113
1165
  '''
1114
1166
  d = float(deg)
1115
1167
  w = _umod_360(d)
1116
- if w > 180:
1168
+ if w > _180_0:
1117
1169
  w -= _360_0
1118
- elif d < 0 and w == 180:
1170
+ elif d < 0 and w == _180_0:
1119
1171
  w = -w
1120
1172
  return w
1121
1173
 
@@ -1163,8 +1215,7 @@ def wrapPI_2(rad):
1163
1215
 
1164
1216
  @return: Radians, wrapped (C{radiansPI_2}).
1165
1217
  '''
1166
- w = wrapPI(rad)
1167
- return (w - PI) if w > PI_2 else ((w + PI) if w < (-PI_2) else w)
1218
+ return _wrapu(wrapPI(rad), PI, PI_2)
1168
1219
 
1169
1220
 
1170
1221
  # def wraplatlon(lat, lon):
@@ -1199,6 +1250,12 @@ def wrap_normal(*normal):
1199
1250
  # return wrapPI_2(phi), wrapPI(lam)
1200
1251
 
1201
1252
 
1253
+ def _wrapu(w, H, Q):
1254
+ '''(INTERNAL) Helper for functions C{wrap180} and C{wrapPI}.
1255
+ '''
1256
+ return (w - H) if w > Q else ((w + H) if w < (-Q) else w)
1257
+
1258
+
1202
1259
  def yard2m(yards):
1203
1260
  '''Convert I{UK} yards to meter.
1204
1261
 
@@ -1212,7 +1269,7 @@ def yard2m(yards):
1212
1269
 
1213
1270
  # **) MIT License
1214
1271
  #
1215
- # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
1272
+ # Copyright (C) 2016-2025 -- mrJean1 at Gmail -- All Rights Reserved.
1216
1273
  #
1217
1274
  # Permission is hereby granted, free of charge, to any person obtaining a
1218
1275
  # copy of this software and associated documentation files (the "Software"),