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.