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/clipy.py ADDED
@@ -0,0 +1,704 @@
1
+
2
+ # -*- coding: utf-8 -*-
3
+
4
+ u'''Clip a path or polygon of C{LatLon} points against a rectangular box or
5
+ an arbitrary (convex) region.
6
+
7
+ Box clip functions L{clipCS4} I{Cohen-Sutherland} and L{clipLB6} I{Liang-Barsky},
8
+ region clip functions L{clipFHP4} I{Foster-Hormann-Popa}, L{clipGH4}
9
+ I{Greiner-Hormann} and L{clipSH} and L{clipSH3} I{Sutherland-Hodgeman}.
10
+ .
11
+ '''
12
+ # make sure int/int division yields float quotient, see .basics
13
+ from __future__ import division as _; del _ # PYCHOK semicolon
14
+
15
+ # from pygeodesy.basics import len2 # from .fmath
16
+ from pygeodesy.constants import EPS, _0_0, _1_0
17
+ from pygeodesy.errors import _AssertionError, ClipError, PointsError
18
+ from pygeodesy.fmath import fabs, len2
19
+ from pygeodesy.fsums import fsumf_, Property_RO
20
+ from pygeodesy.interns import NN, _clipid_, _convex_, _DOT_, _end_, _few_, \
21
+ _fi_, _height_, _i_, _invalid_, _j_, _lat_, \
22
+ _lon_, _near_, _not_, _points_, _start_, _too_
23
+ from pygeodesy.iters import _imdex2, points2
24
+ from pygeodesy.lazily import _ALL_LAZY, _ALL_MODS as _MODS
25
+ from pygeodesy.named import _Named, _NamedTuple, _Pass
26
+ from pygeodesy.points import areaOf, boundsOf, isconvex_, LatLon_
27
+ # from pygeodesy.props import Property_RO # from .fsums
28
+ from pygeodesy.units import Bool, FIx, HeightX, Lat, Lon, Number_
29
+
30
+ # from math import fabs # from .fmath
31
+
32
+ __all__ = _ALL_LAZY.clipy
33
+ __version__ = '23.05.15'
34
+
35
+ _fj_ = 'fj'
36
+ _original_ = 'original'
37
+
38
+
39
+ def _box4(lowerleft, upperight, name):
40
+ '''(INTERNAL) Get the clip box edges.
41
+
42
+ @see: Class C{_Box} in .ellipsoidalBaseDI.py.
43
+ '''
44
+ try:
45
+ yb, yt = lowerleft.lat, upperight.lat
46
+ xl, xr = lowerleft.lon, upperight.lon
47
+ if xl > xr or yb > yt:
48
+ raise ValueError(_invalid_)
49
+ except (AttributeError, TypeError, ValueError) as x:
50
+ raise ClipError(name, 2, (lowerleft, upperight), cause=x)
51
+ return xl, yb, xr, yt
52
+
53
+
54
+ def _4corners(corners):
55
+ '''(INTERNAL) Clip region or box.
56
+ '''
57
+ n, cs = len2(corners)
58
+ if n == 2: # make a box
59
+ yb, xl, yt, xr = boundsOf(cs, wrap=False)
60
+ cs = (LatLon_(yb, xl), LatLon_(yt, xl),
61
+ LatLon_(yt, xr), LatLon_(yb, xr))
62
+ return cs
63
+
64
+
65
+ def _eq(p1, p2, eps=EPS):
66
+ '''(INTERNAL) Check for near-equal points.
67
+ '''
68
+ return not _neq(p1, p2, eps)
69
+
70
+
71
+ def _neq(p1, p2, eps=EPS):
72
+ '''(INTERNAL) Check for not near-equal points.
73
+ '''
74
+ return fabs(p1.lat - p2.lat) > eps or \
75
+ fabs(p1.lon - p2.lon) > eps
76
+
77
+
78
+ def _pts2(points, closed, inull):
79
+ '''(INTERNAL) Get the points to clip as a list.
80
+ '''
81
+ if closed and inull:
82
+ n, pts = len2(points)
83
+ # only remove the final, closing point
84
+ if n > 1 and _eq(pts[n-1], pts[0]):
85
+ n -= 1
86
+ pts = pts[:n]
87
+ if n < 2:
88
+ raise PointsError(points=n, txt=_too_(_few_))
89
+ else:
90
+ n, pts = points2(points, closed=closed)
91
+ return n, list(pts)
92
+
93
+
94
+ class _CS(_Named):
95
+ '''(INTERNAL) Cohen-Sutherland line clipping.
96
+ '''
97
+ # single-bit clip codes
98
+ _IN = 0 # inside clip box
99
+ _XR = 1 # right of upperight.lon
100
+ _XL = 2 # left of lowerleft.lon
101
+ _YT = 4 # above upperight.lat
102
+ _YB = 8 # below lowerleft.lat
103
+
104
+ _dx = _0_0 # pts edge delta lon
105
+ _dy = _0_0 # pts edge delta lat
106
+ _x1 = _0_0 # pts corner
107
+ _y1 = _0_0 # pts corner
108
+
109
+ _xr = _0_0 # clip box upperight.lon
110
+ _xl = _0_0 # clip box lowerleft.lon
111
+ _yt = _0_0 # clip box upperight.lat
112
+ _yb = _0_0 # clip box lowerleft.lat
113
+
114
+ def __init__(self, lowerleft, upperight, name=__name__):
115
+ self._xl, self._yb, \
116
+ self._xr, self._yt = _box4(lowerleft, upperight, name)
117
+ self.name = name
118
+
119
+ # def clip4(self, p, c): # clip point p for code c
120
+ # if c & _CS._YB:
121
+ # return self.lon4(p, self._yb)
122
+ # elif c & _CS._YT:
123
+ # return self.lon4(p, self._yt)
124
+ # elif c & _CS._XL:
125
+ # return self.lat4(p, self._xl)
126
+ # elif c & _CS._XR:
127
+ # return self.lat4(p, self._xr)
128
+ # # should never get here
129
+ # raise _AssertionError(self._DOT_(self.clip4.__name__))
130
+
131
+ def code4(self, p): # compute code for point p
132
+ if p.lat < self._yb:
133
+ c, m, b = _CS._YB, self.lon4, self._yb
134
+ elif p.lat > self._yt:
135
+ c, m, b = _CS._YT, self.lon4, self._yt
136
+ else:
137
+ c, m, b = _CS._IN, self.nop4, None
138
+ if p.lon < self._xl:
139
+ c |= _CS._XL
140
+ m, b = self.lat4, self._xl
141
+ elif p.lon > self._xr:
142
+ c |= _CS._XR
143
+ m, b = self.lat4, self._xr
144
+ return c, m, b, p
145
+
146
+ def edge(self, p1, p2): # set edge p1 to p2
147
+ self._y1, self._dy = p1.lat, float(p2.lat - p1.lat)
148
+ self._x1, self._dx = p1.lon, float(p2.lon - p1.lon)
149
+ return fabs(self._dx) > EPS or fabs(self._dy) > EPS
150
+
151
+ def lat4(self, x, p): # new lat and code at lon x
152
+ y = self._y1 + self._dy * float(x - self._x1) / self._dx
153
+ if y < self._yb: # still outside
154
+ return _CS._YB, self.lon4, self._yb, p
155
+ elif y > self._yt: # still outside
156
+ return _CS._YT, self.lon4, self._yt, p
157
+ else: # inside
158
+ return _CS._IN, self.nop4, None, p.classof(y, x)
159
+
160
+ def lon4(self, y, p): # new lon and code at lat y
161
+ x = self._x1 + self._dx * float(y - self._y1) / self._dy
162
+ if x < self._xl: # still outside
163
+ return _CS._XL, self.lat4, self._xl, p
164
+ elif x > self._xr: # still outside
165
+ return _CS._XR, self.lat4, self._xr, p
166
+ else: # inside
167
+ return _CS._IN, self.nop4, None, p.classof(y, x)
168
+
169
+ def nop4(self, b, p): # PYCHOK no cover
170
+ if p: # should never get here
171
+ raise _AssertionError(self._DOT_(self.nop4.__name__))
172
+ return _CS._IN, self.nop4, b, p
173
+
174
+
175
+ class ClipCS4Tuple(_NamedTuple):
176
+ '''4-Tuple C{(start, end, i, j)} for each edge of a I{clipped}
177
+ path with the C{start} and C{end} points (C{LatLon}) of the
178
+ portion of the edge inside or on the clip box and the indices
179
+ C{i} and C{j} (C{int}) of the edge start and end points in
180
+ the original path.
181
+ '''
182
+ _Names_ = (_start_, _end_, _i_, _j_)
183
+ _Units_ = (_Pass, _Pass, Number_, Number_)
184
+
185
+
186
+ def clipCS4(points, lowerleft, upperight, closed=False, inull=False):
187
+ '''Clip a path against a rectangular clip box using the U{Cohen-Sutherland
188
+ <https://WikiPedia.org/wiki/Cohen-Sutherland_algorithm>} algorithm.
189
+
190
+ @arg points: The points (C{LatLon}[]).
191
+ @arg lowerleft: Bottom-left corner of the clip box (C{LatLon}).
192
+ @arg upperight: Top-right corner of the clip box (C{LatLon}).
193
+ @kwarg closed: Optionally, close the path (C{bool}).
194
+ @kwarg inull: Optionally, retain null edges if inside (C{bool}).
195
+
196
+ @return: Yield a L{ClipCS4Tuple}C{(start, end, i, j)} for each
197
+ edge of the I{clipped} path.
198
+
199
+ @raise ClipError: The B{C{lowerleft}} and B{C{upperight}} corners
200
+ specify an invalid clip box.
201
+
202
+ @raise PointsError: Insufficient number of B{C{points}}.
203
+ '''
204
+ T4 = ClipCS4Tuple
205
+ cs = _CS(lowerleft, upperight, name=clipCS4.__name__)
206
+ n, pts = _pts2(points, closed, inull)
207
+
208
+ i, m = _imdex2(closed, n)
209
+ cmbp = cs.code4(pts[i])
210
+ for j in range(m, n):
211
+ c1, m1, b1, p1 = cmbp
212
+ c2, m2, b2, p2 = cmbp = cs.code4(pts[j])
213
+ if c1 & c2: # edge outside
214
+ pass
215
+ elif cs.edge(p1, p2):
216
+ for _ in range(5):
217
+ if c1: # clip p1
218
+ c1, m1, b1, p1 = m1(b1, p1)
219
+ elif c2: # clip p2
220
+ c2, m2, b2, p2 = m2(b2, p2)
221
+ else: # inside
222
+ if inull or _neq(p1, p2):
223
+ yield T4(p1, p2, i, j)
224
+ break
225
+ if c1 & c2: # edge outside
226
+ break
227
+ else: # PYCHOK no cover
228
+ raise _AssertionError(_DOT_(cs.name, 'for_else'))
229
+
230
+ elif inull and not c1: # null edge
231
+ yield T4(p1, p1, i, j)
232
+ elif inull and not c2:
233
+ yield T4(p2, p2, i, j)
234
+
235
+ i = j
236
+
237
+
238
+ class ClipFHP4Tuple(_NamedTuple):
239
+ '''4-Tuple C{(lat, lon, height, clipid)} for each point of the
240
+ L{clipFHP4} result with the C{lat}-, C{lon}gitude, C{height}
241
+ and C{clipid} of the polygon or clip.
242
+
243
+ @note: The C{height} is a L{HeightX} instance if this point is
244
+ an intersection, otherwise a L{Height} or C{int(0)}.
245
+ '''
246
+ _Names_ = (_lat_, _lon_, _height_, _clipid_)
247
+ _Units_ = ( Lat, Lon, _Pass, Number_)
248
+
249
+ @Property_RO
250
+ def isintersection(self):
251
+ '''Is this an intersection?
252
+ '''
253
+ return isinstance(self.height, HeightX)
254
+
255
+ @Property_RO
256
+ def ispoint(self):
257
+ '''Is this an original (polygon) point?
258
+ '''
259
+ return not self.isintersection
260
+
261
+
262
+ def clipFHP4(points, corners, closed=False, inull=False, raiser=False, eps=EPS):
263
+ '''Clip one or more polygons against a clip region or box using U{Forster-Hormann-Popa
264
+ <https://www.ScienceDirect.com/science/article/pii/S259014861930007X>}'s C++
265
+ implementation transcoded to pure Python.
266
+
267
+ @arg points: The polygon points and clips (C{LatLon}[]).
268
+ @arg corners: Three or more points defining the clip regions (C{LatLon}[])
269
+ or two points to specify a single, rectangular clip box.
270
+ @kwarg closed: If C{True}, close each result clip (C{bool}).
271
+ @kwarg inull: If C{True}, retain null edges in result clips (C{bool}).
272
+ @kwarg raiser: If C{True}, throw L{ClipError} exceptions (C{bool}).
273
+ @kwarg esp: Tolerance for eliminating null edges (C{degrees}, same units
274
+ as the B{C{points}} and B{C{corners}} coordinates).
275
+
276
+ @return: Yield a L{ClipFHP4Tuple}C{(lat, lon, height, clipid)} for each
277
+ clipped point. The result may consist of several clips, each
278
+ a (closed) polygon with a unique C{clipid}.
279
+
280
+ @raise ClipError: Insufficient B{C{points}} or B{C{corners}} or an open clip.
281
+
282
+ @see: U{Forster, Hormann and Popa<https://www.ScienceDirect.com/science/
283
+ article/pii/S259014861930007X>}, class L{BooleanFHP} and function
284
+ L{clipGH4}.
285
+ '''
286
+ P = _MODS.booleans._CompositeFHP(points, kind=_points_, raiser=raiser,
287
+ name=clipFHP4.__name__, eps=eps)
288
+ Q = _4corners(corners)
289
+ return P._clip(Q, Union=False, Clas=ClipFHP4Tuple, closed=closed,
290
+ inull=inull, raiser=P._raiser, eps=eps)
291
+
292
+
293
+ class ClipGH4Tuple(ClipFHP4Tuple):
294
+ '''4-Tuple C{(lat, lon, height, clipid)} for each point of the
295
+ L{clipGH4} result with the C{lat}-, C{lon}gitude, C{height}
296
+ and C{clipid} of the polygon or clip.
297
+
298
+ @note: The C{height} is a L{HeightX} instance if this is
299
+ an intersection, otherwise a L{Height} or C{int(0)}.
300
+ '''
301
+ _Names_ = ClipFHP4Tuple._Names_
302
+ _Units_ = ClipFHP4Tuple._Units_
303
+
304
+
305
+ def clipGH4(points, corners, closed=False, inull=False, raiser=True, xtend=False, eps=EPS):
306
+ '''Clip one or more polygons against a clip region or box using the U{Greiner-Hormann
307
+ <http://www.Inf.USI.CH/hormann/papers/Greiner.1998.ECO.pdf>} algorithm, U{Correia
308
+ <https://GitHub.com/helderco/univ-polyclip>}'s implementation modified and extended.
309
+
310
+ @arg points: The polygon points and clips (C{LatLon}[]).
311
+ @arg corners: Three or more points defining the clip regions (C{LatLon}[])
312
+ or two points to specify a single, rectangular clip box.
313
+ @kwarg closed: If C{True}, close each result clip (C{bool}).
314
+ @kwarg inull: If C{True}, retain null edges in result clips (C{bool}).
315
+ @kwarg raiser: If C{True}, throw L{ClipError} exceptions (C{bool}).
316
+ @kwarg xtend: If C{True}, extend edges of I{degenerate cases}, an attempt
317
+ to handle the latter (C{bool}).
318
+ @kwarg esp: Tolerance for eliminating null edges (C{degrees}, same units
319
+ as the B{C{points}} and B{C{corners}} coordinates).
320
+
321
+ @return: Yield a L{ClipGH4Tuple}C{(lat, lon, height, clipid)} for each
322
+ clipped point. The result may consist of several clips, each
323
+ a (closed) polygon with a unique C{clipid}.
324
+
325
+ @raise ClipError: Insufficient B{C{points}} or B{C{corners}}, an open clip,
326
+ a I{degenerate case} or I{unhandled} intersection.
327
+
328
+ @note: To handle I{degenerate cases} like C{point-edge} and C{point-point}
329
+ intersections, use function L{clipFHP4}.
330
+
331
+ @see: U{Greiner-Hormann<https://WikiPedia.org/wiki/Greiner–Hormann_clipping_algorithm>},
332
+ U{Ionel Daniel Stroe<https://Davis.WPI.edu/~matt/courses/clipping/>}, I{Correia}'s
333
+ U{univ-polyclip<https://GitHub.com/helderco/univ-polyclip>}, class L{BooleanGH}
334
+ and function L{clipFHP4}.
335
+ '''
336
+ S = _MODS.booleans._CompositeGH(points, raiser=raiser, xtend=xtend, eps=eps,
337
+ name=clipGH4.__name__, kind=_points_)
338
+ C = _4corners(corners)
339
+ return S._clip(C, False, False, Clas=ClipGH4Tuple, closed=closed, inull=inull,
340
+ raiser=S._raiser, xtend=S._xtend, eps=eps)
341
+
342
+
343
+ def _LBtrim(p, q, t):
344
+ # Liang-Barsky trim t[0] or t[1]
345
+ if p < 0:
346
+ r = q / p
347
+ if r > t[1]:
348
+ return False # too far above
349
+ elif r > t[0]:
350
+ t[0] = r
351
+ elif p > 0:
352
+ r = q / p
353
+ if r < t[0]:
354
+ return False # too far below
355
+ elif r < t[1]:
356
+ t[1] = r
357
+ elif q < 0: # vertical or horizontal
358
+ return False # ... outside
359
+ return True
360
+
361
+
362
+ class ClipLB6Tuple(_NamedTuple):
363
+ '''6-Tuple C{(start, end, i, fi, fj, j)} for each edge of the
364
+ I{clipped} path with the C{start} and C{end} points (C{LatLon})
365
+ of the portion of the edge inside or on the clip box, indices
366
+ C{i} and C{j} (both C{int}) of the original path edge start
367
+ and end points and I{fractional} indices C{fi} and C{fj}
368
+ (both L{FIx}) of the C{start} and C{end} points along the
369
+ edge of the original path.
370
+
371
+ @see: Class L{FIx} and function L{pygeodesy.fractional}.
372
+ '''
373
+ _Names_ = (_start_, _end_, _i_, _fi_, _fj_, _j_)
374
+ _Units_ = (_Pass, _Pass, Number_, _Pass, _Pass, Number_)
375
+
376
+
377
+ def clipLB6(points, lowerleft, upperight, closed=False, inull=False):
378
+ '''Clip a path against a rectangular clip box using the U{Liang-Barsky
379
+ <https://www.CSE.UNT.edu/~renka/4230/LineClipping.pdf>} algorithm.
380
+
381
+ @arg points: The points (C{LatLon}[]).
382
+ @arg lowerleft: Bottom-left corner of the clip box (C{LatLon}).
383
+ @arg upperight: Top-right corner of the clip box (C{LatLon}).
384
+ @kwarg closed: Optionally, close the path (C{bool}).
385
+ @kwarg inull: Optionally, retain null edges if inside (C{bool}).
386
+
387
+ @return: Yield a L{ClipLB6Tuple}C{(start, end, i, fi, fj, j)} for
388
+ each edge of the I{clipped} path.
389
+
390
+ @raise ClipError: The B{C{lowerleft}} and B{C{upperight}} corners
391
+ specify an invalid clip box.
392
+
393
+ @raise PointsError: Insufficient number of B{C{points}}.
394
+
395
+ @see: U{Liang-Barsky Line Clipping<https://www.CS.Helsinki.FI/group/goa/
396
+ viewing/leikkaus/intro.html>}, U{Liang-Barsky line clipping algorithm
397
+ <https://www.Skytopia.com/project/articles/compsci/clipping.html>} and
398
+ U{Liang-Barsky algorithm<https://WikiPedia.org/wiki/Liang-Barsky_algorithm>}.
399
+ '''
400
+ xl, yb, \
401
+ xr, yt = _box4(lowerleft, upperight, clipLB6.__name__)
402
+ n, pts = _pts2(points, closed, inull)
403
+
404
+ T6 = ClipLB6Tuple
405
+ fin = n if closed else None # wrapping fi [n] to [0]
406
+ _LB = _LBtrim
407
+
408
+ i, m = _imdex2(closed, n)
409
+ for j in range(m, n):
410
+ p1 = pts[i]
411
+ y1 = p1.lat
412
+ x1 = p1.lon
413
+
414
+ p2 = pts[j]
415
+ dy = float(p2.lat - y1)
416
+ dx = float(p2.lon - x1)
417
+ if fabs(dx) > EPS or fabs(dy) > EPS:
418
+ # non-null edge pts[i]...pts[j]
419
+ t = [_0_0, _1_0]
420
+ if _LB(-dx, -xl + x1, t) and \
421
+ _LB( dx, xr - x1, t) and \
422
+ _LB(-dy, -yb + y1, t) and \
423
+ _LB( dy, yt - y1, t):
424
+ # clip edge pts[i]...pts[j]
425
+ # at fractions t[0] to t[1]
426
+ f, t = t
427
+ if f > _0_0: # EPS
428
+ p1 = p1.classof(y1 + f * dy,
429
+ x1 + f * dx)
430
+ fi = i + f
431
+ else:
432
+ fi = i
433
+
434
+ if (t - f) > EPS: # EPS0
435
+ if t < _1_0: # EPS1
436
+ p2 = p2.classof(y1 + t * dy,
437
+ x1 + t * dx)
438
+ fj = i + t
439
+ else:
440
+ fj = j
441
+ fi = FIx(fi, fin=fin)
442
+ fj = FIx(fj, fin=fin)
443
+ yield T6(p1, p2, i, fi, fj, j)
444
+
445
+ elif inull:
446
+ fi = FIx(fi, fin=fin)
447
+ yield T6(p1, p1, i, fi, fi, j)
448
+ # else: # outside
449
+ # pass
450
+ elif inull: # null edge
451
+ yield T6(p1, p2, i, FIx(i, fin=fin),
452
+ FIx(j, fin=fin), j)
453
+ i = j
454
+
455
+
456
+ class _SH(_Named):
457
+ '''(INTERNAL) Sutherland-Hodgman polyon clipping.
458
+ '''
459
+ _cs = () # clip corners
460
+ _cw = 0 # counter-/clockwise
461
+ _ccw = 0 # clock-/counterwise
462
+ _dx = _0_0 # clip edge[e] delta lon
463
+ _dy = _0_0 # clip edge[e] delta lat
464
+ _nc = 0 # len(._cs)
465
+ _x1 = _0_0 # clip edge[e] lon origin
466
+ _xy = _0_0 # see .clipedges
467
+ _y1 = _0_0 # clip edge[e] lat origin
468
+
469
+ def __init__(self, corners, name=__name__):
470
+ n, cs = 0, corners
471
+ try: # check the clip box/region
472
+ cs = _4corners(cs)
473
+ n, cs = len2(cs)
474
+ n, cs = points2(cs, closed=True)
475
+ self._cs = cs = cs[:n]
476
+ self._nc = n
477
+ self._cw = isconvex_(cs, adjust=False, wrap=False)
478
+ if not self._cw:
479
+ raise ValueError(_not_(_convex_))
480
+ if areaOf(cs, adjust=True, radius=1, wrap=True) < EPS:
481
+ raise ValueError(NN(_near_, 'zero area'))
482
+ self._ccw = -self._cw
483
+ except (PointsError, TypeError, ValueError) as x:
484
+ raise ClipError(name, n, cs, cause=x)
485
+ self.name = name
486
+
487
+ def clip2(self, points, closed, inull): # MCCABE 13, clip points
488
+ np, pts = _pts2(points, closed, inull)
489
+ pcs = _SHlist(inull) # clipped points
490
+ _ap = pcs.append
491
+ _d2 = self.dot2
492
+ _in = self.intersect
493
+
494
+ ne = 0 # number of non-null clip edges
495
+ for e in self.clipedges():
496
+ ne += 1 # non-null clip edge
497
+
498
+ # clip points, closed always
499
+ d1, p1 = _d2(pts[np - 1])
500
+ for i in range(np):
501
+ d2, p2 = _d2(pts[i])
502
+ if d1 < 0: # p1 inside, p2 ...
503
+ # _ap(p1)
504
+ _ap(p2 if d2 < 0 else # ... in-
505
+ _in(p1, p2, e)) # ... outside
506
+ elif d2 < 0: # p1 out-, p2 inside
507
+ _ap(_in(p1, p2, e))
508
+ _ap(p2)
509
+ # elif d1 > 0: # both outside
510
+ # pass
511
+ d1, p1 = d2, p2
512
+
513
+ # replace points, in-place
514
+ pts[:] = pcs
515
+ pcs[:] = []
516
+ np = len(pts)
517
+ if not np: # all outside
518
+ break
519
+ else:
520
+ if ne < 3:
521
+ raise ClipError(self.name, ne, self._cs, txt=_too_(_few_))
522
+
523
+ if np > 1:
524
+ p = pts[0]
525
+ if closed: # close clipped pts
526
+ if _neq(pts[np - 1], p):
527
+ pts.append(p)
528
+ np += 1
529
+ elif not inull: # open clipped pts
530
+ while np > 0 and _eq(pts[np - 1], p):
531
+ pts.pop()
532
+ np -= 1
533
+ # assert len(pts) == np
534
+ return np, pts
535
+
536
+ def clipedges(self): # yield clip edge index
537
+ # and set self._x1, ._y1, ._dx, ._dy and
538
+ # ._xy for each non-null clip edge
539
+ nc = self._nc
540
+ cs = self._cs
541
+ c = cs[nc - 1]
542
+ for e in range(nc):
543
+ y, x, c = c.lat, c.lon, cs[e]
544
+ dy = float(c.lat - y)
545
+ dx = float(c.lon - x)
546
+ if fabs(dx) > EPS or fabs(dy) > EPS:
547
+ self._y1, self._dy = y, dy
548
+ self._x1, self._dx = x, dx
549
+ self._xy = y * dx - x * dy
550
+ yield e + 1
551
+
552
+ def clipped2(self, p): # return (clipped point [i], edge)
553
+ if isinstance(p, _SHlli): # intersection point
554
+ return p.classof(p.lat, p.lon), p.edge
555
+ else: # original point
556
+ return p, 0
557
+
558
+ def dot2(self, p): # dot product of point p to clip
559
+ # corner c1 and clip edge c1 to c2, indicating where
560
+ # point p is located: to the right, to the left or
561
+ # on top of the (extended) clip edge from c1 to c2
562
+ d = float(p.lat - self._y1) * self._dx - \
563
+ float(p.lon - self._x1) * self._dy
564
+ # clockwise corners, +1 means point p is to the right
565
+ # of, -1 means on the left of, 0 means on edge c1 to c2
566
+ d = self._ccw if d < 0 else (self._cw if d > 0 else 0)
567
+ return d, p
568
+
569
+ def intersect(self, p1, p2, edge): # compute intersection
570
+ # of polygon edge p1 to p2 and the current clip edge,
571
+ # where p1 and p2 are known to NOT be located on the
572
+ # same side of or on the current, non-null clip edge
573
+ # <https://StackOverflow.com/questions/563198/
574
+ # how-do-you-detect-where-two-line-segments-intersect>
575
+ y, dy = p1.lat, self._dy
576
+ x, dx = p1.lon, self._dx
577
+ fy = float(p2.lat - y)
578
+ fx = float(p2.lon - x)
579
+ d = fy * dx - fx * dy
580
+ if fabs(d) < EPS: # PYCHOK no cover
581
+ raise _AssertionError(self._DOT_(self.intersect.__name__))
582
+ d = fsumf_(self._xy, -y * dx, x * dy) / d
583
+ y += d * fy
584
+ x += d * fx
585
+ return _SHlli(y, x, p1.classof, edge)
586
+
587
+
588
+ class _SHlist(list):
589
+ '''(INTERNAL) List of _SH clipped points.
590
+ '''
591
+ _inull = False
592
+
593
+ def __init__(self, inull):
594
+ self._inull = inull
595
+ list.__init__(self)
596
+
597
+ def append(self, p):
598
+ if (not self) or self._inull or _neq(p, self[-1]):
599
+ list.append(self, p)
600
+
601
+
602
+ class _SHlli(LatLon_):
603
+ '''(INTERNAL) LatLon_ for _SH intersections.
604
+ '''
605
+ # __slots__ are no longer space savers, see
606
+ # the comments at the class .points.LatLon_
607
+ # __slots__ = _lat_, _lon_, 'classof', 'edge', _name_
608
+
609
+ def __init__(self, lat, lon, classof, edge):
610
+ self.lat = lat
611
+ self.lon = lon
612
+ self.classof = classof
613
+ self.edge = edge # clip edge
614
+ self.name = NN
615
+
616
+
617
+ class ClipSH3Tuple(_NamedTuple):
618
+ '''3-Tuple C{(start, end, original)} for each edge of a I{clipped}
619
+ polygon, the C{start} and C{end} points (C{LatLon}) of the
620
+ portion of the edge inside or on the clip region and C{original}
621
+ indicates whether the edge is part of the original polygon or
622
+ part of the clip region (C{bool}).
623
+ '''
624
+ _Names_ = (_start_, _end_, _original_)
625
+ _Units_ = (_Pass, _Pass, Bool)
626
+
627
+
628
+ def clipSH(points, corners, closed=False, inull=False):
629
+ '''Clip a polygon against a clip region or box using the U{Sutherland-Hodgman
630
+ <https://WikiPedia.org/wiki/Sutherland-Hodgman_algorithm>} algorithm.
631
+
632
+ @arg points: The polygon points (C{LatLon}[]).
633
+ @arg corners: Three or more points defining a convex clip
634
+ region (C{LatLon}[]) or two points to specify
635
+ a rectangular clip box.
636
+ @kwarg closed: Close the clipped points (C{bool}).
637
+ @kwarg inull: Optionally, include null edges (C{bool}).
638
+
639
+ @return: Yield the clipped points (C{LatLon}[]).
640
+
641
+ @raise ClipError: The B{C{corners}} specify a polar, zero-area,
642
+ non-convex or otherwise invalid clip box or
643
+ region.
644
+
645
+ @raise PointsError: Insufficient number of B{C{points}}.
646
+ '''
647
+ sh = _SH(corners, name=clipSH.__name__)
648
+ n, pts = sh.clip2(points, closed, inull)
649
+ for i in range(n):
650
+ p, _ = sh.clipped2(pts[i])
651
+ yield p
652
+
653
+
654
+ def clipSH3(points, corners, closed=False, inull=False):
655
+ '''Clip a polygon against a clip region or box using the U{Sutherland-Hodgman
656
+ <https://WikiPedia.org/wiki/Sutherland-Hodgman_algorithm>} algorithm.
657
+
658
+ @arg points: The polygon points (C{LatLon}[]).
659
+ @arg corners: Three or more points defining a convex clip
660
+ region (C{LatLon}[]) or two points to specify
661
+ a rectangular clip box.
662
+ @kwarg closed: Close the clipped points (C{bool}).
663
+ @kwarg inull: Optionally, include null edges (C{bool}).
664
+
665
+ @return: Yield a L{ClipSH3Tuple}C{(start, end, original)} for
666
+ each edge of the I{clipped} polygon.
667
+
668
+ @raise ClipError: The B{C{corners}} specify a polar, zero-area,
669
+ non-convex or otherwise invalid clip box or
670
+ region.
671
+
672
+ @raise PointsError: Insufficient number of B{C{points}} or B{C{corners}}.
673
+ '''
674
+ sh = _SH(corners, name=clipSH3.__name__)
675
+ n, pts = sh.clip2(points, closed, inull)
676
+ if n > 1:
677
+ T3 = ClipSH3Tuple
678
+ p1, e1 = sh.clipped2(pts[0])
679
+ for i in range(1, n):
680
+ p2, e2 = sh.clipped2(pts[i])
681
+ yield T3(p1, p2, not bool(e1 and e2 and e1 == e2))
682
+ p1, e1 = p2, e2
683
+
684
+ # **) MIT License
685
+ #
686
+ # Copyright (C) 2018-2024 -- mrJean1 at Gmail -- All Rights Reserved.
687
+ #
688
+ # Permission is hereby granted, free of charge, to any person obtaining a
689
+ # copy of this software and associated documentation files (the "Software"),
690
+ # to deal in the Software without restriction, including without limitation
691
+ # the rights to use, copy, modify, merge, publish, distribute, sublicense,
692
+ # and/or sell copies of the Software, and to permit persons to whom the
693
+ # Software is furnished to do so, subject to the following conditions:
694
+ #
695
+ # The above copyright notice and this permission notice shall be included
696
+ # in all copies or substantial portions of the Software.
697
+ #
698
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
699
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
700
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
701
+ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
702
+ # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
703
+ # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
704
+ # OTHER DEALINGS IN THE SOFTWARE.