pygeodesy 24.3.24__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 (115) hide show
  1. PyGeodesy-24.3.24.dist-info/METADATA +272 -0
  2. PyGeodesy-24.3.24.dist-info/RECORD +115 -0
  3. PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
  4. PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
  5. pygeodesy/LICENSE +21 -0
  6. pygeodesy/__init__.py +615 -0
  7. pygeodesy/__main__.py +103 -0
  8. pygeodesy/albers.py +867 -0
  9. pygeodesy/auxilats/_CX_4.py +218 -0
  10. pygeodesy/auxilats/_CX_6.py +314 -0
  11. pygeodesy/auxilats/_CX_8.py +475 -0
  12. pygeodesy/auxilats/__init__.py +54 -0
  13. pygeodesy/auxilats/__main__.py +86 -0
  14. pygeodesy/auxilats/auxAngle.py +548 -0
  15. pygeodesy/auxilats/auxDLat.py +302 -0
  16. pygeodesy/auxilats/auxDST.py +296 -0
  17. pygeodesy/auxilats/auxLat.py +848 -0
  18. pygeodesy/auxilats/auxily.py +272 -0
  19. pygeodesy/azimuthal.py +1150 -0
  20. pygeodesy/basics.py +892 -0
  21. pygeodesy/booleans.py +2031 -0
  22. pygeodesy/cartesianBase.py +1062 -0
  23. pygeodesy/clipy.py +704 -0
  24. pygeodesy/constants.py +516 -0
  25. pygeodesy/css.py +660 -0
  26. pygeodesy/datums.py +752 -0
  27. pygeodesy/deprecated/__init__.py +61 -0
  28. pygeodesy/deprecated/bases.py +40 -0
  29. pygeodesy/deprecated/classes.py +262 -0
  30. pygeodesy/deprecated/consterns.py +54 -0
  31. pygeodesy/deprecated/datum.py +40 -0
  32. pygeodesy/deprecated/functions.py +375 -0
  33. pygeodesy/deprecated/nvector.py +48 -0
  34. pygeodesy/deprecated/rhumbBase.py +32 -0
  35. pygeodesy/deprecated/rhumbaux.py +33 -0
  36. pygeodesy/deprecated/rhumbsolve.py +33 -0
  37. pygeodesy/deprecated/rhumbx.py +33 -0
  38. pygeodesy/dms.py +986 -0
  39. pygeodesy/ecef.py +1348 -0
  40. pygeodesy/elevations.py +279 -0
  41. pygeodesy/ellipsoidalBase.py +1224 -0
  42. pygeodesy/ellipsoidalBaseDI.py +913 -0
  43. pygeodesy/ellipsoidalExact.py +343 -0
  44. pygeodesy/ellipsoidalGeodSolve.py +343 -0
  45. pygeodesy/ellipsoidalKarney.py +403 -0
  46. pygeodesy/ellipsoidalNvector.py +685 -0
  47. pygeodesy/ellipsoidalVincenty.py +590 -0
  48. pygeodesy/ellipsoids.py +2476 -0
  49. pygeodesy/elliptic.py +1198 -0
  50. pygeodesy/epsg.py +243 -0
  51. pygeodesy/errors.py +804 -0
  52. pygeodesy/etm.py +1190 -0
  53. pygeodesy/fmath.py +1013 -0
  54. pygeodesy/formy.py +1818 -0
  55. pygeodesy/frechet.py +865 -0
  56. pygeodesy/fstats.py +760 -0
  57. pygeodesy/fsums.py +1898 -0
  58. pygeodesy/gars.py +358 -0
  59. pygeodesy/geodesicw.py +581 -0
  60. pygeodesy/geodesicx/_C4_24.py +1699 -0
  61. pygeodesy/geodesicx/_C4_27.py +2395 -0
  62. pygeodesy/geodesicx/_C4_30.py +3301 -0
  63. pygeodesy/geodesicx/__init__.py +48 -0
  64. pygeodesy/geodesicx/__main__.py +91 -0
  65. pygeodesy/geodesicx/gx.py +1382 -0
  66. pygeodesy/geodesicx/gxarea.py +535 -0
  67. pygeodesy/geodesicx/gxbases.py +154 -0
  68. pygeodesy/geodesicx/gxline.py +669 -0
  69. pygeodesy/geodsolve.py +426 -0
  70. pygeodesy/geohash.py +914 -0
  71. pygeodesy/geoids.py +1884 -0
  72. pygeodesy/hausdorff.py +892 -0
  73. pygeodesy/heights.py +1155 -0
  74. pygeodesy/interns.py +687 -0
  75. pygeodesy/iters.py +545 -0
  76. pygeodesy/karney.py +919 -0
  77. pygeodesy/ktm.py +633 -0
  78. pygeodesy/latlonBase.py +1766 -0
  79. pygeodesy/lazily.py +960 -0
  80. pygeodesy/lcc.py +684 -0
  81. pygeodesy/ltp.py +1107 -0
  82. pygeodesy/ltpTuples.py +1563 -0
  83. pygeodesy/mgrs.py +721 -0
  84. pygeodesy/named.py +1324 -0
  85. pygeodesy/namedTuples.py +683 -0
  86. pygeodesy/nvectorBase.py +695 -0
  87. pygeodesy/osgr.py +781 -0
  88. pygeodesy/points.py +1686 -0
  89. pygeodesy/props.py +628 -0
  90. pygeodesy/resections.py +1048 -0
  91. pygeodesy/rhumb/__init__.py +46 -0
  92. pygeodesy/rhumb/aux_.py +397 -0
  93. pygeodesy/rhumb/bases.py +1148 -0
  94. pygeodesy/rhumb/ekx.py +563 -0
  95. pygeodesy/rhumb/solve.py +572 -0
  96. pygeodesy/simplify.py +647 -0
  97. pygeodesy/solveBase.py +472 -0
  98. pygeodesy/sphericalBase.py +724 -0
  99. pygeodesy/sphericalNvector.py +1264 -0
  100. pygeodesy/sphericalTrigonometry.py +1447 -0
  101. pygeodesy/streprs.py +627 -0
  102. pygeodesy/trf.py +2079 -0
  103. pygeodesy/triaxials.py +1484 -0
  104. pygeodesy/units.py +969 -0
  105. pygeodesy/unitsBase.py +349 -0
  106. pygeodesy/ups.py +538 -0
  107. pygeodesy/utily.py +1231 -0
  108. pygeodesy/utm.py +762 -0
  109. pygeodesy/utmups.py +318 -0
  110. pygeodesy/utmupsBase.py +517 -0
  111. pygeodesy/vector2d.py +785 -0
  112. pygeodesy/vector3d.py +968 -0
  113. pygeodesy/vector3dBase.py +1049 -0
  114. pygeodesy/webmercator.py +383 -0
  115. pygeodesy/wgrs.py +439 -0
pygeodesy/errors.py ADDED
@@ -0,0 +1,804 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Errors, exceptions, exception formatting and exception chaining.
5
+
6
+ Error, exception classes and functions to format PyGeodesy errors, including
7
+ the setting of I{exception chaining} for Python 3.9+.
8
+
9
+ By default, I{exception chaining} is turned I{off}. To enable I{exception
10
+ chaining}, use command line option C{python -X dev} I{OR} set env variable
11
+ C{PYTHONDEVMODE=1} or to any non-empty string I{OR} set env variable
12
+ C{PYGEODESY_EXCEPTION_CHAINING=std} or to any non-empty string.
13
+ '''
14
+ # from pygeodesy.basics import isint, isodd, itemsorted, _xinstanceof, _zip # _MODS
15
+ # from pygeodesy.ellipsoidalBase import CartesianEllipsoidalBase, LatLonEllipsoidalBase # _MODS
16
+ # from pygeodesy import errors # _MODS.getattr
17
+ from pygeodesy.interns import MISSING, NN, _a_, _an_, _and_, _clip_, _COLON_, \
18
+ _COLONSPACE_, _COMMASPACE_, _datum_, _ellipsoidal_, \
19
+ _incompatible_, _invalid_, _len_, _not_, _or_, _SPACE_, \
20
+ _specified_, _UNDER_, _vs_, _with_, _tailof
21
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _getenv, _PYTHON_X_DEV
22
+ # from pygeodesy.streprs import Fmt, unstr # _MODS
23
+ # from pygeodesy.vector3dBase import Vector3dBase # _MODS
24
+
25
+ from copy import copy as _copy
26
+
27
+ __all__ = _ALL_LAZY.errors # _ALL_DOCS('_InvalidError', '_IsnotError') _under
28
+ __version__ = '24.03.24'
29
+
30
+ _box_ = 'box'
31
+ _limiterrors = True # in .formy
32
+ _name_value_ = repr('name=value')
33
+ _rangerrors = True # in .dms
34
+ _region_ = 'region'
35
+ _vs__ = _SPACE_(NN, _vs_, NN)
36
+
37
+ try:
38
+ _exception_chaining = None # not available
39
+ _ = Exception().__cause__ # Python 3.9+ exception chaining
40
+
41
+ if _PYTHON_X_DEV or _getenv('PYGEODESY_EXCEPTION_CHAINING', NN): # == _std_
42
+ _exception_chaining = True # turned on, std
43
+ raise AttributeError # allow exception chaining
44
+
45
+ _exception_chaining = False # turned off
46
+
47
+ def _error_cause(inst, cause=None):
48
+ '''(INTERNAL) Set or avoid Python 3+ exception chaining.
49
+
50
+ Setting C{inst.__cause__ = None} is equivalent to syntax
51
+ C{raise Error(...) from None} to avoid exception chaining.
52
+
53
+ @arg inst: An error instance (I{caught} C{Exception}).
54
+ @kwarg cause: A previous error instance (I{caught} C{Exception})
55
+ or C{None} to avoid exception chaining.
56
+
57
+ @see: Alex Martelli, et.al., "Python in a Nutshell", 3rd Ed., page 163,
58
+ O'Reilly, 2017, U{PEP-3134<https://www.Python.org/dev/peps/pep-3134>},
59
+ U{here<https://StackOverflow.com/questions/17091520/how-can-i-more-
60
+ easily-suppress-previous-exceptions-when-i-raise-my-own-exception>}
61
+ and U{here<https://StackOverflow.com/questions/1350671/
62
+ inner-exception-with-traceback-in-python>}.
63
+ '''
64
+ inst.__cause__ = cause # None, no exception chaining
65
+ return inst
66
+
67
+ except AttributeError: # Python 2+
68
+
69
+ def _error_cause(inst, **unused): # PYCHOK expected
70
+ return inst # no-op
71
+
72
+
73
+ class _AssertionError(AssertionError):
74
+ '''(INTERNAL) Format an C{AssertionError} with/-out exception chaining.
75
+ '''
76
+ def __init__(self, *args, **kwds):
77
+ _error_init(AssertionError, self, args, **kwds)
78
+
79
+
80
+ class _AttributeError(AttributeError):
81
+ '''(INTERNAL) Format an C{AttributeError} with/-out exception chaining.
82
+ '''
83
+ def __init__(self, *args, **kwds):
84
+ _error_init(AttributeError, self, args, **kwds)
85
+
86
+
87
+ class _ImportError(ImportError):
88
+ '''(INTERNAL) Format an C{ImportError} with/-out exception chaining.
89
+ '''
90
+ def __init__(self, *args, **kwds):
91
+ _error_init(ImportError, self, args, **kwds)
92
+
93
+
94
+ class _IndexError(IndexError):
95
+ '''(INTERNAL) Format an C{IndexError} with/-out exception chaining.
96
+ '''
97
+ def __init__(self, *args, **kwds):
98
+ _error_init(IndexError, self, args, **kwds)
99
+
100
+
101
+ class _KeyError(KeyError):
102
+ '''(INTERNAL) Format a C{KeyError} with/-out exception chaining.
103
+ '''
104
+ def __init__(self, *args, **kwds): # txt=_invalid_
105
+ _error_init(KeyError, self, args, **kwds)
106
+
107
+
108
+ class _NameError(NameError):
109
+ '''(INTERNAL) Format a C{NameError} with/-out exception chaining.
110
+ '''
111
+ def __init__(self, *args, **kwds):
112
+ _error_init(NameError, self, args, **kwds)
113
+
114
+
115
+ class _NotImplementedError(NotImplementedError):
116
+ '''(INTERNAL) Format a C{NotImplementedError} with/-out exception chaining.
117
+ '''
118
+ def __init__(self, *args, **kwds):
119
+ _error_init(NotImplementedError, self, args, **kwds)
120
+
121
+
122
+ class _OverflowError(OverflowError):
123
+ '''(INTERNAL) Format an C{OverflowError} with/-out exception chaining.
124
+ '''
125
+ def __init__(self, *args, **kwds): # txt=_invalid_
126
+ _error_init(OverflowError, self, args, **kwds)
127
+
128
+
129
+ class _TypeError(TypeError):
130
+ '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
131
+ '''
132
+ def __init__(self, *args, **kwds):
133
+ _error_init(TypeError, self, args, fmt_name_value='type(%s) (%r)', **kwds)
134
+
135
+
136
+ def _TypesError(name, value, *Types, **kwds):
137
+ '''(INTERNAL) Format a C{TypeError} with/-out exception chaining.
138
+ '''
139
+ # no longer C{class _TypesError} to avoid missing value
140
+ # argument errors in _XError line ...E = Error(str(e))
141
+ t = _not_(_an(_or(*(t.__name__ for t in Types))))
142
+ return _TypeError(name, value, txt=t, **kwds)
143
+
144
+
145
+ class _ValueError(ValueError):
146
+ '''(INTERNAL) Format a C{ValueError} with/-out exception chaining.
147
+ '''
148
+ def __init__(self, *args, **kwds): # ..., cause=None, txt=_invalid_, ...
149
+ _error_init(ValueError, self, args, **kwds)
150
+
151
+
152
+ class _ZeroDivisionError(ZeroDivisionError):
153
+ '''(INTERNAL) Format a C{ZeroDivisionError} with/-out exception chaining.
154
+ '''
155
+ def __init__(self, *args, **kwds):
156
+ _error_init(ZeroDivisionError, self, args, **kwds)
157
+
158
+
159
+ class AuxError(_ValueError):
160
+ '''Error raised for a L{rhumb.aux_}, C{Aux}, C{AuxDLat} or C{AuxLat} issue.
161
+ '''
162
+ pass
163
+
164
+
165
+ class ClipError(_ValueError):
166
+ '''Clip box or clip region issue.
167
+ '''
168
+ def __init__(self, *name_n_corners, **txt_cause):
169
+ '''New L{ClipError}.
170
+
171
+ @arg name_n_corners: Either just a name (C{str}) or
172
+ name, number, corners (C{str},
173
+ C{int}, C{tuple}).
174
+ @kwarg txt_cause: Optional C{B{txt}=str} explanation
175
+ of the error and C{B{cause}=None}
176
+ for exception chaining.
177
+ '''
178
+ if len(name_n_corners) == 3:
179
+ t, n, v = name_n_corners
180
+ n = _SPACE_(t, _clip_, (_box_ if n == 2 else _region_))
181
+ name_n_corners = n, v
182
+ _ValueError.__init__(self, *name_n_corners, **txt_cause)
183
+
184
+
185
+ class CrossError(_ValueError):
186
+ '''Error raised for zero or near-zero vectorial cross products,
187
+ occurring for coincident or colinear points, lines or bearings.
188
+ '''
189
+ pass
190
+
191
+
192
+ class GeodesicError(_ValueError):
193
+ '''Error raised for lack of convergence or other issues in L{pygeodesy.geodesicx},
194
+ L{pygeodesy.geodesicw} or L{pygeodesy.karney}.
195
+ '''
196
+ pass
197
+
198
+
199
+ class IntersectionError(_ValueError): # in .ellipsoidalBaseDI, .formy, ...
200
+ '''Error raised for line or circle intersection issues.
201
+ '''
202
+ def __init__(self, *args, **kwds):
203
+ '''New L{IntersectionError}.
204
+ '''
205
+ if args:
206
+ _ValueError.__init__(self, _SPACE_(*args), **kwds)
207
+ else:
208
+ _ValueError.__init__(self, **kwds)
209
+
210
+
211
+ class LenError(_ValueError): # in .ecef, .fmath, .heights, .iters, .named
212
+ '''Error raised for mis-matching C{len} values.
213
+ '''
214
+ def __init__(self, where, **lens_txt): # txt=None
215
+ '''New L{LenError}.
216
+
217
+ @arg where: Object with C{.__name__} attribute
218
+ (C{class}, C{method}, or C{function}).
219
+ @kwarg lens_txt: Two or more C{name=len(name)} pairs
220
+ (C{keyword arguments}).
221
+ '''
222
+ def _ns_vs_txt_x(cause=None, txt=_invalid_, **kwds):
223
+ ns, vs = zip(*_MODS.basics.itemsorted(kwds)) # unzip
224
+ return ns, vs, txt, cause
225
+
226
+ ns, vs, txt, x = _ns_vs_txt_x(**lens_txt)
227
+ ns = _COMMASPACE_.join(ns)
228
+ t = _MODS.streprs.Fmt.PAREN(where.__name__, ns)
229
+ vs = _vs__.join(map(str, vs))
230
+ t = _SPACE_(t, _len_, vs)
231
+ _ValueError.__init__(self, t, txt=txt, cause=x)
232
+
233
+
234
+ class LimitError(_ValueError):
235
+ '''Error raised for lat- or longitudinal values or deltas exceeding
236
+ the given B{C{limit}} in functions L{pygeodesy.equirectangular},
237
+ L{pygeodesy.equirectangular_}, C{nearestOn*} and C{simplify*}
238
+ or methods with C{limit} or C{options} keyword arguments.
239
+
240
+ @see: Subclass L{UnitError}.
241
+ '''
242
+ pass
243
+
244
+
245
+ class MGRSError(_ValueError):
246
+ '''Military Grid Reference System (MGRS) parse or other L{Mgrs} issue.
247
+ '''
248
+ pass
249
+
250
+
251
+ class NumPyError(_ValueError):
252
+ '''Error raised for C{NumPy} issues.
253
+ '''
254
+ pass
255
+
256
+
257
+ class ParseError(_ValueError): # in .dms, .elevations, .utmupsBase
258
+ '''Error parsing degrees, radians or several other formats.
259
+ '''
260
+ pass
261
+
262
+
263
+ class PointsError(_ValueError): # in .clipy, .frechet, ...
264
+ '''Error for an insufficient number of points.
265
+ '''
266
+ pass
267
+
268
+
269
+ class RangeError(_ValueError):
270
+ '''Error raised for lat- or longitude values outside the B{C{clip}},
271
+ B{C{clipLat}}, B{C{clipLon}} in functions L{pygeodesy.parse3llh},
272
+ L{pygeodesy.parseDMS}, L{pygeodesy.parseDMS2} and L{pygeodesy.parseRad}
273
+ or the given B{C{limit}} in functions L{pygeodesy.clipDegrees} and
274
+ L{pygeodesy.clipRadians}.
275
+
276
+ @see: Function L{pygeodesy.rangerrors}.
277
+ '''
278
+ pass
279
+
280
+
281
+ class RhumbError(_ValueError):
282
+ '''Error raised for a L{pygeodesy.rhumb.aux_}, L{pygeodesy.rhumb.ekx}
283
+ or L{pygeodesy.rhumb.solve} issue.
284
+ '''
285
+ pass
286
+
287
+
288
+ class TriangleError(_ValueError): # in .resections, .vector2d
289
+ '''Error raised for triangle, inter- or resection issues.
290
+ '''
291
+ pass
292
+
293
+
294
+ class SciPyError(PointsError):
295
+ '''Error raised for C{SciPy} issues.
296
+ '''
297
+ pass
298
+
299
+
300
+ class SciPyWarning(PointsError):
301
+ '''Error thrown for C{SciPy} warnings.
302
+
303
+ To raise C{SciPy} warnings as L{SciPyWarning} exceptions, Python
304
+ C{warnings} must be filtered as U{warnings.filterwarnings('error')
305
+ <https://docs.Python.org/3/library/warnings.html#the-warnings-filter>}
306
+ I{prior to} C{import scipy} OR by setting env var U{PYTHONWARNINGS
307
+ <https://docs.Python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>}
308
+ OR by invoking C{python} with command line option U{-W<https://docs.
309
+ Python.org/3/using/cmdline.html#cmdoption-w>} set to C{-W error}.
310
+ '''
311
+ pass
312
+
313
+
314
+ class TRFError(_ValueError): # in .ellipsoidalBase, .trf, .units
315
+ '''Terrestrial Reference Frame (TRF), L{Epoch}, L{RefFrame}
316
+ or L{RefFrame} conversion issue.
317
+ '''
318
+ pass
319
+
320
+
321
+ class UnitError(LimitError): # in .named, .units
322
+ '''Default exception for L{units} issues for a value exceeding the
323
+ C{low} or C{high} limit.
324
+ '''
325
+ pass
326
+
327
+
328
+ class VectorError(_ValueError): # in .nvectorBase, .vector3d, .vector3dBase
329
+ '''L{Vector3d}, C{Cartesian*} or C{*Nvector} issues.
330
+ '''
331
+ pass
332
+
333
+
334
+ def _an(noun):
335
+ '''(INTERNAL) Prepend an article to a noun based
336
+ on the pronounciation of the first letter.
337
+ '''
338
+ a = _an_ if noun[:1].lower() in 'aeinoux' else _a_
339
+ return _SPACE_(a, noun)
340
+
341
+
342
+ def _and(*words):
343
+ '''(INTERNAL) Join C{words} with C{", "} and C{" and "}.
344
+ '''
345
+ return _and_or(_and_, *words)
346
+
347
+
348
+ def _and_or(last, *words):
349
+ '''(INTERNAL) Join C{words} with C{", "} and C{B{last}}.
350
+ '''
351
+ t, w = NN, list(words)
352
+ if w:
353
+ t = w.pop()
354
+ if w:
355
+ w = _COMMASPACE_.join(w)
356
+ t = _SPACE_(w, last, t)
357
+ return t
358
+
359
+
360
+ def crosserrors(raiser=None):
361
+ '''Report or ignore vectorial cross product errors.
362
+
363
+ @kwarg raiser: Use C{True} to throw or C{False} to ignore
364
+ L{CrossError} exceptions. Use C{None} to
365
+ leave the setting unchanged.
366
+
367
+ @return: Previous setting (C{bool}).
368
+
369
+ @see: Property C{Vector3d[Base].crosserrors}.
370
+ '''
371
+ V = _MODS.vector3dBase.Vector3dBase
372
+ t = V._crosserrors # XXX class attr!
373
+ if raiser in (True, False):
374
+ V._crosserrors = raiser
375
+ return t
376
+
377
+
378
+ def _error_init(Error, inst, args, fmt_name_value='%s (%r)', txt=NN,
379
+ cause=None, **kwds): # by .lazily
380
+ '''(INTERNAL) Format an error text and initialize an C{Error} instance.
381
+
382
+ @arg Error: The error super-class (C{Exception}).
383
+ @arg inst: Sub-class instance to be __init__-ed (C{_Exception}).
384
+ @arg args: Either just a value or several name, value, ...
385
+ positional arguments (C{str}, any C{type}), in
386
+ particular for name conflicts with keyword
387
+ arguments of C{error_init} or which can't be
388
+ given as C{name=value} keyword arguments.
389
+ @kwarg fmt_name_value: Format for (name, value) (C{str}).
390
+ @kwarg txt: Optional explanation of the error (C{str}).
391
+ @kwarg cause: Optional, caught error (L{Exception}), for
392
+ exception chaining (supported in Python 3+).
393
+ @kwarg kwds: Additional C{B{name}=value} pairs, if any.
394
+ '''
395
+ def _fmtuple(pairs):
396
+ return tuple(fmt_name_value % t for t in pairs)
397
+
398
+ t, n = (), len(args)
399
+ if n > 2:
400
+ s = _MODS.basics.isodd(n)
401
+ t = _fmtuple(zip(args[0::2], args[1::2]))
402
+ if s: # XXX _xzip(..., strict=s)
403
+ t += args[-1:]
404
+ elif n == 2:
405
+ t = (fmt_name_value % args),
406
+ elif n: # == 1
407
+ t = str(args[0]),
408
+
409
+ if kwds:
410
+ t += _fmtuple(_MODS.basics.itemsorted(kwds))
411
+ t = _or(*t) if t else _SPACE_(_name_value_, MISSING)
412
+
413
+ if txt is not None:
414
+ x = str(txt) or (str(cause) if cause else _invalid_)
415
+ C = _COMMASPACE_ if _COLON_ in t else _COLONSPACE_
416
+ t = C(t, x)
417
+ # else: # LenError, _xzip, .dms, .heights, .vector2d
418
+ # x = NN # XXX or t?
419
+ Error.__init__(inst, t)
420
+ # inst.__x_txt__ = x # hold explanation
421
+ _error_cause(inst, cause=cause if _exception_chaining else None)
422
+ _error_under(inst)
423
+
424
+
425
+ def _error_under(inst):
426
+ '''(INTERNAL) Remove leading underscore from instance' class name.
427
+ '''
428
+ n = inst.__class__.__name__
429
+ if n.startswith(_UNDER_):
430
+ inst.__class__.__name__ = n.lstrip(_UNDER_)
431
+ return inst
432
+
433
+
434
+ def exception_chaining(exc=None):
435
+ '''Get an error's I{cause} or the exception chaining setting.
436
+
437
+ @kwarg exc: An error instance (C{Exception}) or C{None}.
438
+
439
+ @return: If C{B{exc} is None}, return C{True} if exception
440
+ chaining is enabled for PyGeodesy errors, C{False}
441
+ if turned off and C{None} if not available. If
442
+ B{C{exc}} is not C{None}, return it's error I{cause}
443
+ or C{None}.
444
+
445
+ @note: To enable exception chaining for C{pygeodesy} errors,
446
+ set env var C{PYGEODESY_EXCEPTION_CHAINING} to any
447
+ non-empty value prior to C{import pygeodesy}.
448
+ '''
449
+ return _exception_chaining if exc is None else \
450
+ getattr(exc, '__cause__', None)
451
+
452
+
453
+ def _incompatible(this):
454
+ '''(INTERNAL) Format an C{"incompatible with ..."} text.
455
+ '''
456
+ return _SPACE_(_incompatible_, _with_, this)
457
+
458
+
459
+ def _InvalidError(Error=_ValueError, **txt_name_values_cause): # txt=_invalid_, name=value [, ...]
460
+ '''(INTERNAL) Create an C{Error} instance.
461
+
462
+ @kwarg Error: The error class or sub-class (C{Exception}).
463
+ @kwarg txt_name_values: One or more C{B{name}=value} pairs
464
+ and optionally, keyword argument C{B{txt}=str}
465
+ to override the default C{B{txt}='invalid'} and
466
+ C{B{cause}=None} for exception chaining.
467
+
468
+ @return: An B{C{Error}} instance.
469
+ '''
470
+ return _XError(Error, **txt_name_values_cause)
471
+
472
+
473
+ def isError(exc):
474
+ '''Check a (caught) exception.
475
+
476
+ @arg exc: The exception C({Exception}).
477
+
478
+ @return: C{True} if B{C{exc}} is a C{pygeodesy} error,
479
+ C{False} if B{C{exc}} is a standard Python error
480
+ of C{None} if neither.
481
+ '''
482
+ return True if isinstance(exc, _XErrors) else (
483
+ False if isinstance(exc, Exception) else None)
484
+
485
+
486
+ def _IsnotError(*nouns, **name_value_Error_cause): # name=value [, Error=TypeError, cause=None]
487
+ '''Create a C{TypeError} for an invalid C{name=value} type.
488
+
489
+ @arg nouns: One or more expected class or type names, usually nouns (C{str}).
490
+ @kwarg name_value_Error_cause: One C{B{name}=value} pair and optionally,
491
+ keyword argument C{B{Error}=TypeError} to override the default
492
+ and C{B{cause}=None} for exception chaining.
493
+
494
+ @return: A C{TypeError} or an B{C{Error}} instance.
495
+ '''
496
+ def _n_v_E_x(cause=None, Error=TypeError, **name_value):
497
+ return _xkwds_item2(name_value) + (Error, cause)
498
+
499
+ n, v, E, x = _n_v_E_x(**name_value_Error_cause)
500
+
501
+ n = _MODS.streprs.Fmt.PARENSPACED(n, repr(v))
502
+ t = _not_(_an(_or(*nouns)) if nouns else _specified_)
503
+ return _XError(E, n, txt=t, cause=x)
504
+
505
+
506
+ def limiterrors(raiser=None):
507
+ '''Get/set the throwing of L{LimitError}s.
508
+
509
+ @kwarg raiser: Choose C{True} to raise or C{False} to
510
+ ignore L{LimitError} exceptions. Use
511
+ C{None} to leave the setting unchanged.
512
+
513
+ @return: Previous setting (C{bool}).
514
+ '''
515
+ global _limiterrors
516
+ t = _limiterrors
517
+ if raiser in (True, False):
518
+ _limiterrors = raiser
519
+ return t
520
+
521
+
522
+ def _or(*words):
523
+ '''(INTERNAL) Join C{words} with C{", "} and C{" or "}.
524
+ '''
525
+ return _and_or(_or_, *words)
526
+
527
+
528
+ def _parseX(parser, *args, **name_values_Error): # name=value[, ..., Error=ParseError]
529
+ '''(INTERNAL) Invoke a parser and handle exceptions.
530
+
531
+ @arg parser: The parser (C{callable}).
532
+ @arg args: Any B{C{parser}} arguments (any C{type}s).
533
+ @kwarg name_values_Error: Any C{B{name}=value} pairs and
534
+ optionally, C{B{Error}=ParseError} keyword
535
+ argument to override the default.
536
+
537
+ @return: Parser result.
538
+
539
+ @raise ParseError: Or the specified C{B{Error}}.
540
+ '''
541
+ try:
542
+ return parser(*args)
543
+ except Exception as x:
544
+ E = type(x) if isError(x) else ParseError
545
+ E, kwds = _xkwds_pop2(name_values_Error, Error=E)
546
+ raise _XError(E, **_xkwds(kwds, cause=x))
547
+
548
+
549
+ def rangerrors(raiser=None):
550
+ '''Get/set the throwing of L{RangeError}s.
551
+
552
+ @kwarg raiser: Choose C{True} to raise or C{False} to ignore
553
+ L{RangeError} exceptions. Use C{None} to leave
554
+ the setting unchanged.
555
+
556
+ @return: Previous setting (C{bool}).
557
+ '''
558
+ global _rangerrors
559
+ t = _rangerrors
560
+ if raiser in (True, False):
561
+ _rangerrors = raiser
562
+ return t
563
+
564
+
565
+ def _SciPyIssue(exc, *extras): # PYCHOK no cover
566
+ if isinstance(exc, (RuntimeWarning, UserWarning)):
567
+ E = SciPyWarning
568
+ else:
569
+ E = SciPyError # PYCHOK not really
570
+ t = _SPACE_(str(exc).strip(), *extras)
571
+ return E(t, txt=None, cause=exc)
572
+
573
+
574
+ def _xAssertionError(where, *args, **kwds):
575
+ '''(INTERNAL) Embellish an C{AssertionError} with/-out exception chaining.
576
+ '''
577
+ x, kwds = _xkwds_pop2(kwds, cause=None)
578
+ w = _MODS.streprs.unstr(where, *args, **kwds)
579
+ return _AssertionError(w, txt=None, cause=x)
580
+
581
+
582
+ def _xattr(obj, **name_default): # see .strerprs._xattrs
583
+ '''(INTERNAL) Get an C{obj}'s attribute by C{name}.
584
+ '''
585
+ if len(name_default) == 1:
586
+ for n, d in name_default.items():
587
+ return getattr(obj, n, d)
588
+ raise _xAssertionError(_xattr, obj, **name_default)
589
+
590
+
591
+ def _xcallable(**names_callables):
592
+ '''(INTERNAL) Check one or more C{callable}s.
593
+ '''
594
+ for n, c in names_callables.items():
595
+ if not callable(c):
596
+ raise _TypeError(n, c, txt=_not_(callable.__name__))
597
+
598
+
599
+ def _xdatum(datum1, datum2, Error=None):
600
+ '''(INTERNAL) Check for datum, ellipsoid or rhumb mis-match.
601
+ '''
602
+ if Error:
603
+ e1, e2 = datum1.ellipsoid, datum2.ellipsoid
604
+ if e1 != e2:
605
+ raise Error(e2.named2, txt=_incompatible(e1.named2))
606
+ elif datum1 != datum2:
607
+ t = _SPACE_(_datum_, repr(datum1.name),
608
+ _not_, repr(datum2.name))
609
+ raise _AssertionError(t)
610
+
611
+
612
+ def _xellipsoidal(**name_value): # see _xellipsoidall elel
613
+ '''(INTERNAL) Check an I{ellipsoidal} item and return its value.
614
+ '''
615
+ if len(name_value) == 1:
616
+ for n, v in name_value.items():
617
+ try:
618
+ if v.isEllipsoidal:
619
+ return v
620
+ except AttributeError:
621
+ pass
622
+ raise _TypeError(n, v, txt=_not_(_ellipsoidal_))
623
+ raise _xAssertionError(_xellipsoidal, name_value)
624
+
625
+
626
+ def _xellipsoidall(point): # see _xellipsoidal
627
+ '''(INTERNAL) Check an ellipsoidal C{point}, return C{True}
628
+ if geodetic latlon or C{False} if cartesian.
629
+ '''
630
+ m = _MODS.ellipsoidalBase
631
+ ll = isinstance(point, m.LatLonEllipsoidalBase)
632
+ if not ll:
633
+ b = _MODS.basics
634
+ b._xinstanceof(m.CartesianEllipsoidalBase,
635
+ m.LatLonEllipsoidalBase, point=point)
636
+ return ll
637
+
638
+
639
+ def _XError(Error, *args, **kwds):
640
+ '''(INTERNAL) Format an C{Error} or C{_Error}.
641
+ '''
642
+ try: # C{_Error} style
643
+ return Error(*args, **kwds)
644
+ except TypeError: # no keyword arguments
645
+ pass
646
+ e = _ValueError(*args, **kwds)
647
+ E = Error(str(e))
648
+ if _exception_chaining:
649
+ _error_cause(E, cause=e.__cause__) # PYCHOK OK
650
+ return E
651
+
652
+
653
+ def _xError(exc, *args, **kwds):
654
+ '''(INTERNAL) Embellish a (caught) exception.
655
+
656
+ @arg exc: The exception (usually, C{_Error}).
657
+ @arg args: Embelishments (C{any}).
658
+ @kwarg kwds: Embelishments (C{any}).
659
+ '''
660
+ return _XError(type(exc), *args, **_xkwds(kwds, cause=exc))
661
+
662
+
663
+ def _xError2(exc): # in .constants, .fsums, .lazily, .vector2d
664
+ '''(INTERNAL) Map an exception to 2-tuple (C{_Error} class, error C{txt}).
665
+
666
+ @arg exc: The exception instance (usually, C{Exception}).
667
+ '''
668
+ m = __name__ # 'pygeodesy.errors'
669
+ X = type(exc)
670
+ n = NN(_UNDER_, _tailof(X.__name__))
671
+ E = _MODS.getattr(m, n, X) # == _X2Error.get(X, X)
672
+ if E is X and not isError(exc):
673
+ E = _NotImplementedError
674
+ t = repr(exc)
675
+ else:
676
+ t = str(exc)
677
+ return E, t
678
+
679
+
680
+ _XErrors = _TypeError, _ValueError
681
+ # map certain C{Exception} classes to the C{_Error}
682
+ # _X2Error = {AssertionError: _AssertionError,
683
+ # AttributeError: _AttributeError,
684
+ # ImportError: _ImportError,
685
+ # IndexError: _IndexError,
686
+ # KeyError: _KeyError,
687
+ # NameError: _NameError,
688
+ # NotImplementedError: _NotImplementedError,
689
+ # OverflowError: _OverflowError,
690
+ # TypeError: _TypeError,
691
+ # ValueError: _ValueError,
692
+ # ZeroDivisionError: _ZeroDivisionError}
693
+
694
+ try:
695
+ _ = {}.__or__ # {} | {} # Python 3.9+
696
+
697
+ def _xkwds(kwds, **dflts):
698
+ '''(INTERNAL) Update C{dflts} with specified C{kwds}.
699
+ '''
700
+ return (dflts | kwds) if kwds else dflts
701
+
702
+ except AttributeError:
703
+
704
+ def _xkwds(kwds, **dflts): # PYCHOK expected
705
+ '''(INTERNAL) Update C{dflts} with specified C{kwds}.
706
+ '''
707
+ d = dflts
708
+ if kwds:
709
+ d = _copy(d)
710
+ d.update(kwds)
711
+ return d
712
+
713
+
714
+ # def _xkwds_bool(inst, **kwds): # no longer in .frechet, .hausdorff, .heights
715
+ # '''(INTERNAL) Set applicable C{bool} properties/attributes.
716
+ # '''
717
+ # for n, v in kwds.items():
718
+ # b = getattr(inst, n, None)
719
+ # if b is None: # invalid bool attr
720
+ # t = _SPACE_(_EQUAL_(n, repr(v)), 'for', inst.__class__.__name__) # XXX .classname
721
+ # raise _AttributeError(t, txt=_not_('applicable'))
722
+ # if v in (False, True) and v != b:
723
+ # setattr(inst, NN(_UNDER_, n), v)
724
+
725
+
726
+ def _xkwds_get(kwds, **name_default):
727
+ '''(INTERNAL) Get a C{kwds} value by C{name} or the
728
+ C{default} if not present.
729
+ '''
730
+ if isinstance(kwds, dict) and len(name_default) == 1:
731
+ for n, d in name_default.items():
732
+ return kwds.get(n, d)
733
+ raise _xAssertionError(_xkwds_get, kwds, **name_default)
734
+
735
+
736
+ def _xkwds_get_(kwds, **names_defaults):
737
+ '''(INTERNAL) Yield each C{kwds} value or its C{default}
738
+ in I{case-insensitive, alphabetical} C{name} order.
739
+ '''
740
+ if not isinstance(kwds, dict):
741
+ raise _xAssertionError(_xkwds_get_, kwds)
742
+ for n, d in _MODS.basics.itemsorted(names_defaults):
743
+ yield kwds.get(n, d)
744
+
745
+
746
+ def _xkwds_item2(kwds):
747
+ '''(INTERNAL) Return the 2-tuple C{item}, keeping the
748
+ single-item C{kwds} I{unmodified}.
749
+ '''
750
+ if isinstance(kwds, dict) and len(kwds) == 1:
751
+ for item in kwds.items():
752
+ return item
753
+ raise _xAssertionError(_xkwds_item2, kwds)
754
+
755
+
756
+ def _xkwds_not(*args, **kwds):
757
+ '''(INTERNAL) Return C{kwds} with a value not in C{args}.
758
+ '''
759
+ return dict((n, v) for n, v in kwds.items() if v not in args)
760
+
761
+
762
+ def _xkwds_pop2(kwds, **name_default):
763
+ '''(INTERNAL) Pop a C{kwds} item by C{name} and return the value and
764
+ reduced C{kwds} copy, otherwise the C{default} and original C{kwds}.
765
+ '''
766
+ if isinstance(kwds, dict) and len(name_default) == 1:
767
+ for n, d in name_default.items():
768
+ if n in kwds:
769
+ kwds = _copy(kwds)
770
+ d = kwds.pop(n, d)
771
+ return d, kwds
772
+ raise _xAssertionError(_xkwds_pop2, kwds, **name_default)
773
+
774
+
775
+ def _Xorder(_Coeffs, Error, **Xorder): # in .auxLat, .ktm, .rhumb.bases, .rhumb.ekx
776
+ '''(INTERNAL) Validate C{RAorder} or C{TMorder}.
777
+ '''
778
+ X, m = _xkwds_item2(Xorder)
779
+ if m in _Coeffs and _MODS.basics.isint(m):
780
+ return m
781
+ t = sorted(map(str, _Coeffs.keys()))
782
+ raise Error(X, m, txt=_not_(_or(*t)))
783
+
784
+ # **) MIT License
785
+ #
786
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
787
+ #
788
+ # Permission is hereby granted, free of charge, to any person obtaining a
789
+ # copy of this software and associated documentation files (the "Software"),
790
+ # to deal in the Software without restriction, including without limitation
791
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
792
+ # and/or sell copies of the Software, and to permit persons to whom the
793
+ # Software is furnished to do so, subject to the following conditions:
794
+ #
795
+ # The above copyright notice and this permission notice shall be included
796
+ # in all copies or substantial portions of the Software.
797
+ #
798
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
799
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
800
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
801
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
802
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
803
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
804
+ # OTHER DEALINGS IN THE SOFTWARE.