Coverage for pygeodesy/booleans.py : 90%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*-
Classes L{BooleanFHP} and L{BooleanGH} are I{composites} and provide I{boolean} operations C{intersection}, C{difference}, C{reverse-difference}, C{sum} and C{union}.
@note: A I{clip} is defined as a single, usually closed polygon, a I{composite} is a collection of one or more I{clip}s.
@see: U{Forster-Hormann-Popa<https://www.ScienceDirect.com/science/ article/pii/S259014861930007X>} and U{Greiner-Hormann <http://www.Inf.USI.CH/hormann/papers/Greiner.1998.ECO.pdf>}. ''' # make sure int/int division yields float quotient, see .basics
_0_0, _0_5, _1_0 _xkwds_get _e_, _ELLIPSIS_, _few_, _height_, _lat_, \ _lon_, _name_, _not_, _scalar_, _SPACE_, \ _too_, _X_, _x_, _B_, _d_, _R_ # PYCHOK used! # from pygeodesy.latlonBase import LatLonBase # from _MODS # from pygeodesy.points import boundsOf # from _MODS # from pygeodesy.streprs import Fmt, pairs, unstr # from .named
# from math import fabs # from .fmath
# Intersection labels # Entry/Exit flags EXIT: ENTRY, None: None}
# RelativePosition types
(_RP.RIGHT, _RP.LEFT): _L.CROSSING, (_RP.LEFT, _RP.LEFT): _L.BOUNCING, (_RP.RIGHT, _RP.RIGHT): _L.BOUNCING, # overlapping cases (_RP.RIGHT, _RP.IS_Pp): _L.LEFT_ON, (_RP.IS_Pp, _RP.RIGHT): _L.LEFT_ON, (_RP.LEFT, _RP.IS_Pp): _L.RIGHT_ON, (_RP.IS_Pp, _RP.LEFT): _L.RIGHT_ON, (_RP.IS_Pm, _RP.IS_Pp): _L.ON_ON, (_RP.IS_Pp, _RP.IS_Pm): _L.ON_ON, (_RP.IS_Pm, _RP.RIGHT): _L.ON_LEFT, (_RP.RIGHT, _RP.IS_Pm): _L.ON_LEFT, (_RP.LEFT, _RP.IS_Pm): _L.ON_RIGHT, (_RP.IS_Pm, _RP.LEFT): _L.ON_RIGHT}
# Return C{alpha} in C{[0..1]} range if _EPS_0 < alpha < _1_EPS: return max(_0_0, min(alpha, _1_0)) t = _not_(Fmt.SQUARE(_ELLIPSIS_(0, 1))) raise ClipError(_alpha_, alpha, txt=t)
# Return 4-tuple (alpha, abs(alpha) near 0, 0 < alpha < 1, not 0 < alpha < 1) (a, False, False, True) if _0_EPS < fabs(a) else (a, True, False, False))
'''(INTERNAL) Return 2-tuple C{(left, right), (bottom, top)}, oversized. ''' _min_max_eps(p1.y, p2.y))
'''(INTERNAL) Return 2-tuple C{(min, max)}, oversized. '''
'''(INTERNAL) Check for compatible C{type}s. ''' raise _IsnotError(C.__name__, other=other)
'''(INTERNAL) Is C{(x1, x2)} outside C{(lo, hi)}? '''
'''(INTERNAL) Base class for L{LatLonFHP} and L{LatLonGH}. ''' # _e_x_str = NN # shut up PyChecker
'''New C{LatLon[FHP|GH]} from separate C{lat}, C{lon}, C{height} and C{clipid} scalars or from a previous C{LatLon[FHP|GH]}, a C{Clip[FHP|GH]4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (C{LatLon[FHP|GH]}, aC{Clip[FHP|GH]4Tuple} or some other C{LatLon}). @kwarg lon: Longitude (C{scalar}), iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg height: Height (C{scalar}), conventionally C{meter}. @kwarg clipid: Clip identifier (C{int}). @kwarg name: Optional name (C{str}). ''' else: # don't duplicate defaults self.name = name
other.x == self.x and other.y == self.y)
'''String C{repr} of this lat-/longitude. ''' t = _ELLIPSIS_(self._prev, self._next) t = _SPACE_(self, Fmt.ANGLE(t)) else:
'''String C{str} of this lat-/longitude. ''' t += (_alpha_, self._alpha), # if self._dupof: # recursion risk # t += (_dupof_, self._dupof.name), k = _DOT_ if self._checked else _BANG_ t = NN(t, self._e_x_str(k)) # PYCHOK expected
self.x - other.x)
# I{Signed} area of a triangle, I{doubled}. (p3.x - x) * (p2.y - y)
# I{Unsigned} area of a triangle, I{doubled} # or 0 if below the given threshold C{eps}.
'''Get the I{clipid} (C{int} or C{0}). '''
'''Get the I{height} (C{Height} or C{int}). ''' Height(h) if h else _LatLonBool._height)
'''Is this an intersection? '''
'''Is this an original (boolean) point? '''
'''Get the latitude (C{scalar}). '''
# Make this and an other point are neighbors. # assert _other(self, other)
'''Get the longitude (C{scalar}). '''
# Return this vertex as a C{Clas} instance # (L{Clip[FHP|GH]4Tuple} or L{LatLon[FHP|GH]}).
'''A point or intersection in a L{BooleanFHP} clip or composite. '''
'''New C{LatLonFHP} from separate C{lat}, C{lon}, C{h}eight and C{clipid} scalars, or from a previous L{LatLonFHP}, a L{ClipFHP4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (L{LatLonFHP}, C{LatLon} or L{ClipFHP4Tuple}). @arg lon_h_clipid: Longitude (C{scalar}), C{h}eight and C{clipid} iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg name: Optional name (C{str}). '''
_other(self, other) return self.x * other.y - self.y * other.x
raise _IsnotError(_scalar_, other=other)
if self._label: t = NN(self._label, t) if self._en_ex: t = NN(t, self._en_ex) return t
# Is this point a I{duplicate} intersection? and p is not self and p == self # and p._alpha in (None, self._alpha) and self._alpha in (_0_0, p._alpha))
# @property_RO # def _isduplicated(self): # # Return the number of I{duplicates}? # d, v = 0, self # while v: # if v._dupof is self: # d += 1 # v = v._next # if v is self: # break # return d
# Is this point inside a composite, # excluding certain C{_Clip}s. # edge [p1,p2] must straddle y else:
'''Is this point inside B{{composites}} based on C{winding number}?
@arg composites: One or more iterables or I{composites} of clips or points (L{ClipFHP4Tuple}, L{ClipGH4Tuple}, L{LatLonFHP}, L{LatLonGH}, L{LatLon_} or any other C{LatLon}).
@see: U{Algorithm 6<https://www.ScienceDirect.com/science/ article/pii/S0925772101000128>}. ''' self._isinside(_CompositeEdges(self.__class__, composites))
'''Is this an intersection? May be C{ispoint} too! '''
'''Is this an I{original} point? May be C{isintersection} too! '''
# Adjust 2-tuple (._prev, ._next) iff a I{duplicate} intersection p = self._dupof while p._isduplicate: p = p._dupof while n._isduplicate: n = n._next
# def _edge2(self): # # Return the start and end point of the # # edge containing I{intersection} C{v}. # n = p = self # while p.isintersection: # p = p._prev # if p is self: # break # while n.isintersection: # n = n._next # if n is self: # break # # assert p == self or not p._2Abs(self, n) # return p, n
# Relative Position oracle self._2A(p2, p3) > 0 else \ _RP.RIGHT # PYCHOK indent else: # right turn (or straight) self._2A(p2, p3) < 0 else \ _RP.LEFT # PYCHOK indent
'''A point or intersection in a L{BooleanGH} clip or composite. '''
'''New C{LatLonGH} from separate C{lat}, C{lon}, C{h}eight and C{clipid} scalars, or from a previous L{LatLonGH}, L{ClipGH4Tuple} or some other C{LatLon} instance.
@arg lat_ll: Latitude (C{scalar}) or a lat/longitude (L{LatLonGH}, C{LatLon} or L{ClipGH4Tuple}). @arg lon_h_clipid: Longitude (C{scalar}), C{h}eight and C{clipid} iff B{C{lat_ll}} is scalar, ignored otherwise. @kwarg name: Optional name (C{str}). '''
# Check-mark this vertex and its link.
return t if self._entry is None else NN(t, (_e_ if self._entry else _x_))
# Is this vertex inside the composite? I{Odd-even rule}.
# The I{odd-even} rule counts the number of edges # intersecting a ray emitted East-bound from this # point to infinity. When I{odd} this point lies # inside, if I{even} outside. r = not r
'''Is this point inside B{C{composites}} based on C{odd-even rule}?
@arg composites: One or more iterables or I{composites} of clips or points (L{ClipFHP4Tuple}, L{ClipGH4Tuple}, L{LatLonFHP}, L{LatLonGH}, L{LatLon_} or any other C{LatLon}). ''' self._isinside(_CompositeEdges(self.__class__, composites))
'''(INTERNAL) A I{doubly-linked} list representing a I{closed} polygon of L{LatLonFHP} or L{LatLonGH} points, duplicates and intersections with other clips. '''
'''(INTERNAL) New C{_Clip}. ''' # assert isinstance(composite, _CompositeBase) raise ClipError(clipid=clipid, txt=_duplicate_)
'''Is the B{C{point}} in this clip? ''' for v in self: if v is point: # or ==? return True return False
'''Is this clip I{equivalent} to an B{C{other}} clip, do both have the same C{len}, the same points, in the same order, possibly rotated? ''' break # next v else: # equivalent return False
'''See method C{__lt__}. ''' return not self.__lt__(other)
'''Is this clip C{"above"} an B{C{other}} clip, located or stretched farther North or East? ''' return self._bltr4 > _other(self, other)._bltr4
return hash(self._bltr4)
'''Yield the points, duplicates and intersections. '''
'''See method C{__gt__}. ''' return not self.__gt__(other)
'''Return the number of points, duplicates and intersections in this clip. '''
'''Is this clip C{"below"} an B{C{other}} clip, located or stretched farther South or West? '''
'''See method C{__eq__}. ''' return not self.__eq__(other)
# Check whether all verices are ON_ON. return True
# Append a point given as C{y}, C{x}, C{h}eight and C{clipid} # args or as a C{LatLon[FHP|GH]} or C{Clip[FHP|GH}4Tuple}. # assert v._clipid == self._id
else: # insert before _first
# def _appendedup(self, v, clipid=0): # # Like C{._append}, but only append C{v} if not a # # duplicate of the one previously append[edup]'ed. # y, x, p = v.y, v.x, self._last # if p is None or y != p.y or x != p.x or clipid != p._clipid: # p = self._append(y, x, v._height, clipid) # if v._linked: # p._linked = True # to force errors # return p
# Get the bounds as 4-tuple C{(bottom, left, top, right)}.
# End a clip, un-close it and check C{len}. p._next is f and p is not f: # un-close the clip f._prev = p = p._prev p._next = f self._len -= 1 # elif f and raiser: # raise self._OpenClipError(p, f) raise self._Error(_too_(_few_))
# Duplicate a point (or intersection) as intersection. # assert v._prev is q # assert q._next is v
# Yield each I{original} edge as a 2-tuple # C{(LatLon[FHP|GH], LatLon[FHP|GH])}. raise self._OpenClipError(p2, f)
def _Error(self, txt): # PYCHOK no cover # Build a C{ClipError} instance kwds = dict(len=len(self), txt=txt) if self._dups: kwds.update(dups=self._dups) cp = self._composite if self._id: try: i = cp._clips.index(self) if i != self._id: kwds[_clip_] = i except ValueError: pass kwds[_clipid_] = self._id return ClipError(cp.name, cp._kind, **kwds)
# insertVertex between points C{start} and # C{end}, ordered by C{alpha} iff given. # assert start is not end # zap cached C{Property_RO}s # _Clip._bltr4._update(self) # _Clip._ishole._update(self)
# insert an intersection or make a point both else: # intersection at point # assert not v._linked # assert v._alpha is None
# Yield all intersections.
# Is this clip a hole inside its composite? v = self._first return v._isinside(self._composite, self) if v else False
# Yield all non-duplicates.
# Are all intersections non-CROSSINGs?
def _OpenClipError(self, s, e): # PYCHOK no cover # Return a C{CloseError} instance t = NN(s, _ELLIPSIS_(_COMMASPACE_, e)) return self._Error(_SPACE_(_open_, t))
# getNonIntersectionPoint and -Vertex # create a pseudo-point else: # no ._prev, ._next k._clipid = n._clipid r = None
# Yield all points I{in original order}, meybe intersections too.
# Remove vertex C{v}. # assert not v._isduplicated self._first = n self._last = p else: n = self._last = \ p = self._first = None self._len = 0
# Zap the I{cached} properties. _Clip._bltr4._update( self) _Clip._ishole._update(self) return self # for _special_identicals
# Yield all I{un-checked} CROSSING intersections.
class _ClipEdges(_Clip): # PYCHOK no cover # Wrapper yielding edges, eliminating # null edges and adding closing edge.
def __init__(self, cps, LL): # PYCHOK signature self._cps = cps # _CompositeEdges self._LL = LL
def _edges2(self, *unused): # PYCHOK signature # Yield each edge as a C{LatLon[FHP|GH]} point pair. for cp in self._cps: self._composite = cp p2 = s = None for ll in cp: p1, p2 = p2, self._LL(ll) if s is None: s = p1 = p2 elif p1._clipid != p2._clipid: if p1 != s: yield p1, s s = p1 = p2 elif p1 != p2: yield p1, p2 self._id = p2._clipid if p1 != s: yield p1, s
'''(INTERNAL) Base class for L{BooleanFHP} and L{BooleanGH} (C{_CompositeFHP} and C{_CompositeGH}). '''
'''(INTERNAL) See L{BooleanFHP} and L{BooleanGH}. ''' self._eps = eps
else: c._dups += 1
def __contains__(self, point): # PYCHOK no cover '''Is the B{C{point}} in one of the clips? ''' for c in self._clips: if point in c: return True return False
'''Is this I{composite} equivalent to an B{C{other}}, i.e. do both contain I{equivalent} clips in the same or a different order? Two clips are considered I{equivalent} if both have the same points etc. in the same order, possibly rotated. ''' and len(other._clips) == len(cs): except ValueError: # from .index pass return False
'''Yield all points, duplicates and intersections. '''
'''See method C{__eq__}. ''' return not self.__eq__(other)
'''Return the I{total} number of points. '''
'''String C{repr} of this composite. '''
'''String C{str} of this composite. '''
# Get the bottom and top C{y} bounds, oversized. max(v.y for v in self))
# Return a new instance
def _clipids(self): # PYCHOK no cover for c in self._clips: yield c._id
'''Return a tuple with all C{clipid}s, I{ordered}. ''' return tuple(self._clipids)
# def _clipidups(self, other): # # Number common C{clipid}s between this and an C{other} composite # return len(set(self._clipids).intersection(set(other._clipids)))
# Yield each I{original} edge as a 3-tuple # C{(LatLon[FHP|GH], LatLon[FHP|GH], _Clip)}.
'''Get the null edges tolerance (C{degrees}, usually). '''
'''Set the null edges tolerance (C{degrees}, usually). ''' self._eps = eps
# Get eps for _LatLonBool._2Abs e *= _10EPS / EPS else:
# Yield all intersections.
# Get all keyword arguments as C{dict}. name=self.name or op.__name__)
# Get the left and right C{x} bounds, oversized. max(v.x for v in self))
def _points(self, maybe=True): # PYCHOK no cover # Yield all I{original} points, maybe intersections too. for c in self._clips: for v in c._points(maybe=maybe): yield v
'''Get the option to throw L{ClipError} exceptions (C{bool}). '''
'''Set the option to throw L{ClipError} exceptions (C{bool}). ''' self._raiser = bool(throw)
# Yield the dedup'd results, as L{ClipFHP4Tuple}s else: # null, colinear, ... skipped yield f._toClas(C, clipid)
# Combine this and an C{other} composite
'''Yield all (non-duplicate) points and intersections as an instance of B{C{LatLon}}.
@kwarg LatLon: Class to use (C{LatLon}) or if C{None}, L{LatLonFHP} or L{LatLonGH}. @kwarg closed: If C{True}, close each clip (C{bool}). @kwarg LatLon_kwds: Optional, additional B{C{LatLon}} keyword arguments, ignore if C{B{LatLon} is None}.
@raise TypeError: Invalid B{C{LatLon}}.
@note: For intersections, C{height} is an instance of L{HeightX}, otherwise of L{Height}. ''' _MODS.latlonBase.LatLonBase): else: raise _TypeError(LatLon=LatLon)
yield lf
class _CompositeEdges(_CompositeBase): # PYCHOK no cover # Polygon wrapper yielding clips and edges, # eliminating duplicates and closing open clips.
def __init__(self, LL, composites): # PYCHOK signature self._cps = composites self._LL = LL
@property_RO def _clips(self): # Use one C{_Clip}. return (_ClipEdges(self._cps, self._LL),)
'''(INTERNAL) A list of clips representing a I{composite} of L{LatLonFHP} points, duplicates and intersections with an other I{composite}. '''
# New L{_CompositeFHP}. self._raiser = True
# 2) Classify intersection chains. while True: # n.__dict__.pop('_label')
# 3) Copy labels
**closed_inull_raiser_eps): # Clip this composite with another one, C{corners}, # using Foster-Hormann-Popa's algorithm. eps=P._eps, raiser=False)
# compute and insert intersections _outside(p1.y, p2.y, *bt)): # assert not p._linked # assert not q._linked
# label and classify intersections
# check for special cases # handle identicals
# set Entry/Exit flags
# handle splits and crossings
# yield the results
# Yield all clips marked C{._identical}. yield c
# 1) Intersections classification # determine local configuration at this intersection # and positions of Q- and Q+ relative to (P-, I, P+) q3._RPoracle(p1, p, p3)) # check intersecting and overlapping cases
# Yield the result clips, each as # a generator of L{_LatLonFHP}s yield c._all()
# Yield the result from an unchecked CROSSING. while True: raise ClipError(full_circle=v, clipid=v._clipid)
# 4) Set entry/exit flags while True:
# 3.5) Check special cases c._identical = True else: c._pushback = True
# 3.5) Handle identicals # assert len(cds) == len(other._identicals) for c in self._identicals: c._update_all() for v in c._intersections(): d = cds.get(v._linked._clipid, None) if d and d._ishole is c._ishole: c._pushback = True break # next clip
# Yield all intersections marked C{._2split} # assert isinstance(p._2split, _Clip)
# 5) Handle split pairs and 6) crossing candidates
# assert Pc in P._clips # assert p in Pc
# Yield each link as a 2-tuple(p, q)
# overwrites p-q link p._link(qq) q._link(pp) else: q._label = qq._label = L.CROSSING
# Yield all intersections marked C{._2xing}
'''(INTERNAL) A list of clips representing a I{composite} of L{LatLonGH} points, duplicates and intersections with an other I{composite}. '''
# New L{_CompositeGH}. self._xtend = True
**closed_inull_raiser_xtend_eps): # Clip this polygon with another one, C{corners}.
# Core of Greiner/Hormann's algorithm, enhanced U{Correia's # <https://GitHub.com/helderco/univ-polyclip>} implementation*** # and extended to optionally handle so-called "degenerate cases" raiser=False, xtend=False) # 1. find intersections _outside(s1.y, s2.y, *bt)):
# 2. identify entry/exit intersections
# 3. yield the result(s)
# Get the very first vertex return None
# Get the kwds C{dict}.
# Yield the unchecked intersection(s).
# Yield the result from an un-checked intersection. while True: raise ClipError(full_circle=v, clipid=v._clipid)
'''Get the option to handle I{degenerate cases} (C{bool}). '''
'''Set the option to handle I{degenerate cases} (C{bool}). ''' self._xtend = bool(xtend)
# An edge between two L{LatLonFHP} points.
# New edge between points C{p1} and C{p2}, each a L{LatLonFHP}.
self._bt = _left_right_bottom_top_eps(p1, p2)
# Return intersection Type or C{None} _outside(q1.y, q2.y, *self._bt)): # compute and classify alpha and beta # distinguish intersection types E.P_INTERSECT if a_0_1 and b_0 else ( E.Q_INTERSECT if a_0 and b_0_1 else ( E.V_INTERSECT if a_0 and b_0 else None)))
# compute and classify alpha and beta # distinguish overlap type E.P_OVERLAP if a_0_1 and _b_0_1 else ( E.Q_OVERLAP if _a_0_1 and b_0_1 else ( E.V_OVERLAP if a_0 and b_0 else None)))
else:
# An edge between two L{LatLonGH} points.
# New edge between points C{s1} and C{s2}, each a L{LatLonGH}. s1.y, s2.y - s1.y) self._bt = _left_right_bottom_top_eps(s1, s2)
self._xtend = True
# Return C{(alpha)}, see .points.nearestOn5
t = _SPACE_(self._intersect4.__name__, _EdgeGH(c1, c2)) raise ClipError(_case_, n, txt=t)
# Yield the intersections of this and another edge.
# @return: None, 1 or 2 intersections, each a 4-Tuple # (x, y, s_alpha, c_alpha) with intersection # x, y and both alphas.
# @raise ClipError: Intersection unhandled.
# @see: U{Intersection point of two line segments # <http://PaulBourke.net/geometry/pointlineplane/>}. _outside(c1_y, c2.y, *self._bt)): y, sy = self._x_sx_y_sy
_EPS_0 < ca < _1_EPS): _EPS_0 < sa < _1_EPS):
# unhandled, "degenerate" cases 1, 2 or 3 elif self._raiser and not (sa < _EPS_0 or sa > _1_EPS): self._error(1, c1, c2) # intersection at s1 or s2
# intersection at c1 or c2 or at c1 or c2 and s1 or s2 sa = (cx * dy - cy * dx) / d e = 2 if sa < _EPS_0 or sa > _1_EPS else 3 self._error(e, c1, c2)
elif parallel and (sx or sy) and (cx or cy): # PYCHOK no cover # non-null, parallel or colinear edges sa1, d1 = self._alpha2(c1_x - x, c1_y - y, sx, sy) sa2, d2 = self._alpha2(c2.x - x, c2.y - y, sx, sy) if max(d1, d2) < _0_EPS: if self._xtend and not _outside(sa1, sa2, _EPS_0, _1_EPS): if sa1 > sa2: # anti-parallel sa1, sa2 = sa2, sa1 ca1, ca2 = _1_0, _0_0 else: # parallel ca1, ca2 = _0_0, _1_0 ca = fabs((sx / cx) if cx else (sy / cy)) # = hypot(sx, sy) / hypot(cx, cy) if sa1 < 0: # s1 is between c1 and c2 ca *= ca1 + sa1 yield x, y, ca1, _alpha1(ca) else: # c1 is between s1 and s2 yield (x + sa1 * sx), (y + sa1 * sy), sa1, ca1 if sa2 > 1: # s2 is between c1 and c2 ca *= sa2 - _1_0 yield (x + sx), (y + sy), ca2, _alpha1(ca2 - ca) else: # c2 is between s1 and s2 yield (x + sa2 * sx), (y + sa2 * sy), sa2, ca2 elif self._raiser and not _outside(sa1, sa2, _0_0, _1_EPS): self._error(4, c1, c2)
# def _intersect4(self, c1, c2, **unused): # # Intersect this and another edge. # # # @return: 4-Tuple (x, y, s_alpha, c_alpha) with the # # intersection point x, y and both alphas. # # # @raise ClipError: Intersection unhandled. # # # @see: U{Intersection point of two line segments # # <http://PaulBourke.net/geometry/pointlineplane/>}. # # if not (_outside(c1.x, c2.x, *self._lr) or # _outside(c1.y, c2.y, *self._bt)): # x, sx, \ # y, sy = self._x_sx_y_sy # # cx = c2.x - c1.x # cy = c2.y - c1.y # d = cy * sx - cx * sy # # if fabs(d) > _0_EPS: # non-parallel edges # dx = x - c1.x # dy = y - c1.y # sa = (cx * dy - cy * dx) / d # ca = (sx * dy - sy * dx) / d # if _0_EPS < ca < _EPS_1: # if _0_EPS < sa < _EPS_1: # yield (x + sa * sx), (y + sa * sy), sa, ca # # # unhandled, "degenerate" cases 1, 2 or 3 # elif self.raiser and not (sa < _EPS_0 or sa > _1_EPS): # self._error(1, c1, c2) # insection at s1 or s2 # # elif self.raiser and not (ca < _EPS_0 or ca > _1_EPS): # # intersection at c1 or c2 or at c1 or c2 and s1 or s2 # self._error(2 if sa < _EPS_0 or sa > _1_EPS else 3, c1, c2) # # elif self.raiser and (sx or sy) and (cx or cy): # # null, parallel or colinear edges # h = hypot(sx, sy) * _0_EPS # if min(_perpendicular(c1.x - x, c1.y - y, sx, sy), # _perpendicular(c2.x - x, c2.y - y, sx, sy)) < h: # self._error(4, c1, c2) # colinear, overlapping
# Shared C{Boolean[FHP|GH]} methods.
'''Sum: C{this + other} clips. '''
'''Intersection: C{this & other}. '''
'''In-place sum: C{this += other} clips. ''' return self._inplace(self.__add__(other))
'''In-place intersection: C{this &= other}. '''
'''In-place union: C{this |= other}. '''
'''Union: C{this | other}. '''
'''Reverse sum: C{other + this} clips. ''' return _other(self, other)._sum(self, self.__radd__)
'''Reverse intersection: C{other & this} ''' return _other(self, other).__and__(self)
'''Reverse union: C{other | this} ''' return _other(self, other).__or__(self)
# Set up a new C{Boolean[FHP|GH]}.
# Replace this with a L{Boolean*} result. # if self._raiser != r._raiser: # self._raiser = r._raiser # if self._xtend != r._xtend: # self._xtend = r._xtend # if self._eps != r._eps: # self._eps = r._eps
'''I{Composite} class providing I{boolean} operations between two I{composites} using U{Forster-Hormann-Popa<https://www.ScienceDirect.com/ science/article/pii/S259014861930007X>}'s C++ implementation, transcoded to pure Python.
The supported operations between (composite) polygon A and B are:
- C = A & B or A &= B, intersection of A and B
- C = A + B or A += B, sum of A and B clips
- C = A | B or A |= B, union of A and B
- A == B or A != B, equivalent A and B clips
@see: Function L{clipFHP4} and class L{BooleanGH}. '''
'''New L{BooleanFHP} operand for I{boolean} operation.
@arg lls: The polygon points and clips (iterable of L{LatLonFHP}s, L{ClipFHP4Tuple}s or other C{LatLon}s). @kwarg raiser: If C{True}, throw L{ClipError} exceptions (C{bool}). @kwarg esp: Tolerance for eliminating null edges (C{degrees}, same units as the B{C{lls}} coordinates). @kwarg name: Optional name (C{str}). ''' eps=eps, name=name)
'''Not implemented.''' return _NotImplemented(self, other)
'''Not implemented.''' return _NotImplemented(self, other)
'''Not implemented.''' return _NotImplemented(self, other)
# One C{BooleanFHP} operation.
'''I{Composite} class providing I{boolean} operations between two I{composites} using the U{Greiner-Hormann<http://www.Inf.USI.CH/ hormann/papers/Greiner.1998.ECO.pdf>} algorithm and U{Correia <https://GitHub.com/helderco/univ-polyclip>}'s implementation, modified and extended.
The supported operations between (composite) polygon A and B are:
- C = A - B or A -= B, difference A less B
- C = B - A or B -= A, difference B less B
- C = A & B or A &= B, intersection of A and B
- C = A + B or A += B, sum of A and B clips
- C = A | B or A |= B, union of A and B
- A == B or A != B, equivalent A and B clips
@note: To handle I{degenerate cases} like C{point-edge} and C{point-point} intersections, use class L{BooleanFHP}.
@see: Function L{clipGH4} and class L{BooleanFHP}. '''
'''New L{BooleanFHP} operand for I{boolean} operation.
@arg lls: The polygon points and clips (iterable of L{LatLonGH}s, L{ClipGH4Tuple}s or other C{LatLon}s). @kwarg raiser: If C{True}, throw L{ClipError} exceptions (C{bool}). @kwarg xtend: If C{True}, extend edges of I{degenerate cases}, an attempt to handle the latter (C{bool}). @kwarg esp: Tolerance for eliminating null edges (C{degrees}, same units as the B{C{lls}} coordinates). @kwarg name: Optional name (C{str}). ''' eps=eps, name=name)
# One C{BooleanGH} operation.
'''In-place difference: C{this -= other}. ''' return self._inplace(self.__sub__(other))
''' Reverse difference: C{other - this} ''' return _other(self, other).__sub__(self)
'''Difference: C{this - other}. '''
_CompositeBase, _CompositeFHP, _CompositeGH, _LatLonBool)
# **) MIT License # # Copyright (C) 2018-2023 -- mrJean1 at Gmail -- All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE.
# ***) GNU GPL 3 # # Copyright (C) 2011-2012 Helder Correia <Helder.MC@Gmail.com> # # This program is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of # the License, or any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.GNU.org/licenses/>. # # You should have received the README file along with this program. # If not, see <https://GitHub.com/helderco/univ-polyclip>. |