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/basics.py ADDED
@@ -0,0 +1,892 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Some, basic definitions, functions and dependencies.
5
+
6
+ Use env variable C{PYGEODESY_XPACKAGES} to avoid import of dependencies
7
+ C{geographiclib}, C{numpy} and/or C{scipy}. Set C{PYGEODESY_XPACKAGES}
8
+ to a comma-separated list of package names to be excluded from import.
9
+ '''
10
+ # make sure int/int division yields float quotient
11
+ from __future__ import division
12
+ division = 1 / 2 # .albers, .azimuthal, .constants, etc., .utily
13
+ if not division:
14
+ raise ImportError('%s 1/2 == %s' % ('division', division))
15
+ del division
16
+
17
+ from pygeodesy.errors import _AttributeError, _ImportError, _NotImplementedError, \
18
+ _TypeError, _TypesError, _ValueError, _xAssertionError, \
19
+ _xkwds_get
20
+ from pygeodesy.interns import MISSING, NN, _1_, _by_, _COMMA_, _DOT_, _DEPRECATED_, \
21
+ _ELLIPSIS4_, _enquote, _EQUAL_, _in_, _invalid_, _N_A_, \
22
+ _not_scalar_, _SPACE_, _UNDER_, _version_, _version_info
23
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS, _FOR_DOCS, \
24
+ _getenv, LazyImportError, _sys, _sys_version_info2
25
+
26
+ from copy import copy as _copy, deepcopy as _deepcopy
27
+ from math import copysign as _copysign
28
+ import inspect as _inspect
29
+
30
+ __all__ = _ALL_LAZY.basics
31
+ __version__ = '24.03.19'
32
+
33
+ _0_0 = 0.0 # in .constants
34
+ _below_ = 'below'
35
+ _list_tuple_types = (list, tuple)
36
+ _list_tuple_set_types = (list, tuple, set)
37
+ _odd_ = 'odd'
38
+ _PYGEODESY_XPACKAGES_ = 'PYGEODESY_XPACKAGES'
39
+ _required_ = 'required'
40
+
41
+ try: # Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 395, 2022 p. 577+
42
+ from numbers import Integral as _Ints, Real as _Scalars # .units
43
+ except ImportError:
44
+ try:
45
+ _Ints = int, long # int objects (C{tuple})
46
+ except NameError: # Python 3+
47
+ _Ints = int, # int objects (C{tuple})
48
+ _Scalars = _Ints + (float,)
49
+
50
+ try:
51
+ try: # use C{from collections.abc import ...} in Python 3.9+
52
+ from collections.abc import Sequence as _Sequence # in .points
53
+ except ImportError: # no .abc in Python 3.8- and 2.7-
54
+ from collections import Sequence as _Sequence # in .points
55
+ if isinstance([], _Sequence) and isinstance((), _Sequence):
56
+ # and isinstance(range(1), _Sequence):
57
+ _Seqs = _Sequence
58
+ else:
59
+ raise ImportError() # _AssertionError
60
+ except ImportError:
61
+ _Sequence = tuple # immutable for .points._Basequence
62
+ _Seqs = list, _Sequence # range for function len2 below
63
+
64
+
65
+ def _passarg(arg): # in .auxilats.auxLat
66
+ '''(INTERNAL) Helper, no-op.
67
+ '''
68
+ return arg
69
+
70
+
71
+ def _passargs(*args): # in .utily
72
+ '''(INTERNAL) Helper, no-op.
73
+ '''
74
+ return args
75
+
76
+
77
+ try:
78
+ _Bytes = unicode, bytearray # PYCHOK expected
79
+ _Strs = basestring, str # XXX , bytes
80
+ str2ub = ub2str = _passarg # avoids UnicodeDecodeError
81
+
82
+ def _Xstr(exc): # PYCHOK no cover
83
+ '''I{Invoke only with caught ImportError} B{C{exc}}.
84
+
85
+ C{... "can't import name _distributor_init" ...}
86
+
87
+ only for C{numpy}, C{scipy} import errors occurring
88
+ on arm64 Apple Silicon running macOS' Python 2.7.16?
89
+ '''
90
+ t = str(exc)
91
+ if '_distributor_init' in t:
92
+ from sys import exc_info
93
+ from traceback import extract_tb
94
+ tb = exc_info()[2] # 3-tuple (type, value, traceback)
95
+ t4 = extract_tb(tb, 1)[0] # 4-tuple (file, line, name, 'import ...')
96
+ t = _SPACE_("can't", t4[3] or _N_A_)
97
+ del tb, t4
98
+ return t
99
+
100
+ except NameError: # Python 3+
101
+ from pygeodesy.interns import _utf_8_
102
+
103
+ _Bytes = bytes, bytearray
104
+ _Strs = str, # tuple
105
+ _Xstr = str
106
+
107
+ def str2ub(sb):
108
+ '''Convert C{str} to C{unicode bytes}.
109
+ '''
110
+ if isinstance(sb, _Strs):
111
+ sb = sb.encode(_utf_8_)
112
+ return sb
113
+
114
+ def ub2str(ub):
115
+ '''Convert C{unicode bytes} to C{str}.
116
+ '''
117
+ if isinstance(ub, _Bytes):
118
+ ub = str(ub.decode(_utf_8_))
119
+ return ub
120
+
121
+
122
+ def _args_kwds_names(func):
123
+ '''(INTERNAL) Get a C{func}'s args and kwds names, including
124
+ C{self} for methods.
125
+
126
+ @note: Python 2 may I{not} include the C{*args} nor the
127
+ C{**kwds} names.
128
+ '''
129
+ try:
130
+ args_kwds = _inspect.signature(func).parameters.keys()
131
+ except AttributeError: # .signature new Python 3+
132
+ args_kwds = _inspect.getargspec(func).args
133
+ return tuple(args_kwds)
134
+
135
+
136
+ def clips(sb, limit=50, white=NN):
137
+ '''Clip a string to the given length limit.
138
+
139
+ @arg sb: String (C{str} or C{bytes}).
140
+ @kwarg limit: Length limit (C{int}).
141
+ @kwarg white: Optionally, replace all whitespace (C{str}).
142
+
143
+ @return: The clipped or unclipped B{C{sb}}.
144
+ '''
145
+ T = type(sb)
146
+ if len(sb) > limit > 8:
147
+ h = limit // 2
148
+ sb = T(_ELLIPSIS4_).join((sb[:h], sb[-h:]))
149
+ if white: # replace whitespace
150
+ sb = T(white).join(sb.split())
151
+ return sb
152
+
153
+
154
+ def copysign0(x, y):
155
+ '''Like C{math.copysign(x, y)} except C{zero}, I{unsigned}.
156
+
157
+ @return: C{math.copysign(B{x}, B{y})} if B{C{x}} else
158
+ C{type(B{x})(0)}.
159
+ '''
160
+ return _copysign(x, (y if y else 0)) if x else copytype(0, x)
161
+
162
+
163
+ def copytype(x, y):
164
+ '''Return the value of B{x} as C{type} of C{y}.
165
+
166
+ @return: C{type(B{y})(B{x})}.
167
+ '''
168
+ return type(y)(x if x else _0_0)
169
+
170
+
171
+ def halfs2(str2):
172
+ '''Split a string in 2 halfs.
173
+
174
+ @arg str2: String to split (C{str}).
175
+
176
+ @return: 2-Tuple C{(_1st, _2nd)} half (C{str}).
177
+
178
+ @raise ValueError: Zero or odd C{len(B{str2})}.
179
+ '''
180
+ h, r = divmod(len(str2), 2)
181
+ if r or not h:
182
+ raise _ValueError(str2=str2, txt=_odd_)
183
+ return str2[:h], str2[h:]
184
+
185
+
186
+ def int1s(x):
187
+ '''Count the number of 1-bits in an C{int}, I{unsigned}.
188
+
189
+ @note: C{int1s(-B{x}) == int1s(abs(B{x}))}.
190
+ '''
191
+ try:
192
+ return x.bit_count() # Python 3.10+
193
+ except AttributeError:
194
+ # bin(-x) = '-' + bin(abs(x))
195
+ return bin(x).count(_1_)
196
+
197
+
198
+ def isbool(obj):
199
+ '''Check whether an object is C{bool}ean.
200
+
201
+ @arg obj: The object (any C{type}).
202
+
203
+ @return: C{True} if B{C{obj}} is C{bool}ean,
204
+ C{False} otherwise.
205
+ '''
206
+ return isinstance(obj, bool) # and (obj is False
207
+ # or obj is True)
208
+
209
+ assert not (isbool(1) or isbool(0) or isbool(None)) # PYCHOK 2
210
+
211
+ if _FOR_DOCS: # XXX avoid epydoc Python 2.7 error
212
+
213
+ def isclass(obj):
214
+ '''Return C{True} if B{C{obj}} is a C{class} or C{type}.
215
+
216
+ @see: Python's C{inspect.isclass}.
217
+ '''
218
+ return _inspect.isclass(obj)
219
+ else:
220
+ isclass = _inspect.isclass
221
+
222
+
223
+ def isCartesian(obj, ellipsoidal=None):
224
+ '''Is B{C{obj}} some C{Cartesian}?
225
+
226
+ @arg obj: The object (any C{type}).
227
+ @kwarg ellipsoidal: If C{None}, return the type of any C{Cartesian},
228
+ if C{True}, only an ellipsoidal C{Cartesian type}
229
+ or if C{False}, only a spherical C{Cartesian type}.
230
+
231
+ @return: C{type(B{obj}} if B{C{obj}} is a C{Cartesian} instance of
232
+ the required type, C{False} if a C{Cartesian} of an other
233
+ type or C{None} otherwise.
234
+ '''
235
+ if ellipsoidal is not None:
236
+ try:
237
+ return obj.ellipsoidalCartesian if ellipsoidal else obj.sphericalCartesian
238
+ except AttributeError:
239
+ return None
240
+ return isinstanceof(obj, _MODS.cartesianBase.CartesianBase)
241
+
242
+
243
+ def iscomplex(obj):
244
+ '''Check whether an object is a C{complex} or complex C{str}.
245
+
246
+ @arg obj: The object (any C{type}).
247
+
248
+ @return: C{True} if B{C{obj}} is C{complex}, otherwise
249
+ C{False}.
250
+ '''
251
+ try: # hasattr('conjugate'), hasattr('real') and hasattr('imag')
252
+ return isinstance(obj, complex) or (isstr(obj)
253
+ and isinstance(complex(obj), complex)) # numbers.Complex?
254
+ except (TypeError, ValueError):
255
+ return False
256
+
257
+
258
+ def isDEPRECATED(obj):
259
+ '''Return C{True} if C{B{obj}} is a C{DEPRECATED} class, method
260
+ or function, C{False} if not or C{None} if undetermined.
261
+ '''
262
+ try: # XXX inspect.getdoc(obj)
263
+ return bool(obj.__doc__.lstrip().startswith(_DEPRECATED_))
264
+ except AttributeError:
265
+ return None
266
+
267
+
268
+ def isfloat(obj):
269
+ '''Check whether an object is a C{float} or float C{str}.
270
+
271
+ @arg obj: The object (any C{type}).
272
+
273
+ @return: C{True} if B{C{obj}} is a C{float}, otherwise
274
+ C{False}.
275
+ '''
276
+ try:
277
+ return isinstance( obj, float) or (isstr(obj)
278
+ and isinstance(float(obj), float))
279
+ except (TypeError, ValueError):
280
+ return False
281
+
282
+
283
+ try:
284
+ isidentifier = str.isidentifier # Python 3, must be str
285
+ except AttributeError: # Python 2-
286
+
287
+ def isidentifier(obj):
288
+ '''Return C{True} if B{C{obj}} is a Python identifier.
289
+ '''
290
+ return bool(obj and isstr(obj)
291
+ and obj.replace(_UNDER_, NN).isalnum()
292
+ and not obj[:1].isdigit())
293
+
294
+
295
+ def isinstanceof(obj, *classes):
296
+ '''Is B{C{ob}} an instance of one of the C{classes}?
297
+
298
+ @arg obj: The instance (any C{type}).
299
+ @arg classes: One or more classes (C{class}).
300
+
301
+ @return: C{type(B{obj}} if B{C{obj}} is an instance
302
+ of the B{C{classes}}, C{None} otherwise.
303
+ '''
304
+ return type(obj) if isinstance(obj, classes) else None
305
+
306
+
307
+ def isint(obj, both=False):
308
+ '''Check for C{int} type or an integer C{float} value.
309
+
310
+ @arg obj: The object (any C{type}).
311
+ @kwarg both: If C{true}, check C{float} and L{Fsum}
312
+ type and value (C{bool}).
313
+
314
+ @return: C{True} if B{C{obj}} is C{int} or I{integer}
315
+ C{float} or L{Fsum}, C{False} otherwise.
316
+
317
+ @note: Both C{isint(True)} and C{isint(False)} return
318
+ C{False} (and no longer C{True}).
319
+ '''
320
+ if isinstance(obj, _Ints) and not isbool(obj):
321
+ return True
322
+ elif both: # and isinstance(obj, (float, Fsum))
323
+ try: # NOT , _Scalars) to include Fsum!
324
+ return obj.is_integer()
325
+ except AttributeError:
326
+ pass # XXX float(int(obj)) == obj?
327
+ return False
328
+
329
+
330
+ try:
331
+ from keyword import iskeyword # Python 2.7+
332
+ except ImportError:
333
+
334
+ def iskeyword(unused):
335
+ '''Not Implemented, C{False} always.
336
+ '''
337
+ return False
338
+
339
+
340
+ def isLatLon(obj, ellipsoidal=None):
341
+ '''Is B{C{obj}} some C{LatLon}?
342
+
343
+ @arg obj: The object (any C{type}).
344
+ @kwarg ellipsoidal: If C{None}, return the type of any C{LatLon},
345
+ if C{True}, only an ellipsoidal C{LatLon type}
346
+ or if C{False}, only a spherical C{LatLon type}.
347
+
348
+ @return: C{type(B{obj}} if B{C{obj}} is a C{LatLon} instance of
349
+ the required type, C{False} if a C{LatLon} of an other
350
+ type or {None} otherwise.
351
+ '''
352
+ if ellipsoidal is not None:
353
+ try:
354
+ return obj.ellipsoidalLatLon if ellipsoidal else obj.sphericalLatLon
355
+ except AttributeError:
356
+ return None
357
+ return isinstanceof(obj, _MODS.latlonBase.LatLonBase)
358
+
359
+
360
+ def islistuple(obj, minum=0):
361
+ '''Check for list or tuple C{type} with a minumal length.
362
+
363
+ @arg obj: The object (any C{type}).
364
+ @kwarg minum: Minimal C{len} required C({int}).
365
+
366
+ @return: C{True} if B{C{obj}} is C{list} or C{tuple} with
367
+ C{len} at least B{C{minum}}, C{False} otherwise.
368
+ '''
369
+ return isinstance(obj, _list_tuple_types) and len(obj) >= minum
370
+
371
+
372
+ def isNvector(obj, ellipsoidal=None):
373
+ '''Is B{C{obj}} some C{Nvector}?
374
+
375
+ @arg obj: The object (any C{type}).
376
+ @kwarg ellipsoidal: If C{None}, return the type of any C{Nvector},
377
+ if C{True}, only an ellipsoidal C{Nvector type}
378
+ or if C{False}, only a spherical C{Nvector type}.
379
+
380
+ @return: C{type(B{obj}} if B{C{obj}} is an C{Nvector} instance of
381
+ the required type, C{False} if an C{Nvector} of an other
382
+ type or {None} otherwise.
383
+ '''
384
+ if ellipsoidal is not None:
385
+ try:
386
+ return obj.ellipsoidalNvector if ellipsoidal else obj.sphericalNvector
387
+ except AttributeError:
388
+ return None
389
+ return isinstanceof(obj, _MODS.nvectorBase.NvectorBase)
390
+
391
+
392
+ def isodd(x):
393
+ '''Is B{C{x}} odd?
394
+
395
+ @arg x: Value (C{scalar}).
396
+
397
+ @return: C{True} if B{C{x}} is odd,
398
+ C{False} otherwise.
399
+ '''
400
+ return bool(int(x) & 1) # == bool(int(x) % 2)
401
+
402
+
403
+ def isscalar(obj):
404
+ '''Check for scalar types.
405
+
406
+ @arg obj: The object (any C{type}).
407
+
408
+ @return: C{True} if B{C{obj}} is C{scalar}, C{False} otherwise.
409
+ '''
410
+ return isinstance(obj, _Scalars) and not isbool(obj)
411
+
412
+
413
+ def issequence(obj, *excls):
414
+ '''Check for sequence types.
415
+
416
+ @arg obj: The object (any C{type}).
417
+ @arg excls: Classes to exclude (C{type}), all positional.
418
+
419
+ @note: Excluding C{tuple} implies excluding C{namedtuple}.
420
+
421
+ @return: C{True} if B{C{obj}} is a sequence, C{False} otherwise.
422
+ '''
423
+ return isinstance(obj, _Seqs) and not (excls and isinstance(obj, excls))
424
+
425
+
426
+ def isstr(obj):
427
+ '''Check for string types.
428
+
429
+ @arg obj: The object (any C{type}).
430
+
431
+ @return: C{True} if B{C{obj}} is C{str}, C{False} otherwise.
432
+ '''
433
+ return isinstance(obj, _Strs)
434
+
435
+
436
+ def issubclassof(Sub, *Supers):
437
+ '''Check whether a class is a sub-class of some other class(es).
438
+
439
+ @arg Sub: The sub-class (C{class}).
440
+ @arg Supers: One or more C(super) classes (C{class}).
441
+
442
+ @return: C{True} if B{C{Sub}} is a sub-class of any B{C{Supers}},
443
+ C{False} if not (C{bool}) or C{None} if B{C{Sub}} is not
444
+ a class or if no B{C{Supers}} are given or none of those
445
+ are a class.
446
+ '''
447
+ if isclass(Sub):
448
+ t = tuple(S for S in Supers if isclass(S))
449
+ if t:
450
+ return bool(issubclass(Sub, t))
451
+ return None
452
+
453
+
454
+ def itemsorted(adict, *items_args, **asorted_reverse):
455
+ '''Return the items of C{B{adict}} sorted I{alphabetically,
456
+ case-insensitively} and in I{ascending} order.
457
+
458
+ @arg items_args: Optional positional argument(s) for method
459
+ C{B{adict}.items(B*{items_args})}.
460
+ @kwarg asorted_reverse: Use keyword argument C{B{asorted}=False}
461
+ for I{alphabetical, case-sensitive} sorting and
462
+ C{B{reverse}=True} for sorting in C{descending}
463
+ order.
464
+ '''
465
+ def _ins(item):
466
+ return item[0].lower()
467
+
468
+ def _key_rev(asorted=True, reverse=False):
469
+ return (_ins if asorted else None), reverse
470
+
471
+ key, rev = _key_rev(**asorted_reverse)
472
+ items = adict.items(*items_args) if items_args else adict.items()
473
+ return sorted(items, reverse=rev, key=key)
474
+
475
+
476
+ def len2(items):
477
+ '''Make built-in function L{len} work for generators, iterators,
478
+ etc. since those can only be started exactly once.
479
+
480
+ @arg items: Generator, iterator, list, range, tuple, etc.
481
+
482
+ @return: 2-Tuple C{(n, items)} of the number of items (C{int})
483
+ and the items (C{list} or C{tuple}).
484
+ '''
485
+ if not isinstance(items, _Seqs): # NOT hasattr(items, '__len__'):
486
+ items = list(items)
487
+ return len(items), items
488
+
489
+
490
+ def map1(fun1, *xs): # XXX map_
491
+ '''Apply a single-argument function to each B{C{xs}} and
492
+ return a C{tuple} of results.
493
+
494
+ @arg fun1: 1-Arg function (C{callable}).
495
+ @arg xs: Arguments (C{any positional}).
496
+
497
+ @return: Function results (C{tuple}).
498
+ '''
499
+ return tuple(map(fun1, xs))
500
+
501
+
502
+ def map2(fun, *xs):
503
+ '''Apply a function to arguments and return a C{tuple} of results.
504
+
505
+ Unlike Python 2's built-in L{map}, Python 3+ L{map} returns a
506
+ L{map} object, an iterator-like object which generates the
507
+ results only once. Converting the L{map} object to a tuple
508
+ maintains the Python 2 behavior.
509
+
510
+ @arg fun: Function (C{callable}).
511
+ @arg xs: Arguments (C{list, tuple, ...}).
512
+
513
+ @return: Function results (C{tuple}).
514
+ '''
515
+ return tuple(map(fun, *xs))
516
+
517
+
518
+ def neg(x, neg0=None):
519
+ '''Negate C{x} and optionally, negate C{0.0} and C{-0.0}.
520
+
521
+ @kwarg neg0: Defines the return value for zero C{B{x}}: if C{None}
522
+ return C{0.0}, if C{True} return C{NEG0 if B{x}=0.0}
523
+ and C{0.0 if B{x}=NEG0} or if C{False} return C{B{x}}
524
+ I{as-is} (C{bool} or C{None}).
525
+
526
+ @return: C{-B{x} if B{x} else 0.0, NEG0 or B{x}}.
527
+ '''
528
+ return (-x) if x else (_0_0 if neg0 is None else (x if not neg0 else
529
+ (_0_0 if signBit(x) else _MODS.constants.NEG0)))
530
+
531
+
532
+ def neg_(*xs):
533
+ '''Negate all C{xs} with L{neg}.
534
+
535
+ @return: A C{map(neg, B{xs})}.
536
+ '''
537
+ return map(neg, xs)
538
+
539
+
540
+ def _req_d_by(where, **name): # in .basics
541
+ '''(INTERNAL) Get the fully qualified name.
542
+ '''
543
+ m = _MODS.named.modulename(where, prefixed=True)
544
+ if name:
545
+ n = _xkwds_get(name, name=NN)
546
+ if n:
547
+ m = _DOT_(m, n)
548
+ return _SPACE_(_required_, _by_, m)
549
+
550
+
551
+ def _reverange(n, stop=-1, step=-1):
552
+ '''(INTERNAL) Reversed range yielding C{n-1, n-1-step, ..., stop+1}.
553
+ '''
554
+ return range(n - 1, stop, step)
555
+
556
+
557
+ def signBit(x):
558
+ '''Return C{signbit(B{x})}, like C++.
559
+
560
+ @return: C{True} if C{B{x} < 0} or C{NEG0} (C{bool}).
561
+ '''
562
+ return x < 0 or _MODS.constants.isneg0(x)
563
+
564
+
565
+ def _signOf(x, ref): # in .fsums
566
+ '''(INTERNAL) Return the sign of B{C{x}} versus B{C{ref}}.
567
+ '''
568
+ return +1 if x > ref else (-1 if x < ref else 0)
569
+
570
+
571
+ def signOf(x):
572
+ '''Return sign of C{x} as C{int}.
573
+
574
+ @return: -1, 0 or +1 (C{int}).
575
+ '''
576
+ try:
577
+ s = x.signOf() # Fsum instance?
578
+ except AttributeError:
579
+ s = _signOf(x, 0)
580
+ return s
581
+
582
+
583
+ def _sizeof(inst):
584
+ '''(INTERNAL) Recursively size an C{inst}ance.
585
+
586
+ @return: Instance' size in bytes (C{int}),
587
+ ignoring class attributes and
588
+ counting duplicates only once or
589
+ C{None}.
590
+
591
+ @note: With C{PyPy}, the size is always C{None}.
592
+ '''
593
+ try:
594
+ _zB = _sys.getsizeof
595
+ _zD = _zB(None) # get some default
596
+ except TypeError: # PyPy3.10
597
+ return None
598
+
599
+ def _zR(s, iterable):
600
+ z, _s = 0, s.add
601
+ for o in iterable:
602
+ i = id(o)
603
+ if i not in s:
604
+ _s(i)
605
+ z += _zB(o, _zD)
606
+ if isinstance(o, dict):
607
+ z += _zR(s, o.keys())
608
+ z += _zR(s, o.values())
609
+ elif isinstance(o, _list_tuple_set_types):
610
+ z += _zR(s, o)
611
+ else:
612
+ try: # size instance' attr values only
613
+ z += _zR(s, o.__dict__.values())
614
+ except AttributeError: # None, int, etc.
615
+ pass
616
+ return z
617
+
618
+ return _zR(set(), (inst,))
619
+
620
+
621
+ def splice(iterable, n=2, **fill):
622
+ '''Split an iterable into C{n} slices.
623
+
624
+ @arg iterable: Items to be spliced (C{list}, C{tuple}, ...).
625
+ @kwarg n: Number of slices to generate (C{int}).
626
+ @kwarg fill: Optional fill value for missing items.
627
+
628
+ @return: A generator for each of B{C{n}} slices,
629
+ M{iterable[i::n] for i=0..n}.
630
+
631
+ @raise TypeError: Invalid B{C{n}}.
632
+
633
+ @note: Each generated slice is a C{tuple} or a C{list},
634
+ the latter only if the B{C{iterable}} is a C{list}.
635
+
636
+ @example:
637
+
638
+ >>> from pygeodesy import splice
639
+
640
+ >>> a, b = splice(range(10))
641
+ >>> a, b
642
+ ((0, 2, 4, 6, 8), (1, 3, 5, 7, 9))
643
+
644
+ >>> a, b, c = splice(range(10), n=3)
645
+ >>> a, b, c
646
+ ((0, 3, 6, 9), (1, 4, 7), (2, 5, 8))
647
+
648
+ >>> a, b, c = splice(range(10), n=3, fill=-1)
649
+ >>> a, b, c
650
+ ((0, 3, 6, 9), (1, 4, 7, -1), (2, 5, 8, -1))
651
+
652
+ >>> tuple(splice(list(range(9)), n=5))
653
+ ([0, 5], [1, 6], [2, 7], [3, 8], [4])
654
+
655
+ >>> splice(range(9), n=1)
656
+ <generator object splice at 0x0...>
657
+ '''
658
+ if not isint(n):
659
+ raise _TypeError(n=n)
660
+
661
+ t = iterable
662
+ if not isinstance(t, _list_tuple_types):
663
+ t = tuple(t) # force tuple, also for PyPy3
664
+
665
+ if n > 1:
666
+ if fill:
667
+ fill = _xkwds_get(fill, fill=MISSING)
668
+ if fill is not MISSING:
669
+ m = len(t) % n
670
+ if m > 0: # same type fill
671
+ t += type(t)((fill,) * (n - m))
672
+ for i in range(n):
673
+ # XXX t[i::n] chokes PyChecker
674
+ yield t[slice(i, None, n)]
675
+ else:
676
+ yield t
677
+
678
+
679
+ def _splituple(strs, *sep_splits): # in .mgrs, .osgr, .webmercator
680
+ '''(INTERNAL) Split a C{comma}- or C{whitespace}-separated
681
+ string into a C{tuple} of stripped strings.
682
+ '''
683
+ t = (strs.split(*sep_splits) if sep_splits else
684
+ strs.replace(_COMMA_, _SPACE_).split()) if strs else ()
685
+ return tuple(s.strip() for s in t if s)
686
+
687
+
688
+ def unsigned0(x):
689
+ '''Unsign if C{0.0}.
690
+
691
+ @return: C{B{x}} if B{C{x}} else C{0.0}.
692
+ '''
693
+ return x if x else _0_0
694
+
695
+
696
+ def _xcopy(obj, deep=False):
697
+ '''(INTERNAL) Copy an object, shallow or deep.
698
+
699
+ @arg obj: The object to copy (any C{type}).
700
+ @kwarg deep: If C{True} make a deep, otherwise
701
+ a shallow copy (C{bool}).
702
+
703
+ @return: The copy of B{C{obj}}.
704
+ '''
705
+ return _deepcopy(obj) if deep else _copy(obj)
706
+
707
+
708
+ def _xdup(obj, deep=False, **items):
709
+ '''(INTERNAL) Duplicate an object, replacing some attributes.
710
+
711
+ @arg obj: The object to copy (any C{type}).
712
+ @kwarg deep: If C{True} copy deep, otherwise shallow.
713
+ @kwarg items: Attributes to be changed (C{any}).
714
+
715
+ @return: A duplicate of B{C{obj}} with modified
716
+ attributes, if any B{C{items}}.
717
+
718
+ @raise AttributeError: Some B{C{items}} invalid.
719
+ '''
720
+ d = _xcopy(obj, deep=deep)
721
+ for n, v in items.items():
722
+ if getattr(d, n, v) != v:
723
+ setattr(d, n, v)
724
+ elif not hasattr(d, n):
725
+ t = _MODS.named.classname(obj)
726
+ t = _SPACE_(_DOT_(t, n), _invalid_)
727
+ raise _AttributeError(txt=t, obj=obj, **items)
728
+ # if items:
729
+ # _MODS.props._update_all(d)
730
+ return d
731
+
732
+
733
+ def _xgeographiclib(where, *required):
734
+ '''(INTERNAL) Import C{geographiclib} and check required version.
735
+ '''
736
+ try:
737
+ _xpackage(_xgeographiclib)
738
+ import geographiclib
739
+ except ImportError as x:
740
+ raise _xImportError(x, where, Error=LazyImportError)
741
+ return _xversion(geographiclib, where, *required)
742
+
743
+
744
+ def _xImportError(exc, where, Error=_ImportError, **name):
745
+ '''(INTERNAL) Embellish an C{Lazy/ImportError}.
746
+ '''
747
+ t = _req_d_by(where, **name)
748
+ return Error(_Xstr(exc), txt=t, cause=exc)
749
+
750
+
751
+ def _xinstanceof(*Types, **names_values):
752
+ '''(INTERNAL) Check C{Types} of all C{name=value} pairs.
753
+
754
+ @arg Types: One or more classes or types (C{class}), all
755
+ positional.
756
+ @kwarg names_values: One or more C{B{name}=value} pairs
757
+ with the C{value} to be checked.
758
+
759
+ @raise TypeError: One B{C{names_values}} pair is not an
760
+ instance of any of the B{C{Types}}.
761
+ '''
762
+ if not (Types and names_values):
763
+ raise _xAssertionError(_xinstanceof, *Types, **names_values)
764
+
765
+ for n, v in names_values.items():
766
+ if not isinstance(v, Types):
767
+ raise _TypesError(n, v, *Types)
768
+
769
+
770
+ def _xisscalar(**names_values):
771
+ '''(INTERNAL) Check all C{name=value} pairs to be C{scalar}.
772
+ '''
773
+ for n, v in names_values.items():
774
+ if not isscalar(v):
775
+ raise _TypeError(n, v, txt=_not_scalar_)
776
+
777
+
778
+ def _xnumpy(where, *required):
779
+ '''(INTERNAL) Import C{numpy} and check required version.
780
+ '''
781
+ try:
782
+ _xpackage(_xnumpy)
783
+ import numpy
784
+ except ImportError as x:
785
+ raise _xImportError(x, where)
786
+ return _xversion(numpy, where, *required)
787
+
788
+
789
+ def _xor(x, *xs):
790
+ '''(INTERNAL) Exclusive-or C{x} and C{xs}.
791
+ '''
792
+ for x_ in xs:
793
+ x ^= x_
794
+ return x
795
+
796
+
797
+ def _xpackage(_xpkg):
798
+ '''(INTERNAL) Check dependency to be excluded.
799
+ '''
800
+ n = _xpkg.__name__[2:] # remove _x
801
+ if n in _XPACKAGES:
802
+ x = _SPACE_(n, _in_, _PYGEODESY_XPACKAGES_)
803
+ e = _enquote(_getenv(_PYGEODESY_XPACKAGES_, NN))
804
+ raise ImportError(_EQUAL_(x, e))
805
+
806
+
807
+ def _xscipy(where, *required):
808
+ '''(INTERNAL) Import C{scipy} and check required version.
809
+ '''
810
+ try:
811
+ _xpackage(_xscipy)
812
+ import scipy
813
+ except ImportError as x:
814
+ raise _xImportError(x, where)
815
+ return _xversion(scipy, where, *required)
816
+
817
+
818
+ def _xsubclassof(*Classes, **names_values):
819
+ '''(INTERNAL) Check (super) class of all C{name=value} pairs.
820
+
821
+ @arg Classes: One or more classes or types (C{class}), all
822
+ positional.
823
+ @kwarg names_values: One or more C{B{name}=value} pairs
824
+ with the C{value} to be checked.
825
+
826
+ @raise TypeError: One B{C{names_values}} pair is not a
827
+ (sub-)class of any of the B{C{Classes}}.
828
+ '''
829
+ if not (Classes and names_values):
830
+ raise _xAssertionError(_xsubclassof, *Classes, **names_values)
831
+
832
+ for n, v in names_values.items():
833
+ if not issubclassof(v, *Classes):
834
+ raise _TypesError(n, v, *Classes)
835
+
836
+
837
+ def _xversion(package, where, *required, **name):
838
+ '''(INTERNAL) Check the C{package} version vs B{C{required}}.
839
+ '''
840
+ if required:
841
+ t = _version_info(package)
842
+ if t[:len(required)] < required:
843
+ t = _SPACE_(package.__name__,
844
+ _version_, _DOT_(*t),
845
+ _below_, _DOT_(*required),
846
+ _req_d_by(where, **name))
847
+ raise ImportError(t)
848
+ return package
849
+
850
+
851
+ def _xzip(*args, **strict): # PYCHOK no cover
852
+ '''(INTERNAL) Standard C{zip(..., strict=True)}.
853
+ '''
854
+ s = _xkwds_get(strict, strict=True)
855
+ if s:
856
+ if _zip is zip: # < (3, 10)
857
+ t = _MODS.streprs.unstr(_xzip, *args, strict=s)
858
+ raise _NotImplementedError(t, txt=None)
859
+ return _zip(*args)
860
+ return zip(*args)
861
+
862
+
863
+ if _sys_version_info2 < (3, 10): # see .errors
864
+ _zip = zip # PYCHOK exported
865
+ else: # Python 3.10+
866
+
867
+ def _zip(*args):
868
+ return zip(*args, strict=True)
869
+
870
+ _XPACKAGES = _splituple(_getenv(_PYGEODESY_XPACKAGES_, NN).lower())
871
+
872
+ # **) MIT License
873
+ #
874
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
875
+ #
876
+ # Permission is hereby granted, free of charge, to any person obtaining a
877
+ # copy of this software and associated documentation files (the "Software"),
878
+ # to deal in the Software without restriction, including without limitation
879
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
880
+ # and/or sell copies of the Software, and to permit persons to whom the
881
+ # Software is furnished to do so, subject to the following conditions:
882
+ #
883
+ # The above copyright notice and this permission notice shall be included
884
+ # in all copies or substantial portions of the Software.
885
+ #
886
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
887
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
888
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
889
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
890
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
891
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
892
+ # OTHER DEALINGS IN THE SOFTWARE.