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.
- PyGeodesy-24.3.24.dist-info/METADATA +272 -0
- PyGeodesy-24.3.24.dist-info/RECORD +115 -0
- PyGeodesy-24.3.24.dist-info/WHEEL +6 -0
- PyGeodesy-24.3.24.dist-info/top_level.txt +1 -0
- pygeodesy/LICENSE +21 -0
- pygeodesy/__init__.py +615 -0
- pygeodesy/__main__.py +103 -0
- pygeodesy/albers.py +867 -0
- pygeodesy/auxilats/_CX_4.py +218 -0
- pygeodesy/auxilats/_CX_6.py +314 -0
- pygeodesy/auxilats/_CX_8.py +475 -0
- pygeodesy/auxilats/__init__.py +54 -0
- pygeodesy/auxilats/__main__.py +86 -0
- pygeodesy/auxilats/auxAngle.py +548 -0
- pygeodesy/auxilats/auxDLat.py +302 -0
- pygeodesy/auxilats/auxDST.py +296 -0
- pygeodesy/auxilats/auxLat.py +848 -0
- pygeodesy/auxilats/auxily.py +272 -0
- pygeodesy/azimuthal.py +1150 -0
- pygeodesy/basics.py +892 -0
- pygeodesy/booleans.py +2031 -0
- pygeodesy/cartesianBase.py +1062 -0
- pygeodesy/clipy.py +704 -0
- pygeodesy/constants.py +516 -0
- pygeodesy/css.py +660 -0
- pygeodesy/datums.py +752 -0
- pygeodesy/deprecated/__init__.py +61 -0
- pygeodesy/deprecated/bases.py +40 -0
- pygeodesy/deprecated/classes.py +262 -0
- pygeodesy/deprecated/consterns.py +54 -0
- pygeodesy/deprecated/datum.py +40 -0
- pygeodesy/deprecated/functions.py +375 -0
- pygeodesy/deprecated/nvector.py +48 -0
- pygeodesy/deprecated/rhumbBase.py +32 -0
- pygeodesy/deprecated/rhumbaux.py +33 -0
- pygeodesy/deprecated/rhumbsolve.py +33 -0
- pygeodesy/deprecated/rhumbx.py +33 -0
- pygeodesy/dms.py +986 -0
- pygeodesy/ecef.py +1348 -0
- pygeodesy/elevations.py +279 -0
- pygeodesy/ellipsoidalBase.py +1224 -0
- pygeodesy/ellipsoidalBaseDI.py +913 -0
- pygeodesy/ellipsoidalExact.py +343 -0
- pygeodesy/ellipsoidalGeodSolve.py +343 -0
- pygeodesy/ellipsoidalKarney.py +403 -0
- pygeodesy/ellipsoidalNvector.py +685 -0
- pygeodesy/ellipsoidalVincenty.py +590 -0
- pygeodesy/ellipsoids.py +2476 -0
- pygeodesy/elliptic.py +1198 -0
- pygeodesy/epsg.py +243 -0
- pygeodesy/errors.py +804 -0
- pygeodesy/etm.py +1190 -0
- pygeodesy/fmath.py +1013 -0
- pygeodesy/formy.py +1818 -0
- pygeodesy/frechet.py +865 -0
- pygeodesy/fstats.py +760 -0
- pygeodesy/fsums.py +1898 -0
- pygeodesy/gars.py +358 -0
- pygeodesy/geodesicw.py +581 -0
- pygeodesy/geodesicx/_C4_24.py +1699 -0
- pygeodesy/geodesicx/_C4_27.py +2395 -0
- pygeodesy/geodesicx/_C4_30.py +3301 -0
- pygeodesy/geodesicx/__init__.py +48 -0
- pygeodesy/geodesicx/__main__.py +91 -0
- pygeodesy/geodesicx/gx.py +1382 -0
- pygeodesy/geodesicx/gxarea.py +535 -0
- pygeodesy/geodesicx/gxbases.py +154 -0
- pygeodesy/geodesicx/gxline.py +669 -0
- pygeodesy/geodsolve.py +426 -0
- pygeodesy/geohash.py +914 -0
- pygeodesy/geoids.py +1884 -0
- pygeodesy/hausdorff.py +892 -0
- pygeodesy/heights.py +1155 -0
- pygeodesy/interns.py +687 -0
- pygeodesy/iters.py +545 -0
- pygeodesy/karney.py +919 -0
- pygeodesy/ktm.py +633 -0
- pygeodesy/latlonBase.py +1766 -0
- pygeodesy/lazily.py +960 -0
- pygeodesy/lcc.py +684 -0
- pygeodesy/ltp.py +1107 -0
- pygeodesy/ltpTuples.py +1563 -0
- pygeodesy/mgrs.py +721 -0
- pygeodesy/named.py +1324 -0
- pygeodesy/namedTuples.py +683 -0
- pygeodesy/nvectorBase.py +695 -0
- pygeodesy/osgr.py +781 -0
- pygeodesy/points.py +1686 -0
- pygeodesy/props.py +628 -0
- pygeodesy/resections.py +1048 -0
- pygeodesy/rhumb/__init__.py +46 -0
- pygeodesy/rhumb/aux_.py +397 -0
- pygeodesy/rhumb/bases.py +1148 -0
- pygeodesy/rhumb/ekx.py +563 -0
- pygeodesy/rhumb/solve.py +572 -0
- pygeodesy/simplify.py +647 -0
- pygeodesy/solveBase.py +472 -0
- pygeodesy/sphericalBase.py +724 -0
- pygeodesy/sphericalNvector.py +1264 -0
- pygeodesy/sphericalTrigonometry.py +1447 -0
- pygeodesy/streprs.py +627 -0
- pygeodesy/trf.py +2079 -0
- pygeodesy/triaxials.py +1484 -0
- pygeodesy/units.py +969 -0
- pygeodesy/unitsBase.py +349 -0
- pygeodesy/ups.py +538 -0
- pygeodesy/utily.py +1231 -0
- pygeodesy/utm.py +762 -0
- pygeodesy/utmups.py +318 -0
- pygeodesy/utmupsBase.py +517 -0
- pygeodesy/vector2d.py +785 -0
- pygeodesy/vector3d.py +968 -0
- pygeodesy/vector3dBase.py +1049 -0
- pygeodesy/webmercator.py +383 -0
- 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.
|