OSDN Git Service

data write directory change. shapely library include.
[openembroidery/EmbroideryOutputExtensionForInkscape.git] / embroider / shapely / geometry / polygon.py
diff --git a/embroider/shapely/geometry/polygon.py b/embroider/shapely/geometry/polygon.py
new file mode 100644 (file)
index 0000000..d4e1f43
--- /dev/null
@@ -0,0 +1,484 @@
+"""Polygons and their linear ring components
+"""
+
+import sys
+
+if sys.version_info[0] < 3:
+    range = xrange
+
+from ctypes import c_double, c_void_p, cast, POINTER
+from ctypes import ArgumentError
+import weakref
+
+from shapely.algorithms.cga import signed_area
+from shapely.coords import required
+from shapely.geos import lgeos
+from shapely.geometry.base import BaseGeometry
+from shapely.geometry.linestring import LineString, LineStringAdapter
+from shapely.geometry.proxy import PolygonProxy
+
+__all__ = ['Polygon', 'asPolygon', 'LinearRing', 'asLinearRing']
+
+
+class LinearRing(LineString):
+    """
+    A closed one-dimensional feature comprising one or more line segments
+
+    A LinearRing that crosses itself or touches itself at a single point is
+    invalid and operations on it may fail.
+    """
+    
+    def __init__(self, coordinates=None):
+        """
+        Parameters
+        ----------
+        coordinates : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples
+
+        Rings are implicitly closed. There is no need to specific a final
+        coordinate pair identical to the first.
+
+        Example
+        -------
+        Construct a square ring.
+
+          >>> ring = LinearRing( ((0, 0), (0, 1), (1 ,1 ), (1 , 0)) )
+          >>> ring.is_closed
+          True
+          >>> ring.length
+          4.0
+        """
+        BaseGeometry.__init__(self)
+        if coordinates is not None:
+            self._set_coords(coordinates)
+
+    @property
+    def __geo_interface__(self):
+        return {
+            'type': 'LinearRing',
+            'coordinates': tuple(self.coords)
+            }
+
+    # Coordinate access
+
+    _get_coords = BaseGeometry._get_coords
+
+    def _set_coords(self, coordinates):
+        self.empty()
+        self._geom, self._ndim = geos_linearring_from_py(coordinates)
+
+    coords = property(_get_coords, _set_coords)
+
+    @property
+    def is_ccw(self):
+        """True is the ring is oriented counter clock-wise"""
+        return bool(self.impl['is_ccw'](self))
+
+    @property
+    def is_simple(self):
+        """True if the geometry is simple, meaning that any self-intersections
+        are only at boundary points, else False"""
+        return LineString(self).is_simple
+
+
+class LinearRingAdapter(LineStringAdapter):
+
+    __p__ = None
+
+    def __init__(self, context):
+        self.context = context
+        self.factory = geos_linearring_from_py
+
+    @property
+    def __geo_interface__(self):
+        return {
+            'type': 'LinearRing',
+            'coordinates': tuple(self.coords)
+            }
+
+    coords = property(BaseGeometry._get_coords)
+
+
+def asLinearRing(context):
+    """Adapt an object to the LinearRing interface"""
+    return LinearRingAdapter(context)
+
+
+class InteriorRingSequence(object):
+
+    _factory = None
+    _geom = None
+    __p__ = None
+    _ndim = None
+    _index = 0
+    _length = 0
+    __rings__ = None
+    _gtag = None
+
+    def __init__(self, parent):
+        self.__p__ = parent
+        self._geom = parent._geom
+        self._ndim = parent._ndim
+
+    def __iter__(self):
+        self._index = 0
+        self._length = self.__len__()
+        return self
+
+    def __next__(self):
+        if self._index < self._length:
+            ring = self._get_ring(self._index)
+            self._index += 1
+            return ring
+        else:
+            raise StopIteration 
+
+    if sys.version_info[0] < 3:
+        next = __next__
+
+    def __len__(self):
+        return lgeos.GEOSGetNumInteriorRings(self._geom)
+
+    def __getitem__(self, key):
+        m = self.__len__()
+        if isinstance(key, int):
+            if key + m < 0 or key >= m:
+                raise IndexError("index out of range")
+            if key < 0:
+                i = m + key
+            else:
+                i = key
+            return self._get_ring(i)
+        elif isinstance(key, slice):
+            res = []
+            start, stop, stride = key.indices(m)
+            for i in range(start, stop, stride):
+                res.append(self._get_ring(i))
+            return res
+        else:
+            raise TypeError("key must be an index or slice")
+
+    @property
+    def _longest(self):
+        max = 0
+        for g in iter(self):
+            l = len(g.coords)
+            if l > max:
+                max = l
+
+    def gtag(self):
+        return hash(repr(self.__p__))
+
+    def _get_ring(self, i):
+        gtag = self.gtag()
+        if gtag != self._gtag:
+            self.__rings__ = {}
+        if i not in self.__rings__:
+            g = lgeos.GEOSGetInteriorRingN(self._geom, i)
+            ring = LinearRing()
+            ring.__geom__ = g
+            ring.__p__ = self
+            ring._owned = True
+            ring._ndim = self._ndim
+            self.__rings__[i] = weakref.ref(ring)
+        return self.__rings__[i]()
+        
+
+class Polygon(BaseGeometry):
+    """
+    A two-dimensional figure bounded by a linear ring
+
+    A polygon has a non-zero area. It may have one or more negative-space
+    "holes" which are also bounded by linear rings. If any rings cross each
+    other, the feature is invalid and operations on it may fail.
+
+    Attributes
+    ----------
+    exterior : LinearRing
+        The ring which bounds the positive space of the polygon.
+    interiors : sequence
+        A sequence of rings which bound all existing holes.
+    """
+
+    _exterior = None
+    _interiors = []
+    _ndim = 2
+
+    def __init__(self, shell=None, holes=None):
+        """
+        Parameters
+        ----------
+        shell : sequence
+            A sequence of (x, y [,z]) numeric coordinate pairs or triples
+        holes : sequence
+            A sequence of objects which satisfy the same requirements as the
+            shell parameters above
+
+        Example
+        -------
+        Create a square polygon with no holes
+
+          >>> coords = ((0., 0.), (0., 1.), (1., 1.), (1., 0.), (0., 0.))
+          >>> polygon = Polygon(coords)
+          >>> polygon.area
+          1.0
+        """
+        BaseGeometry.__init__(self)
+
+        if shell is not None:
+            self._geom, self._ndim = geos_polygon_from_py(shell, holes)
+
+    @property
+    def exterior(self):
+        if self.is_empty:
+            return None
+        elif self._exterior is None or self._exterior() is None:
+            g = lgeos.GEOSGetExteriorRing(self._geom)
+            ring = LinearRing()
+            ring.__geom__ = g
+            ring.__p__ = self
+            ring._owned = True
+            ring._ndim = self._ndim
+            self._exterior = weakref.ref(ring)
+        return self._exterior()
+
+    @property
+    def interiors(self):
+        if self.is_empty:
+            return []
+        return InteriorRingSequence(self)
+
+    @property
+    def ctypes(self):
+        if not self._ctypes_data:
+            self._ctypes_data = self.exterior.ctypes
+        return self._ctypes_data
+
+    @property
+    def __array_interface__(self):
+        raise NotImplementedError(
+        "A polygon does not itself provide the array interface. Its rings do.")
+
+    def _get_coords(self):
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
+
+    def _set_coords(self, ob):
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
+
+    @property
+    def coords(self):
+        raise NotImplementedError(
+        "Component rings have coordinate sequences, but the polygon does not")
+
+    @property
+    def __geo_interface__(self):
+        coords = [tuple(self.exterior.coords)]
+        for hole in self.interiors:
+            coords.append(tuple(hole.coords))
+        return {
+            'type': 'Polygon',
+            'coordinates': tuple(coords)
+            }
+
+
+class PolygonAdapter(PolygonProxy, Polygon):
+    
+    def __init__(self, shell, holes=None):
+        self.shell = shell
+        self.holes = holes
+        self.context = (shell, holes)
+        self.factory = geos_polygon_from_py
+
+    @property
+    def _ndim(self):
+        try:
+            # From array protocol
+            array = self.shell.__array_interface__
+            n = array['shape'][1]
+            assert n == 2 or n == 3
+            return n
+        except AttributeError:
+            # Fall back on list
+            return len(self.shell[0])
+
+
+def asPolygon(shell, holes=None):
+    """Adapt objects to the Polygon interface"""
+    return PolygonAdapter(shell, holes)
+
+def orient(polygon, sign=1.0):
+    s = float(sign)
+    rings = []
+    ring = polygon.exterior
+    if signed_area(ring)/s >= 0.0:
+        rings.append(ring)
+    else:
+        rings.append(list(ring.coords)[::-1])
+    for ring in polygon.interiors:
+        if signed_area(ring)/s <= 0.0:
+            rings.append(ring)
+        else:
+            rings.append(list(ring.coords)[::-1])
+    return Polygon(rings[0], rings[1:])
+
+def geos_linearring_from_py(ob, update_geom=None, update_ndim=0):
+    # If numpy is present, we use numpy.require to ensure that we have a
+    # C-continguous array that owns its data. View data will be copied.
+    ob = required(ob)
+    try:
+        # From array protocol
+        array = ob.__array_interface__
+        assert len(array['shape']) == 2
+        m = array['shape'][0]
+        n = array['shape'][1]
+        if m < 3:
+            raise ValueError(
+                "A LinearRing must have at least 3 coordinate tuples")
+        assert n == 2 or n == 3
+
+        # Make pointer to the coordinate array
+        if isinstance(array['data'], tuple):
+            # numpy tuple (addr, read-only)
+            cp = cast(array['data'][0], POINTER(c_double))
+        else:
+            cp = array['data']
+
+        # Add closing coordinates to sequence?
+        if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]:
+            M = m + 1
+        else:
+            M = m
+
+        # Create a coordinate sequence
+        if update_geom is not None:
+            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
+            if n != update_ndim:
+                raise ValueError(
+                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
+                % update_ndim)
+        else:
+            cs = lgeos.GEOSCoordSeq_create(M, n)
+
+        # add to coordinate sequence
+        for i in range(m):
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, i, cp[n*i])
+            lgeos.GEOSCoordSeq_setY(cs, i, cp[n*i+1])
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, i, cp[n*i+2])
+
+        # Add closing coordinates to sequence?
+        if M > m:        
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, M-1, cp[0])
+            lgeos.GEOSCoordSeq_setY(cs, M-1, cp[1])
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, M-1, cp[2])
+            
+    except AttributeError:
+        # Fall back on list
+        try:
+            m = len(ob)
+        except TypeError:  # Iterators, e.g. Python 3 zip
+            ob = list(ob)
+            m = len(ob)
+
+        n = len(ob[0])
+        if m < 3:
+            raise ValueError(
+                "A LinearRing must have at least 3 coordinate tuples")
+        assert (n == 2 or n == 3)
+
+        # Add closing coordinates if not provided
+        if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]:
+            M = m + 1
+        else:
+            M = m
+
+        # Create a coordinate sequence
+        if update_geom is not None:
+            cs = lgeos.GEOSGeom_getCoordSeq(update_geom)
+            if n != update_ndim:
+                raise ValueError(
+                "Wrong coordinate dimensions; this geometry has dimensions: %d" \
+                % update_ndim)
+        else:
+            cs = lgeos.GEOSCoordSeq_create(M, n)
+        
+        # add to coordinate sequence
+        for i in range(m):
+            coords = ob[i]
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, i, coords[0])
+            lgeos.GEOSCoordSeq_setY(cs, i, coords[1])
+            if n == 3:
+                try:
+                    lgeos.GEOSCoordSeq_setZ(cs, i, coords[2])
+                except IndexError:
+                    raise ValueError("Inconsistent coordinate dimensionality")
+
+        # Add closing coordinates to sequence?
+        if M > m:
+            coords = ob[0]
+            # Because of a bug in the GEOS C API, 
+            # always set X before Y
+            lgeos.GEOSCoordSeq_setX(cs, M-1, coords[0])
+            lgeos.GEOSCoordSeq_setY(cs, M-1, coords[1])
+            if n == 3:
+                lgeos.GEOSCoordSeq_setZ(cs, M-1, coords[2])
+
+    if update_geom is not None:
+        return None
+    else:
+        return lgeos.GEOSGeom_createLinearRing(cs), n
+
+def update_linearring_from_py(geom, ob):
+    geos_linearring_from_py(ob, geom._geom, geom._ndim)
+
+def geos_polygon_from_py(shell, holes=None):
+    if shell is not None:
+        geos_shell, ndim = geos_linearring_from_py(shell)
+        if holes is not None and len(holes) > 0:
+            ob = holes
+            L = len(ob)
+            exemplar = ob[0]
+            try:
+                N = len(exemplar[0])
+            except TypeError:
+                N = exemplar._ndim
+            if not L >= 1:
+                raise ValueError("number of holes must be non zero")
+            if not N in (2, 3):
+                raise ValueError("insufficiant coordinate dimension")
+
+            # Array of pointers to ring geometries
+            geos_holes = (c_void_p * L)()
+    
+            # add to coordinate sequence
+            for l in range(L):
+                geom, ndim = geos_linearring_from_py(ob[l])
+                geos_holes[l] = cast(geom, c_void_p)
+        else:
+            geos_holes = POINTER(c_void_p)()
+            L = 0
+        return (
+            lgeos.GEOSGeom_createPolygon(
+                        c_void_p(geos_shell),
+                        geos_holes,
+                        L
+                        ),
+            ndim
+            )
+
+# Test runner
+def _test():
+    import doctest
+    doctest.testmod()
+
+if __name__ == "__main__":
+    _test()