def clipCS3(points, lowerleft, upperright, closed=False, inull=False): # MCCABE 25 '''Clip a path against a rectangular clip box using the U{Cohen-Sutherland <http://WikiPedia.org/wiki/Cohen-Sutherland_algorithm>} algorithm. @param points: The points (C{LatLon}[]). @param lowerleft: Bottom-left corner of the clip box (C{LatLon}). @param upperright: Top-right corner of the clip box (C{LatLon}). @keyword closed: Optionally, close the path (C{bool}). @keyword inull: Optionally, include null edges if inside (C{bool}). @return: Yield a 3-tuple (start, end, index) for each edge of the clipped path with the start and end points C{LatLon} of the portion of the edge inside or on the clip box and the index C{int} of the edge in the original path. @raise ValueError: The I{lowerleft} corner is not below and/or not to the left of the I{upperright} corner. ''' cs = _CS(lowerleft, upperright) n, points = points2(points, closed=closed) i, m = _imdex2(closed, n) cmbp = cs.code4(points[i]) for i in range(m, n): c1, m1, b1, p1 = cmbp c2, m2, b2, p2 = cmbp = cs.code4(points[i]) if c1 & c2: # edge outside continue if not cs.edge(p1, p2): if inull: # null edge if not c1: yield p1, p1, i elif not c2: yield p2, p2, i continue for _ in range(5): if c1: # clip p1 c1, m1, b1, p1 = m1(b1, p1) elif c2: # clip p2 c2, m2, b2, p2 = m2(b2, p2) else: # inside if inull or _neq(p1, p2): yield p1, p2, i break if c1 & c2: # edge outside break else: # should never get here raise AssertionError('clipCS3.for_')
def __init__(self, corners): n = '' try: n, cs = len2(corners) if n == 2: # make a box b, l, t, r = boundsOf(cs, wrap=False) cs = LL_(b, l), LL_(t, l), LL_(t, r), LL_(b, r) n, cs = points2(cs, closed=True) self._corners = cs = cs[:n] self._nc = n self._cw = 1 if isclockwise(cs, adjust=False, wrap=False) else -1 if self._cw != isconvex_(cs, adjust=False, wrap=False): raise ValueError except ValueError: raise ValueError('invalid %s[%s]: %r' % ('corners', n, corners)) self._clipped = self._points = []
def points2(self, points, closed=True): '''Check a polygon represented by points. @param points: The polygon points (C{LatLon}[]) @keyword closed: Optionally, consider the polygon closed, ignoring any duplicate or closing final I{points} (C{bool}). @return: 2-Tuple (number, ...) of points (C{int}, C{list} or C{tuple}). @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points}. ''' return points2(points, closed=closed, base=self)
def _geodesic(datum, points, closed, line, wrap): # Compute the area or perimeter of a polygon, # using the GeographicLib package, iff installed g = datum.ellipsoid.geodesic if not wrap: # capability LONG_UNROLL can't be off raise ValueError('%s invalid: %s' % ('wrap', wrap)) _, points = points2(points, closed=closed) # base=LatLonEllipsoidalBase(0, 0) g = g.Polygon(line) # note, lon deltas are unrolled, by default for p in points: g.AddPoint(p.lat, p.lon) if line and closed: p = points[0] g.AddPoint(p.lat, p.lon) # g.Compute returns (number_of_points, perimeter, signed area) return g.Compute(False, True)[1 if line else 2]
def ispolar(points, wrap=False): '''Check whether a polygon encloses a pole. @param points: The polygon points (C{LatLon}[]). @keyword wrap: Wrap and unroll longitudes (C{bool}). @return: C{True} if a pole is enclosed by the polygon, C{False} otherwise. @raise ValueError: Insufficient number of I{points}. @raise TypeError: Some I{points} are not C{LatLon} or don't have C{bearingTo2}, C{initialBearingTo} and C{finalBearingTo} methods. ''' n, points = points2(points, closed=True) def _cds(n, points): # iterate over course deltas p1 = points[n - 1] try: # LatLon must have initial- and finalBearingTo b1, _ = p1.bearingTo2(points[0], wrap=wrap) except AttributeError: raise TypeError('invalid %s: %r' % ('points', p1)) for i in range(n): p2 = points[i] if not p2.isequalTo(p1, EPS): b, b2 = p1.bearingTo2(p2, wrap=wrap) yield wrap180(b - b1) # (b - b1 + 540) % 360 - 180 yield wrap180(b2 - b) # (b2 - b + 540) % 360 - 180 p1, b1 = p2, b2 # sum of course deltas around pole is 0° rather than normally ±360° # <http://blog.Element84.com/determining-if-a-spherical-polygon-contains-a-pole.html> s = fsum(_cds(n, points)) # XXX fix (intermittant) edge crossing pole - eg (85,90), (85,0), (85,-90) return abs(s) < 90 # "zero-ish"
def __init__(self, latlons, closed=False, radius=None, wrap=True): '''Handle C{LatLon} points as pseudo-xy coordinates. @note: The C{LatLon} latitude is considered the pseudo-y and longitude the pseudo-x coordinate. Similarly, 2-tuples (x, y) are (longitude, latitude). @param latlons: Points C{list}, C{sequence}, C{set}, C{tuple}, etc. (I{LatLon[]}). @keyword closed: Optionally, close the polygon (C{bool}). @keyword radius: Optional, mean earth radius (C{meter}). @keyword wrap: Wrap lat- and longitudes (C{bool}). @raise TypeError: Some I{latlons} are not C{LatLon}. @raise ValueError: Insufficient number of I{latlons}. ''' self._closed = closed self._len, self._array = points2(latlons, closed=closed) if radius: self._radius = radius self._deg2m = degrees2m(1.0, radius) self._wrap = wrap
def nearestOn4(point, points, closed=False, wrap=False, **options): '''Locate the point on a path or polygon closest to an other point. If the given point is within the extent of a polygon edge, the closest point is on that edge, otherwise the closest point is the nearest of that edge's end points. Distances are approximated by function L{equirectangular_}, subject to the supplied I{options}. @param point: The other, reference point (C{LatLon}). @param points: The path or polygon points (C{LatLon}[]). @keyword closed: Optionally, close the path or polygon (C{bool}). @keyword wrap: Wrap and L{unroll180} longitudes and longitudinal delta (C{bool}) in function L{equirectangular_}. @keyword options: Other keyword arguments for function L{equirectangular_}. @return: 4-Tuple (lat, lon, I{distance}, I{angle}) all in C{degrees}. The I{distance} is the L{equirectangular_} distance between the closest and the reference I{point} in C{degrees}. The I{angle} from the reference I{point} to the closest point is in compass C{degrees360}, like function L{compassAngle}. @raise LimitError: Lat- and/or longitudinal delta exceeds I{limit}, see function L{equirectangular_}. @raise TypeError: Some I{points} are not C{LatLon}. @raise ValueError: Insufficient number of I{points}. @see: Function L{nearestOn3}. Use function L{degrees2m} to convert C{degrees} to C{meter}. ''' n, points = points2(points, closed=closed) def _d2yx(p2, p1, u, i): w = wrap if (not closed or i < (n - 1)) else False # equirectangular_ returns a 4-tuple (distance in # degrees squared, delta lat, delta lon, p2.lon # unroll/wrap); the previous p2.lon unroll/wrap # is also applied to the next edge's p1.lon return equirectangular_(p1.lat, p1.lon + u, p2.lat, p2.lon, wrap=w, **options) # point (x, y) on axis rotated ccw by angle a: # x' = y * sin(a) + x * cos(a) # y' = y * cos(a) - x * sin(a) # # distance (w) along and perpendicular (h) to # a line thru point (dx, dy) and the origin: # w = (y * dy + x * dx) / hypot(dx, dy) # h = (y * dx - x * dy) / hypot(dx, dy) # # closest point on that line thru (dx, dy): # xc = dx * w / hypot(dx, dy) # yc = dy * w / hypot(dx, dy) # or # xc = dx * f # yc = dy * f # with # f = w / hypot(dx, dy) # or # f = (y * dy + x * dx) / (dx**2 + dy**2) i, m = _imdex2(closed, n) p2 = c = points[i] u2 = u = 0 d, dy, dx, _ = _d2yx(p2, point, u2, i) for i in range(m, n): p1, u1, p2 = p2, u2, points[i] # iff wrapped, unroll lon1 (actually previous # lon2) like function unroll180/-PI would've d21, y21, x21, u2 = _d2yx(p2, p1, u1, i) if d21 > EPS: # distance point to p1, y01 and x01 inverted d2, y01, x01, _ = _d2yx(point, p1, u1, n) if d2 > EPS: w2 = y01 * y21 + x01 * x21 if w2 > 0: if w2 < d21: # closest is between p1 and p2, use # original delta's, not y21 and x21 f = w2 / d21 p1 = LatLon_(favg(p1.lat, p2.lat, f=f), favg(p1.lon, p2.lon + u2, f=f)) u1 = 0 else: # p2 is closest p1, u1 = p2, u2 d2, y01, x01, _ = _d2yx(point, p1, u1, n) if d2 < d: # p1 is closer, y01 and x01 inverted c, u, d, dy, dx = p1, u1, d2, -y01, -x01 return c.lat, c.lon + u, hypot(dx, dy), degrees360(atan2(dx, dy))
def points(self, points, closed=False, base=None): return points2(points, closed=closed, base=base)
def polygon(points, closed=True, base=None): '''DEPRECATED, use function L{points2}. ''' from utily import points2 return points2(points, closed=closed, base=base)