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/iters.py ADDED
@@ -0,0 +1,545 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Iterators with options.
5
+
6
+ Iterator classes L{LatLon2PsxyIter} and L{PointsIter} to iterate
7
+ over iterables, lists, sets, tuples, etc. with optional loop-back to
8
+ the initial items, skipping of duplicate items and copying of the
9
+ iterated items.
10
+ '''
11
+
12
+ from pygeodesy.basics import islistuple, issubclassof, \
13
+ len2, map2, _passarg
14
+ # from pygeodesy.constants import _1_0 # from .utily
15
+ from pygeodesy.errors import _IndexError, LenError, PointsError, \
16
+ _TypeError, _ValueError
17
+ from pygeodesy.interns import NN, _0_, _composite_, _few_, \
18
+ _latlon_, _points_, _too_
19
+ from pygeodesy.lazily import _ALL_DOCS, _ALL_LAZY, _ALL_MODS as _MODS
20
+ from pygeodesy.named import Fmt, _Named, property_RO
21
+ from pygeodesy.namedTuples import Point3Tuple, Points2Tuple
22
+ # from pygeodesy.props import property_RO # from .named
23
+ # from pygeodesy.streprs import Fmt # from .named
24
+ from pygeodesy.units import Int, Radius
25
+ from pygeodesy.utily import degrees2m, _Wrap, _1_0
26
+
27
+ __all__ = _ALL_LAZY.iters
28
+ __version__ = '23.12.14'
29
+
30
+ _items_ = 'items'
31
+ _iterNumpy2len = 1 # adjustable for testing purposes
32
+ _NOTHING = object() # unique
33
+
34
+
35
+ class _BaseIter(_Named):
36
+ '''(INTERNAL) Iterator over items with loop-back and de-duplication.
37
+
38
+ @see: Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 418+, 2022 p. 600+
39
+ '''
40
+ _closed = True
41
+ _copies = ()
42
+ _dedup = False
43
+ _Error = LenError
44
+ _items = None
45
+ _len = 0
46
+ _loop = ()
47
+ _looped = False
48
+ _name = _items_
49
+ _prev = _NOTHING
50
+ _wrap = False
51
+
52
+ def __init__(self, items, loop=0, dedup=False, Error=None, name=NN):
53
+ '''New iterator over an iterable of B{C{items}}.
54
+
55
+ @arg items: Iterable (any C{type}, except composites).
56
+ @kwarg loop: Number of loop-back items, also initial enumerate and
57
+ iterate index (non-negative C{int}).
58
+ @kwarg dedup: Skip duplicate items (C{bool}).
59
+ @kwarg Error: Error to raise (L{LenError}).
60
+ @kwarg name: Optional name (C{str}).
61
+
62
+ @raise Error: Invalid B{C{items}} or sufficient number of B{C{items}}.
63
+
64
+ @raise TypeError: Composite B{C{items}}.
65
+ '''
66
+ if dedup:
67
+ self._dedup = True
68
+ if issubclassof(Error, Exception):
69
+ self._Error = Error
70
+ if name:
71
+ self.rename(name)
72
+
73
+ if islistuple(items): # range in Python 2
74
+ self._items = items
75
+ elif _MODS.booleans.isBoolean(items):
76
+ raise _TypeError(points=_composite_)
77
+ # XXX if hasattr(items, 'next') or hasattr(items, '__length_hint__'):
78
+ # XXX # handle reversed, iter, etc. items types
79
+ self._iter = iter(items)
80
+ self._indx = -1
81
+ if Int(loop) > 0:
82
+ try:
83
+ self._loop = tuple(self.next for _ in range(loop))
84
+ if self.loop != loop:
85
+ raise RuntimeError # force Error
86
+ except (RuntimeError, StopIteration):
87
+ raise self._Error(self.name, self.loop, txt=_too_(_few_))
88
+
89
+ @property_RO
90
+ def copies(self):
91
+ '''Get the saved copies, if any (C{tuple} or C{list}) and only I{once}.
92
+ '''
93
+ cs = self._copies
94
+ if cs:
95
+ self._copies = ()
96
+ return cs
97
+
98
+ @property_RO
99
+ def dedup(self):
100
+ '''Get the de-duplication setting (C{bool}).
101
+ '''
102
+ return self._dedup
103
+
104
+ def enumerate(self, closed=False, copies=False, dedup=False):
105
+ '''Yield all items, each as a 2-tuple C{(index, item)}.
106
+
107
+ @kwarg closed: Loop back to the first B{C{point(s)}}.
108
+ @kwarg copies: Make a copy of all B{C{items}} (C{bool}).
109
+ @kwarg dedup: Set de-duplication in loop-back (C{bool}).
110
+ '''
111
+ for item in self.iterate(closed=closed, copies=copies, dedup=dedup):
112
+ yield self._indx, item
113
+
114
+ def __getitem__(self, index):
115
+ '''Get the item(s) at the given B{C{index}} or C{slice}.
116
+
117
+ @raise IndexError: Invalid B{C{index}}, beyond B{C{loop}}.
118
+ '''
119
+ t = self._items or self._copies or self._loop
120
+ try: # Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 293+, 2022 p. 408+
121
+ if isinstance(index, slice):
122
+ return t[index.start:index.stop:index.step]
123
+ else:
124
+ return t[index]
125
+ except IndexError as x:
126
+ t = Fmt.SQUARE(self.name, index)
127
+ raise _IndexError(str(x), txt=t, cause=x)
128
+
129
+ def __iter__(self): # PYCHOK no cover
130
+ '''Make this iterator C{iterable}.
131
+ '''
132
+ # Luciano Ramalho, "Fluent Python", O'Reilly, 2016 p. 421, 2022 p. 604+
133
+ return self.iterate() # XXX or self?
134
+
135
+ def iterate(self, closed=False, copies=False, dedup=False):
136
+ '''Yield all items, each as C{item}.
137
+
138
+ @kwarg closed: Loop back to the first B{C{point(s)}}.
139
+ @kwarg copies: Make a copy of all B{C{items}} (C{bool}).
140
+ @kwarg dedup: Set de-duplication in loop-back (C{bool}).
141
+
142
+ @raise Error: Using C{B{closed}=True} without B{C{loop}}-back.
143
+ '''
144
+ if closed and not self.loop:
145
+ raise self._Error(closed=closed, loop=self.loop)
146
+
147
+ if copies:
148
+ if self._items:
149
+ self._copies = self._items
150
+ self._items = _copy = None
151
+ else:
152
+ self._copies = list(self._loop)
153
+ _copy = self._copies.append
154
+ else: # del B{C{items}} reference
155
+ self._items = _copy = None
156
+
157
+ self._closed = closed
158
+ self._looped = False
159
+ if self._iter:
160
+ try:
161
+ _next_ = self.next_
162
+ if _copy:
163
+ while True:
164
+ item = _next_(dedup=dedup)
165
+ _copy(item)
166
+ yield item
167
+ else:
168
+ while True:
169
+ yield _next_(dedup=dedup)
170
+ except StopIteration:
171
+ self._iter = () # del self._iter, prevent re-iterate
172
+
173
+ def __len__(self):
174
+ '''Get the number of items seen so far.
175
+ '''
176
+ return self._len
177
+
178
+ @property_RO
179
+ def loop(self):
180
+ '''Get the B{C{loop}} setting (C{int}), C{0} for non-loop-back.
181
+ '''
182
+ return len(self._loop)
183
+
184
+ @property_RO
185
+ def looped(self):
186
+ '''In this C{Iter}ator in loop-back? (C{bool}).
187
+ '''
188
+ return self._looped
189
+
190
+ @property_RO
191
+ def next(self):
192
+ '''Get the next item.
193
+ '''
194
+ return self._next_dedup() if self._dedup else self._next(False)
195
+
196
+ # __next__ # NO __next__ AND __iter__ ... see Luciano Ramalho,
197
+ # # "Fluent Python", O'Reilly, 2016 p. 426, 2022 p. 610
198
+
199
+ def next_(self, dedup=False):
200
+ '''Return the next item.
201
+
202
+ @kwarg dedup: Set de-duplication for loop-back (C{bool}).
203
+ '''
204
+ return self._next_dedup() if self._dedup else self._next(dedup)
205
+
206
+ def _next(self, dedup):
207
+ '''Return the next item, regardless.
208
+
209
+ @arg dedup: Set de-duplication for loop-back (C{bool}).
210
+ '''
211
+ try:
212
+ self._indx += 1
213
+ self._len = self._indx # max(_len, _indx)
214
+ self._prev = item = next(self._iter)
215
+ return item
216
+ except StopIteration:
217
+ pass
218
+ if self._closed and self._loop: # loop back
219
+ self._dedup = bool(dedup or self._dedup)
220
+ self._indx = 0
221
+ self._iter = iter(self._loop)
222
+ self._loop = ()
223
+ self._looped = True
224
+ return next(self._iter)
225
+
226
+ def _next_dedup(self):
227
+ '''Return the next item, different from the previous one.
228
+ '''
229
+ prev = self._prev
230
+ item = self._next(True)
231
+ if prev is not _NOTHING:
232
+ while item == prev:
233
+ item = self._next(True)
234
+ return item
235
+
236
+
237
+ class PointsIter(_BaseIter):
238
+ '''Iterator for C{points} with optional loop-back and copies.
239
+ '''
240
+ _base = None
241
+ _Error = PointsError
242
+
243
+ def __init__(self, points, loop=0, base=None, dedup=False, wrap=False, name=NN):
244
+ '''New L{PointsIter} iterator.
245
+
246
+ @arg points: C{Iterable} or C{list}, C{sequence}, C{set}, C{tuple},
247
+ etc. (C{point}s).
248
+ @kwarg loop: Number of loop-back points, also initial C{enumerate} and
249
+ C{iterate} index (non-negative C{int}).
250
+ @kwarg base: Optional B{C{points}} instance for type checking (C{any}).
251
+ @kwarg dedup: Skip duplicate points (C{bool}).
252
+ @kwarg wrap: If C{True}, wrap or I{normalize} the enum-/iterated
253
+ B{C{points}} (C{bool}).
254
+ @kwarg name: Optional name (C{str}).
255
+
256
+ @raise PointsError: Insufficient number of B{C{points}}.
257
+
258
+ @raise TypeError: Some B{C{points}} are not B{C{base}}.
259
+ '''
260
+ _BaseIter.__init__(self, points, loop=loop, dedup=dedup, name=name or _points_)
261
+
262
+ if base and not (isNumpy2(points) or isTuple2(points)):
263
+ self._base = base
264
+ if wrap:
265
+ self._wrap = True
266
+
267
+ def enumerate(self, closed=False, copies=False): # PYCHOK signature
268
+ '''Iterate and yield each point as a 2-tuple C{(index, point)}.
269
+
270
+ @kwarg closed: Loop back to the first B{C{point(s)}}, de-dup'ed (C{bool}).
271
+ @kwarg copies: Save a copy of all B{C{points}} (C{bool}).
272
+
273
+ @raise PointsError: Insufficient number of B{C{points}} or using
274
+ C{B{closed}=True} without B{C{loop}}-back.
275
+
276
+ @raise TypeError: Some B{C{points}} are not B{C{base}}-compatible.
277
+ '''
278
+ for p in self.iterate(closed=closed, copies=copies):
279
+ yield self._indx, p
280
+
281
+ def iterate(self, closed=False, copies=False): # PYCHOK signature
282
+ '''Iterate through all B{C{points}} starting at index C{loop}.
283
+
284
+ @kwarg closed: Loop back to the first B{C{point(s)}}, de-dup'ed (C{bool}).
285
+ @kwarg copies: Save a copy of all B{C{points}} (C{bool}).
286
+
287
+ @raise PointsError: Insufficient number of B{C{points}} or using
288
+ C{B{closed}=True} without B{C{loop}}-back.
289
+
290
+ @raise TypeError: Some B{C{points}} are not B{C{base}}-compatible.
291
+ '''
292
+ if self._base:
293
+ _oth = self._base.others
294
+ _fmt = Fmt.SQUARE(points=0).replace
295
+ else:
296
+ _oth = _fmt = None
297
+
298
+ n = self.loop if self._iter else 0
299
+ _p = _Wrap.point if self._wrap else _passarg # and _Wrap.normal is not None
300
+ for p in _BaseIter.iterate(self, closed=closed, copies=copies, dedup=closed):
301
+ if _oth:
302
+ _oth(p, name=_fmt(_0_, str(self._indx)), up=2)
303
+ yield _p(p)
304
+ n += 1
305
+ if n < (4 if closed else 2):
306
+ raise self._Error(self.name, n, txt=_too_(_few_))
307
+
308
+
309
+ class LatLon2PsxyIter(PointsIter):
310
+ '''Iterate and convert for C{points} with optional loop-back and copies.
311
+ '''
312
+ _deg2m = None
313
+ _radius = None # keep degrees
314
+ _wrap = True
315
+
316
+ def __init__(self, points, loop=0, base=None, wrap=True, radius=None,
317
+ dedup=False, name=_latlon_):
318
+ '''New L{LatLon2PsxyIter} iterator.
319
+
320
+ @note: The C{LatLon} latitude is considered the I{pseudo-y} and
321
+ longitude the I{pseudo-x} coordinate, like L{LatLon2psxy}.
322
+
323
+ @arg points: C{Iterable} or C{list}, C{sequence}, C{set}, C{tuple},
324
+ etc. (C{LatLon}[]).
325
+ @kwarg loop: Number of loop-back points, also initial C{enumerate} and
326
+ C{iterate} index (non-negative C{int}).
327
+ @kwarg base: Optional B{C{points}} instance for type checking (C{any}).
328
+ @kwarg wrap: If C{True}, wrap or I{normalize} the enum-/iterated
329
+ B{C{points}} (C{bool}).
330
+ @kwarg radius: Mean earth radius (C{meter}) for conversion from
331
+ C{degrees} to C{meter} (or C{radians} if C{B{radius}=1}).
332
+ @kwarg dedup: Skip duplicate points (C{bool}).
333
+ @kwarg name: Optional name (C{str}).
334
+
335
+ @raise PointsError: Insufficient number of B{C{points}}.
336
+
337
+ @raise TypeError: Some B{C{points}} are not B{C{base}}-compatible.
338
+ '''
339
+ PointsIter.__init__(self, points, loop=loop, base=base, dedup=dedup, name=name)
340
+ if not wrap:
341
+ self._wrap = False
342
+ if radius:
343
+ self._radius = r = Radius(radius)
344
+ self._deg2m = degrees2m(_1_0, r)
345
+
346
+ def __getitem__(self, index):
347
+ '''Get the point(s) at the given B{C{index}} or C{slice}.
348
+
349
+ @raise IndexError: Invalid B{C{index}}, beyond B{C{loop}}.
350
+ '''
351
+ ll = PointsIter.__getitem__(self, index)
352
+ if isinstance(index, slice):
353
+ return map2(self._point3Tuple, ll)
354
+ else:
355
+ return self._point3Tuple(ll)
356
+
357
+ def enumerate(self, closed=False, copies=False): # PYCHOK signature
358
+ '''Iterate and yield each point as a 2-tuple C{(index, L{Point3Tuple})}.
359
+
360
+ @kwarg closed: Loop back to the first B{C{point(s)}}, de-dup'ed (C{bool}).
361
+ @kwarg copies: Save a copy of all B{C{points}} (C{bool}).
362
+
363
+ @raise PointsError: Insufficient number of B{C{points}} or using
364
+ C{B{closed}=True} without B{C{loop}}-back.
365
+
366
+ @raise TypeError: Some B{C{points}} are not B{C{base}}-compatible.
367
+ '''
368
+ return PointsIter.enumerate(self, closed=closed, copies=copies)
369
+
370
+ def iterate(self, closed=False, copies=False): # PYCHOK signature
371
+ '''Iterate the B{C{points}} starting at index B{C{loop}} and
372
+ yield each as a L{Point3Tuple}C{(x, y, ll)}.
373
+
374
+ @kwarg closed: Loop back to the first B{C{point(s)}}, de-dup'ed (C{bool}).
375
+ @kwarg copies: Save a copy of all B{C{points}} (C{bool}).
376
+
377
+ @raise PointsError: Insufficient number of B{C{points}} or using
378
+ C{B{closed}=True} without B{C{loop}}-back.
379
+
380
+ @raise TypeError: Some B{C{points}} are not B{C{base}}-compatible.
381
+ '''
382
+ if self._deg2m not in (None, _1_0):
383
+ _p3 = self._point3Tuple
384
+ else:
385
+ def _p3(ll): # PYCHOK redef
386
+ return Point3Tuple(ll.lon, ll.lat, ll)
387
+
388
+ for ll in PointsIter.iterate(self, closed=closed, copies=copies):
389
+ yield _p3(ll)
390
+
391
+ def _point3Tuple(self, ll):
392
+ '''(INTERNAL) Create a L{Point3Tuple} for point B{C{ll}}.
393
+ '''
394
+ x, y = ll.lon, ll.lat # note, x, y = lon, lat
395
+ d = self._deg2m
396
+ if d: # convert degrees
397
+ x *= d
398
+ y *= d
399
+ return Point3Tuple(x, y, ll)
400
+
401
+
402
+ def _imdex2(closed, n): # PYCHOK by .clipy
403
+ '''(INTERNAL) Return first and second index of C{range(B{n})}.
404
+ '''
405
+ return (n-1, 0) if closed else (0, 1)
406
+
407
+
408
+ def isNumpy2(obj):
409
+ '''Check for a B{C{Numpy2LatLon}} points wrapper.
410
+
411
+ @arg obj: The object (any C{type}).
412
+
413
+ @return: C{True} if B{C{obj}} is a B{C{Numpy2LatLon}}
414
+ instance, C{False} otherwise.
415
+ '''
416
+ # isinstance(self, (Numpy2LatLon, ...))
417
+ return getattr(obj, isNumpy2.__name__, False)
418
+
419
+
420
+ def isPoints2(obj):
421
+ '''Check for a B{C{LatLon2psxy}} points wrapper.
422
+
423
+ @arg obj: The object (any C{type}).
424
+
425
+ @return: C{True} if B{C{obj}} is a B{C{LatLon2psxy}}
426
+ instance, C{False} otherwise.
427
+ '''
428
+ # isinstance(self, (LatLon2psxy, ...))
429
+ return getattr(obj, isPoints2.__name__, False)
430
+
431
+
432
+ def isTuple2(obj):
433
+ '''Check for a B{C{Tuple2LatLon}} points wrapper.
434
+
435
+ @arg obj: The object (any).
436
+
437
+ @return: C{True} if B{C{obj}} is a B{C{Tuple2LatLon}}
438
+ instance, C{False} otherwise.
439
+ '''
440
+ # isinstance(self, (Tuple2LatLon, ...))
441
+ return getattr(obj, isTuple2.__name__, False)
442
+
443
+
444
+ def iterNumpy2(obj):
445
+ '''Iterate over Numpy2 wrappers or other sequences exceeding
446
+ the threshold.
447
+
448
+ @arg obj: Points array, list, sequence, set, etc. (any).
449
+
450
+ @return: C{True} do, C{False} don't iterate.
451
+ '''
452
+ try:
453
+ return isNumpy2(obj) or len(obj) > _iterNumpy2len
454
+ except TypeError:
455
+ return False
456
+
457
+
458
+ def iterNumpy2over(n=None):
459
+ '''Get or set the L{iterNumpy2} threshold.
460
+
461
+ @kwarg n: Optional, new threshold (C{int}).
462
+
463
+ @return: Previous threshold (C{int}).
464
+
465
+ @raise ValueError: Invalid B{C{n}}.
466
+ '''
467
+ global _iterNumpy2len
468
+ p = _iterNumpy2len
469
+ if n is not None:
470
+ try:
471
+ i = int(n)
472
+ if i > 0:
473
+ _iterNumpy2len = i
474
+ else:
475
+ raise ValueError
476
+ except (TypeError, ValueError):
477
+ raise _ValueError(n=n)
478
+ return p
479
+
480
+
481
+ def points2(points, closed=True, base=None, Error=PointsError):
482
+ '''Check a path or polygon represented by points.
483
+
484
+ @arg points: The path or polygon points (C{LatLon}[])
485
+ @kwarg closed: Optionally, consider the polygon closed,
486
+ ignoring any duplicate or closing final
487
+ B{C{points}} (C{bool}).
488
+ @kwarg base: Optionally, check all B{C{points}} against
489
+ this base class, if C{None} don't check.
490
+ @kwarg Error: Exception to raise (C{ValueError}).
491
+
492
+ @return: A L{Points2Tuple}C{(number, points)} with the number
493
+ of points and the points C{list} or C{tuple}.
494
+
495
+ @raise PointsError: Insufficient number of B{C{points}}.
496
+
497
+ @raise TypeError: Some B{C{points}} are not B{C{base}}
498
+ compatible or composite B{C{points}}.
499
+ '''
500
+ if _MODS.booleans.isBoolean(points):
501
+ raise Error(points=points, txt=_composite_)
502
+
503
+ n, points = len2(points)
504
+
505
+ if closed:
506
+ # remove duplicate or closing final points
507
+ while n > 1 and points[n-1] in (points[0], points[n-2]):
508
+ n -= 1
509
+ # XXX following line is unneeded if points
510
+ # are always indexed as ... i in range(n)
511
+ points = points[:n] # XXX numpy.array slice is a view!
512
+
513
+ if n < (3 if closed else 1):
514
+ raise Error(points=n, txt=_too_(_few_))
515
+
516
+ if base and not (isNumpy2(points) or isTuple2(points)):
517
+ for i in range(n):
518
+ base.others(points[i], name=Fmt.SQUARE(points=i))
519
+
520
+ return Points2Tuple(n, points)
521
+
522
+
523
+ __all__ += _ALL_DOCS(_BaseIter)
524
+
525
+ # **) MIT License
526
+ #
527
+ # Copyright (C) 2016-2024 -- mrJean1 at Gmail -- All Rights Reserved.
528
+ #
529
+ # Permission is hereby granted, free of charge, to any person obtaining a
530
+ # copy of this software and associated documentation files (the "Software"),
531
+ # to deal in the Software without restriction, including without limitation
532
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
533
+ # and/or sell copies of the Software, and to permit persons to whom the
534
+ # Software is furnished to do so, subject to the following conditions:
535
+ #
536
+ # The above copyright notice and this permission notice shall be included
537
+ # in all copies or substantial portions of the Software.
538
+ #
539
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
540
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
541
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
542
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
543
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
544
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
545
+ # OTHER DEALINGS IN THE SOFTWARE.