def __init__(self, *args, **kwargs):
        """
        Initializes on the given sequence -- may take lists, tuples, NumPy arrays
        of X,Y pairs, or Point objects.  If Point objects are used, ownership is
        _not_ transferred to the LineString object.

        Examples:
         ls = LineString((1, 1), (2, 2))
         ls = LineString([(1, 1), (2, 2)])
         ls = LineString(array([(1, 1), (2, 2)]))
         ls = LineString(Point(1, 1), Point(2, 2))
        """
        # If only one argument provided, set the coords array appropriately
        if len(args) == 1: coords = args[0]
        else: coords = args

        if isinstance(coords, (tuple, list)):
            # Getting the number of coords and the number of dimensions -- which
            #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
            ncoords = len(coords)
            if coords: ndim = len(coords[0])
            else: raise TypeError('Cannot initialize on empty sequence.')
            self._checkdim(ndim)
            # Incrementing through each of the coordinates and verifying
            for i in xrange(1, ncoords):
                if not isinstance(coords[i], (tuple, list, Point)):
                    raise TypeError(
                        'each coordinate should be a sequence (list or tuple)')
                if len(coords[i]) != ndim:
                    raise TypeError('Dimension mismatch.')
            numpy_coords = False
        elif numpy and isinstance(coords, numpy.ndarray):
            shape = coords.shape  # Using numpy's shape.
            if len(shape) != 2: raise TypeError('Too many dimensions.')
            self._checkdim(shape[1])
            ncoords = shape[0]
            ndim = shape[1]
            numpy_coords = True
        else:
            raise TypeError('Invalid initialization input for LineStrings.')

        # Creating a coordinate sequence object because it is easier to
        # set the points using GEOSCoordSeq.__setitem__().
        cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))

        for i in xrange(ncoords):
            if numpy_coords: cs[i] = coords[i, :]
            elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
            else: cs[i] = coords[i]

        # If SRID was passed in with the keyword arguments
        srid = kwargs.get('srid', None)

        # Calling the base geometry initialization with the returned pointer
        #  from the function.
        super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)
    def _set_list(self, length, items):
        ndim = self._cs.dims
        hasz = self._cs.hasz  # I don't understand why these are different

        # create a new coordinate sequence and populate accordingly
        cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
        for i, c in enumerate(items):
            cs[i] = c

        ptr = self._init_func(cs.ptr)
        if ptr:
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
    def _set_list(self, length, items):
        ndim = self._cs.dims
        hasz = self._cs.hasz  # I don't understand why these are different

        # create a new coordinate sequence and populate accordingly
        cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
        for i, c in enumerate(items):
            cs[i] = c

        ptr = self._init_func(cs.ptr)
        if ptr:
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
            self._post_init(self.srid)
        else:
            # can this happen?
            raise GEOSException('Geometry resulting from slice deletion was invalid.')
    def __init__(self, *args, **kwargs):
        """
        Initialize on the given sequence -- may take lists, tuples, NumPy arrays
        of X,Y pairs, or Point objects.  If Point objects are used, ownership is
        _not_ transferred to the LineString object.

        Examples:
         ls = LineString((1, 1), (2, 2))
         ls = LineString([(1, 1), (2, 2)])
         ls = LineString(array([(1, 1), (2, 2)]))
         ls = LineString(Point(1, 1), Point(2, 2))
        """
        # If only one argument provided, set the coords array appropriately
        if len(args) == 1:
            coords = args[0]
        else:
            coords = args

        if not (isinstance(coords, (tuple, list)) or numpy and isinstance(coords, numpy.ndarray)):
            raise TypeError('Invalid initialization input for LineStrings.')

        # If SRID was passed in with the keyword arguments
        srid = kwargs.get('srid')

        ncoords = len(coords)
        if not ncoords:
            super().__init__(self._init_func(None), srid=srid)
            return

        if ncoords < self._minlength:
            raise ValueError(
                '%s requires at least %d points, got %s.' % (
                    self.__class__.__name__,
                    self._minlength,
                    ncoords,
                )
            )

        if isinstance(coords, (tuple, list)):
            # Getting the number of coords and the number of dimensions -- which
            #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
            ndim = None
            # Incrementing through each of the coordinates and verifying
            for coord in coords:
                if not isinstance(coord, (tuple, list, Point)):
                    raise TypeError('Each coordinate should be a sequence (list or tuple)')

                if ndim is None:
                    ndim = len(coord)
                    self._checkdim(ndim)
                elif len(coord) != ndim:
                    raise TypeError('Dimension mismatch.')
            numpy_coords = False
        else:
            shape = coords.shape  # Using numpy's shape.
            if len(shape) != 2:
                raise TypeError('Too many dimensions.')
            self._checkdim(shape[1])
            ndim = shape[1]
            numpy_coords = True

        # Creating a coordinate sequence object because it is easier to
        # set the points using its methods.
        cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))
        point_setter = cs._set_point_3d if ndim == 3 else cs._set_point_2d

        for i in range(ncoords):
            if numpy_coords:
                point_coords = coords[i, :]
            elif isinstance(coords[i], Point):
                point_coords = coords[i].tuple
            else:
                point_coords = coords[i]
            point_setter(i, point_coords)

        # Calling the base geometry initialization with the returned pointer
        #  from the function.
        super().__init__(self._init_func(cs.ptr), srid=srid)
Exemple #5
0
class GEOSGeometry(GEOSBase, ListMixin):
    "A class that, generally, encapsulates a GEOS geometry."

    # Raise GEOSIndexError instead of plain IndexError
    # (see ticket #4740 and GEOSIndexError docstring)
    _IndexError = GEOSIndexError

    ptr_type = GEOM_PTR

    #### Python 'magic' routines ####
    def __init__(self, geo_input, srid=None):
        """
        The base constructor for GEOS geometry objects, and may take the
        following inputs:

         * strings:
            - WKT
            - HEXEWKB (a PostGIS-specific canonical form)
            - GeoJSON (requires GDAL)
         * buffer:
            - WKB

        The `srid` keyword is used to specify the Source Reference Identifier
        (SRID) number for this Geometry.  If not set, the SRID will be None.
        """
        if isinstance(geo_input, basestring):
            if isinstance(geo_input, unicode):
                # Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
                geo_input = geo_input.encode('ascii')

            wkt_m = wkt_regex.match(geo_input)
            if wkt_m:
                # Handling WKT input.
                if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
                g = wkt_r().read(wkt_m.group('wkt'))
            elif hex_regex.match(geo_input):
                # Handling HEXEWKB input.
                g = wkb_r().read(geo_input)
            elif gdal.HAS_GDAL and json_regex.match(geo_input):
                # Handling GeoJSON input.
                g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb)
            else:
                raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
        elif isinstance(geo_input, GEOM_PTR):
            # When the input is a pointer to a geomtry (GEOM_PTR).
            g = geo_input
        elif isinstance(geo_input, buffer):
            # When the input is a buffer (WKB).
            g = wkb_r().read(geo_input)
        elif isinstance(geo_input, GEOSGeometry):
            g = capi.geom_clone(geo_input.ptr)
        else:
            # Invalid geometry type.
            raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))

        if bool(g):
            # Setting the pointer object with a valid pointer.
            self.ptr = g
        else:
            raise GEOSException('Could not initialize GEOS Geometry with given input.')

        # Post-initialization setup.
        self._post_init(srid)

    def _post_init(self, srid):
        "Helper routine for performing post-initialization setup."
        # Setting the SRID, if given.
        if srid and isinstance(srid, int): self.srid = srid

        # Setting the class type (e.g., Point, Polygon, etc.)
        self.__class__ = GEOS_CLASSES[self.geom_typeid]

        # Setting the coordinate sequence for the geometry (will be None on
        # geometries that do not have coordinate sequences)
        self._set_cs()

    def __del__(self):
        """
        Destroys this Geometry; in other words, frees the memory used by the
        GEOS C++ object.
        """
        if self._ptr: capi.destroy_geom(self._ptr)

    def __copy__(self):
        """
        Returns a clone because the copy of a GEOSGeometry may contain an
        invalid pointer location if the original is garbage collected.
        """
        return self.clone()

    def __deepcopy__(self, memodict):
        """
        The `deepcopy` routine is used by the `Node` class of django.utils.tree;
        thus, the protocol routine needs to be implemented to return correct
        copies (clones) of these GEOS objects, which use C pointers.
        """
        return self.clone()

    def __str__(self):
        "WKT is used for the string representation."
        return self.wkt

    def __repr__(self):
        "Short-hand representation because WKT may be very large."
        return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))

    # Pickling support
    def __getstate__(self):
        # The pickled state is simply a tuple of the WKB (in string form)
        # and the SRID.
        return str(self.wkb), self.srid

    def __setstate__(self, state):
        # Instantiating from the tuple state that was pickled.
        wkb, srid = state
        ptr = wkb_r().read(buffer(wkb))
        if not ptr: raise GEOSException('Invalid Geometry loaded from pickled state.')
        self.ptr = ptr
        self._post_init(srid)

    # Comparison operators
    def __eq__(self, other):
        """
        Equivalence testing, a Geometry may be compared with another Geometry
        or a WKT representation.
        """
        if isinstance(other, basestring):
            return self.wkt == other
        elif isinstance(other, GEOSGeometry):
            return self.equals_exact(other)
        else:
            return False

    def __ne__(self, other):
        "The not equals operator."
        return not (self == other)

    ### Geometry set-like operations ###
    # Thanks to Sean Gillies for inspiration:
    #  http://lists.gispython.org/pipermail/community/2007-July/001034.html
    # g = g1 | g2
    def __or__(self, other):
        "Returns the union of this Geometry and the other."
        return self.union(other)

    # g = g1 & g2
    def __and__(self, other):
        "Returns the intersection of this Geometry and the other."
        return self.intersection(other)

    # g = g1 - g2
    def __sub__(self, other):
        "Return the difference this Geometry and the other."
        return self.difference(other)

    # g = g1 ^ g2
    def __xor__(self, other):
        "Return the symmetric difference of this Geometry and the other."
        return self.sym_difference(other)

    #### Coordinate Sequence Routines ####
    @property
    def has_cs(self):
        "Returns True if this Geometry has a coordinate sequence, False if not."
        # Only these geometries are allowed to have coordinate sequences.
        if isinstance(self, (Point, LineString, LinearRing)):
            return True
        else:
            return False

    def _set_cs(self):
        "Sets the coordinate sequence for this Geometry."
        if self.has_cs:
            self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
        else:
            self._cs = None

    @property
    def coord_seq(self):
        "Returns a clone of the coordinate sequence for this Geometry."
        if self.has_cs:
            return self._cs.clone()

    #### Geometry Info ####
    @property
    def geom_type(self):
        "Returns a string representing the Geometry type, e.g. 'Polygon'"
        return capi.geos_type(self.ptr)

    @property
    def geom_typeid(self):
        "Returns an integer representing the Geometry type."
        return capi.geos_typeid(self.ptr)

    @property
    def num_geom(self):
        "Returns the number of geometries in the Geometry."
        return capi.get_num_geoms(self.ptr)

    @property
    def num_coords(self):
        "Returns the number of coordinates in the Geometry."
        return capi.get_num_coords(self.ptr)

    @property
    def num_points(self):
        "Returns the number points, or coordinates, in the Geometry."
        return self.num_coords

    @property
    def dims(self):
        "Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
        return capi.get_dims(self.ptr)

    def normalize(self):
        "Converts this Geometry to normal form (or canonical form)."
        return capi.geos_normalize(self.ptr)

    #### Unary predicates ####
    @property
    def empty(self):
        """
        Returns a boolean indicating whether the set of points in this Geometry
        are empty.
        """
        return capi.geos_isempty(self.ptr)

    @property
    def hasz(self):
        "Returns whether the geometry has a 3D dimension."
        return capi.geos_hasz(self.ptr)

    @property
    def ring(self):
        "Returns whether or not the geometry is a ring."
        return capi.geos_isring(self.ptr)

    @property
    def simple(self):
        "Returns false if the Geometry not simple."
        return capi.geos_issimple(self.ptr)

    @property
    def valid(self):
        "This property tests the validity of this Geometry."
        return capi.geos_isvalid(self.ptr)

    @property
    def valid_reason(self):
        """
        Returns a string containing the reason for any invalidity.
        """
        if not GEOS_PREPARE:
            raise GEOSException('Upgrade GEOS to 3.1 to get validity reason.')
        return capi.geos_isvalidreason(self.ptr)

    #### Binary predicates. ####
    def contains(self, other):
        "Returns true if other.within(this) returns true."
        return capi.geos_contains(self.ptr, other.ptr)

    def crosses(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*T****** (for a point and a curve,a point and an area or a line and
        an area) 0******** (for two curves).
        """
        return capi.geos_crosses(self.ptr, other.ptr)

    def disjoint(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is FF*FF****.
        """
        return capi.geos_disjoint(self.ptr, other.ptr)

    def equals(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*F**FFF*.
        """
        return capi.geos_equals(self.ptr, other.ptr)

    def equals_exact(self, other, tolerance=0):
        """
        Returns true if the two Geometries are exactly equal, up to a
        specified tolerance.
        """
        return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))

    def intersects(self, other):
        "Returns true if disjoint returns false."
        return capi.geos_intersects(self.ptr, other.ptr)

    def overlaps(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
        """
        return capi.geos_overlaps(self.ptr, other.ptr)

    def relate_pattern(self, other, pattern):
        """
        Returns true if the elements in the DE-9IM intersection matrix for the
        two Geometries match the elements in pattern.
        """
        if not isinstance(pattern, basestring) or len(pattern) > 9:
            raise GEOSException('invalid intersection matrix pattern')
        return capi.geos_relatepattern(self.ptr, other.ptr, pattern)

    def touches(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is FT*******, F**T***** or F***T****.
        """
        return capi.geos_touches(self.ptr, other.ptr)

    def within(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*F**F***.
        """
        return capi.geos_within(self.ptr, other.ptr)

    #### SRID Routines ####
    def get_srid(self):
        "Gets the SRID for the geometry, returns None if no SRID is set."
        s = capi.geos_get_srid(self.ptr)
        if s == 0: return None
        else: return s

    def set_srid(self, srid):
        "Sets the SRID for the geometry."
        capi.geos_set_srid(self.ptr, srid)
    srid = property(get_srid, set_srid)

    #### Output Routines ####
    @property
    def ewkt(self):
        """
        Returns the EWKT (WKT + SRID) of the Geometry.  Note that Z values
        are *not* included in this representation because GEOS does not yet
        support serializing them.
        """
        if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
        else: return self.wkt

    @property
    def wkt(self):
        "Returns the WKT (Well-Known Text) representation of this Geometry."
        return wkt_w().write(self)

    @property
    def hex(self):
        """
        Returns the WKB of this Geometry in hexadecimal form.  Please note
        that the SRID and Z values are not included in this representation
        because it is not a part of the OGC specification (use the `hexewkb`
        property instead).
        """
        # A possible faster, all-python, implementation:
        #  str(self.wkb).encode('hex')
        return wkb_w().write_hex(self)

    @property
    def hexewkb(self):
        """
        Returns the EWKB of this Geometry in hexadecimal form.  This is an
        extension of the WKB specification that includes SRID and Z values
        that are a part of this geometry.
        """
        if self.hasz:
            if not GEOS_PREPARE:
                # See: http://trac.osgeo.org/geos/ticket/216
                raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
            return ewkb_w3d().write_hex(self)
        else:
            return ewkb_w().write_hex(self)

    @property
    def json(self):
        """
        Returns GeoJSON representation of this Geometry if GDAL is installed.
        """
        if gdal.HAS_GDAL:
            return self.ogr.json
        else:
            raise GEOSException('GeoJSON output only supported when GDAL is installed.')
    geojson = json

    @property
    def wkb(self):
        """
        Returns the WKB (Well-Known Binary) representation of this Geometry
        as a Python buffer.  SRID and Z values are not included, use the
        `ewkb` property instead.
        """
        return wkb_w().write(self)

    @property
    def ewkb(self):
        """
        Return the EWKB representation of this Geometry as a Python buffer.
        This is an extension of the WKB specification that includes any SRID
        and Z values that are a part of this geometry.
        """
        if self.hasz:
            if not GEOS_PREPARE:
                # See: http://trac.osgeo.org/geos/ticket/216
                raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
            return ewkb_w3d().write(self)
        else:
            return ewkb_w().write(self)

    @property
    def kml(self):
        "Returns the KML representation of this Geometry."
        gtype = self.geom_type
        return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)

    @property
    def prepared(self):
        """
        Returns a PreparedGeometry corresponding to this geometry -- it is
        optimized for the contains, intersects, and covers operations.
        """
        if GEOS_PREPARE:
            return PreparedGeometry(self)
        else:
            raise GEOSException('GEOS 3.1+ required for prepared geometry support.')

    #### GDAL-specific output routines ####
    @property
    def ogr(self):
        "Returns the OGR Geometry for this Geometry."
        if gdal.HAS_GDAL:
            if self.srid:
                return gdal.OGRGeometry(self.wkb, self.srid)
            else:
                return gdal.OGRGeometry(self.wkb)
        else:
            raise GEOSException('GDAL required to convert to an OGRGeometry.')

    @property
    def srs(self):
        "Returns the OSR SpatialReference for SRID of this Geometry."
        if gdal.HAS_GDAL:
            if self.srid:
                return gdal.SpatialReference(self.srid)
            else:
                return None
        else:
            raise GEOSException('GDAL required to return a SpatialReference object.')

    @property
    def crs(self):
        "Alias for `srs` property."
        return self.srs

    def transform(self, ct, clone=False):
        """
        Requires GDAL. Transforms the geometry according to the given
        transformation object, which may be an integer SRID, and WKT or
        PROJ.4 string. By default, the geometry is transformed in-place and
        nothing is returned. However if the `clone` keyword is set, then this
        geometry will not be modified and a transformed clone will be returned
        instead.
        """
        srid = self.srid

        if ct == srid:
            # short-circuit where source & dest SRIDs match
            if clone:
                return self.clone()
            else:
                return

        if (srid is None) or (srid < 0):
            raise GEOSException("Calling transform() with no SRID set is not supported")

        if not gdal.HAS_GDAL:
            raise GEOSException("GDAL library is not available to transform() geometry.")

        # Creating an OGR Geometry, which is then transformed.
        g = gdal.OGRGeometry(self.wkb, srid)
        g.transform(ct)
        # Getting a new GEOS pointer
        ptr = wkb_r().read(g.wkb)
        if clone:
            # User wants a cloned transformed geometry returned.
            return GEOSGeometry(ptr, srid=g.srid)
        if ptr:
            # Reassigning pointer, and performing post-initialization setup
            # again due to the reassignment.
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
            self._post_init(g.srid)
        else:
            raise GEOSException('Transformed WKB was invalid.')

    #### Topology Routines ####
    def _topology(self, gptr):
        "Helper routine to return Geometry from the given pointer."
        return GEOSGeometry(gptr, srid=self.srid)

    @property
    def boundary(self):
        "Returns the boundary as a newly allocated Geometry object."
        return self._topology(capi.geos_boundary(self.ptr))

    def buffer(self, width, quadsegs=8):
        """
        Returns a geometry that represents all points whose distance from this
        Geometry is less than or equal to distance. Calculations are in the
        Spatial Reference System of this Geometry. The optional third parameter sets
        the number of segment used to approximate a quarter circle (defaults to 8).
        (Text from PostGIS documentation at ch. 6.1.3)
        """
        return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))

    @property
    def centroid(self):
        """
        The centroid is equal to the centroid of the set of component Geometries
        of highest dimension (since the lower-dimension geometries contribute zero
        "weight" to the centroid).
        """
        return self._topology(capi.geos_centroid(self.ptr))

    @property
    def convex_hull(self):
        """
        Returns the smallest convex Polygon that contains all the points
        in the Geometry.
        """
        return self._topology(capi.geos_convexhull(self.ptr))

    def difference(self, other):
        """
        Returns a Geometry representing the points making up this Geometry
        that do not make up other.
        """
        return self._topology(capi.geos_difference(self.ptr, other.ptr))

    @property
    def envelope(self):
        "Return the envelope for this geometry (a polygon)."
        return self._topology(capi.geos_envelope(self.ptr))

    def intersection(self, other):
        "Returns a Geometry representing the points shared by this Geometry and other."
        return self._topology(capi.geos_intersection(self.ptr, other.ptr))

    @property
    def point_on_surface(self):
        "Computes an interior point of this Geometry."
        return self._topology(capi.geos_pointonsurface(self.ptr))

    def relate(self, other):
        "Returns the DE-9IM intersection matrix for this Geometry and the other."
        return capi.geos_relate(self.ptr, other.ptr)

    def simplify(self, tolerance=0.0, preserve_topology=False):
        """
        Returns the Geometry, simplified using the Douglas-Peucker algorithm
        to the specified tolerance (higher tolerance => less points).  If no
        tolerance provided, defaults to 0.

        By default, this function does not preserve topology - e.g. polygons can
        be split, collapse to lines or disappear holes can be created or
        disappear, and lines can cross. By specifying preserve_topology=True,
        the result will have the same dimension and number of components as the
        input. This is significantly slower.
        """
        if preserve_topology:
            return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
        else:
            return self._topology(capi.geos_simplify(self.ptr, tolerance))

    def sym_difference(self, other):
        """
        Returns a set combining the points in this Geometry not in other,
        and the points in other not in this Geometry.
        """
        return self._topology(capi.geos_symdifference(self.ptr, other.ptr))

    def union(self, other):
        "Returns a Geometry representing all the points in this Geometry and other."
        return self._topology(capi.geos_union(self.ptr, other.ptr))

    #### Other Routines ####
    @property
    def area(self):
        "Returns the area of the Geometry."
        return capi.geos_area(self.ptr, byref(c_double()))

    def distance(self, other):
        """
        Returns the distance between the closest points on this Geometry
        and the other. Units will be in those of the coordinate system of
        the Geometry.
        """
        if not isinstance(other, GEOSGeometry):
            raise TypeError('distance() works only on other GEOS Geometries.')
        return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))

    @property
    def extent(self):
        """
        Returns the extent of this geometry as a 4-tuple, consisting of
        (xmin, ymin, xmax, ymax).
        """
        env = self.envelope
        if isinstance(env, Point):
            xmin, ymin = env.tuple
            xmax, ymax = xmin, ymin
        else:
            xmin, ymin = env[0][0]
            xmax, ymax = env[0][2]
        return (xmin, ymin, xmax, ymax)

    @property
    def length(self):
        """
        Returns the length of this Geometry (e.g., 0 for point, or the
        circumfrence of a Polygon).
        """
        return capi.geos_length(self.ptr, byref(c_double()))

    def clone(self):
        "Clones this Geometry."
        return GEOSGeometry(capi.geom_clone(self.ptr), srid=self.srid)
Exemple #6
0
 def _set_cs(self):
     "Sets the coordinate sequence for this Geometry."
     if self.has_cs:
         self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
     else:
         self._cs = None
Exemple #7
0
class GEOSGeometry(GEOSBase, ListMixin):
    "A class that, generally, encapsulates a GEOS geometry."

    # Raise GEOSIndexError instead of plain IndexError
    # (see ticket #4740 and GEOSIndexError docstring)
    _IndexError = GEOSIndexError

    ptr_type = GEOM_PTR

    def __init__(self, geo_input, srid=None):
        """
        The base constructor for GEOS geometry objects, and may take the
        following inputs:

         * strings:
            - WKT
            - HEXEWKB (a PostGIS-specific canonical form)
            - GeoJSON (requires GDAL)
         * buffer:
            - WKB

        The `srid` keyword is used to specify the Source Reference Identifier
        (SRID) number for this Geometry.  If not set, the SRID will be None.
        """
        if isinstance(geo_input, bytes):
            geo_input = force_text(geo_input)
        if isinstance(geo_input, six.string_types):
            wkt_m = wkt_regex.match(geo_input)
            if wkt_m:
                # Handling WKT input.
                if wkt_m.group('srid'):
                    srid = int(wkt_m.group('srid'))
                g = wkt_r().read(force_bytes(wkt_m.group('wkt')))
            elif hex_regex.match(geo_input):
                # Handling HEXEWKB input.
                g = wkb_r().read(force_bytes(geo_input))
            elif json_regex.match(geo_input):
                # Handling GeoJSON input.
                if not gdal.HAS_GDAL:
                    raise ValueError(
                        'Initializing geometry from JSON input requires GDAL.')
                g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb)
            else:
                raise ValueError(
                    'String or unicode input unrecognized as WKT EWKT, and HEXEWKB.'
                )
        elif isinstance(geo_input, GEOM_PTR):
            # When the input is a pointer to a geometry (GEOM_PTR).
            g = geo_input
        elif isinstance(geo_input, six.memoryview):
            # When the input is a buffer (WKB).
            g = wkb_r().read(geo_input)
        elif isinstance(geo_input, GEOSGeometry):
            g = capi.geom_clone(geo_input.ptr)
        else:
            # Invalid geometry type.
            raise TypeError('Improper geometry input type: %s' %
                            str(type(geo_input)))

        if g:
            # Setting the pointer object with a valid pointer.
            self.ptr = g
        else:
            raise GEOSException(
                'Could not initialize GEOS Geometry with given input.')

        # Post-initialization setup.
        self._post_init(srid)

    def _post_init(self, srid):
        "Helper routine for performing post-initialization setup."
        # Setting the SRID, if given.
        if srid and isinstance(srid, int):
            self.srid = srid

        # Setting the class type (e.g., Point, Polygon, etc.)
        self.__class__ = GEOS_CLASSES[self.geom_typeid]

        # Setting the coordinate sequence for the geometry (will be None on
        # geometries that do not have coordinate sequences)
        self._set_cs()

    def __del__(self):
        """
        Destroys this Geometry; in other words, frees the memory used by the
        GEOS C++ object.
        """
        if self._ptr and capi:
            capi.destroy_geom(self._ptr)

    def __copy__(self):
        """
        Returns a clone because the copy of a GEOSGeometry may contain an
        invalid pointer location if the original is garbage collected.
        """
        return self.clone()

    def __deepcopy__(self, memodict):
        """
        The `deepcopy` routine is used by the `Node` class of django.utils.tree;
        thus, the protocol routine needs to be implemented to return correct
        copies (clones) of these GEOS objects, which use C pointers.
        """
        return self.clone()

    def __str__(self):
        "EWKT is used for the string representation."
        return self.ewkt

    def __repr__(self):
        "Short-hand representation because WKT may be very large."
        return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))

    # Pickling support
    def __getstate__(self):
        # The pickled state is simply a tuple of the WKB (in string form)
        # and the SRID.
        return bytes(self.wkb), self.srid

    def __setstate__(self, state):
        # Instantiating from the tuple state that was pickled.
        wkb, srid = state
        ptr = wkb_r().read(six.memoryview(wkb))
        if not ptr:
            raise GEOSException('Invalid Geometry loaded from pickled state.')
        self.ptr = ptr
        self._post_init(srid)

    # Comparison operators
    def __eq__(self, other):
        """
        Equivalence testing, a Geometry may be compared with another Geometry
        or a WKT representation.
        """
        if isinstance(other, six.string_types):
            return self.wkt == other
        elif isinstance(other, GEOSGeometry):
            return self.equals_exact(other)
        else:
            return False

    def __ne__(self, other):
        "The not equals operator."
        return not (self == other)

    # ### Geometry set-like operations ###
    # Thanks to Sean Gillies for inspiration:
    #  http://lists.gispython.org/pipermail/community/2007-July/001034.html
    # g = g1 | g2
    def __or__(self, other):
        "Returns the union of this Geometry and the other."
        return self.union(other)

    # g = g1 & g2
    def __and__(self, other):
        "Returns the intersection of this Geometry and the other."
        return self.intersection(other)

    # g = g1 - g2
    def __sub__(self, other):
        "Return the difference this Geometry and the other."
        return self.difference(other)

    # g = g1 ^ g2
    def __xor__(self, other):
        "Return the symmetric difference of this Geometry and the other."
        return self.sym_difference(other)

    # #### Coordinate Sequence Routines ####
    @property
    def has_cs(self):
        "Returns True if this Geometry has a coordinate sequence, False if not."
        # Only these geometries are allowed to have coordinate sequences.
        if isinstance(self, (Point, LineString, LinearRing)):
            return True
        else:
            return False

    def _set_cs(self):
        "Sets the coordinate sequence for this Geometry."
        if self.has_cs:
            self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
        else:
            self._cs = None

    @property
    def coord_seq(self):
        "Returns a clone of the coordinate sequence for this Geometry."
        if self.has_cs:
            return self._cs.clone()

    # #### Geometry Info ####
    @property
    def geom_type(self):
        "Returns a string representing the Geometry type, e.g. 'Polygon'"
        return capi.geos_type(self.ptr).decode()

    @property
    def geom_typeid(self):
        "Returns an integer representing the Geometry type."
        return capi.geos_typeid(self.ptr)

    @property
    def num_geom(self):
        "Returns the number of geometries in the Geometry."
        return capi.get_num_geoms(self.ptr)

    @property
    def num_coords(self):
        "Returns the number of coordinates in the Geometry."
        return capi.get_num_coords(self.ptr)

    @property
    def num_points(self):
        "Returns the number points, or coordinates, in the Geometry."
        return self.num_coords

    @property
    def dims(self):
        "Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
        return capi.get_dims(self.ptr)

    def normalize(self):
        "Converts this Geometry to normal form (or canonical form)."
        return capi.geos_normalize(self.ptr)

    # #### Unary predicates ####
    @property
    def empty(self):
        """
        Returns a boolean indicating whether the set of points in this Geometry
        are empty.
        """
        return capi.geos_isempty(self.ptr)

    @property
    def hasz(self):
        "Returns whether the geometry has a 3D dimension."
        return capi.geos_hasz(self.ptr)

    @property
    def ring(self):
        "Returns whether or not the geometry is a ring."
        return capi.geos_isring(self.ptr)

    @property
    def simple(self):
        "Returns false if the Geometry not simple."
        return capi.geos_issimple(self.ptr)

    @property
    def valid(self):
        "This property tests the validity of this Geometry."
        return capi.geos_isvalid(self.ptr)

    @property
    def valid_reason(self):
        """
        Returns a string containing the reason for any invalidity.
        """
        return capi.geos_isvalidreason(self.ptr).decode()

    # #### Binary predicates. ####
    def contains(self, other):
        "Returns true if other.within(this) returns true."
        return capi.geos_contains(self.ptr, other.ptr)

    def crosses(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*T****** (for a point and a curve,a point and an area or a line and
        an area) 0******** (for two curves).
        """
        return capi.geos_crosses(self.ptr, other.ptr)

    def disjoint(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is FF*FF****.
        """
        return capi.geos_disjoint(self.ptr, other.ptr)

    def equals(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*F**FFF*.
        """
        return capi.geos_equals(self.ptr, other.ptr)

    def equals_exact(self, other, tolerance=0):
        """
        Returns true if the two Geometries are exactly equal, up to a
        specified tolerance.
        """
        return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))

    def intersects(self, other):
        "Returns true if disjoint returns false."
        return capi.geos_intersects(self.ptr, other.ptr)

    def overlaps(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
        """
        return capi.geos_overlaps(self.ptr, other.ptr)

    def relate_pattern(self, other, pattern):
        """
        Returns true if the elements in the DE-9IM intersection matrix for the
        two Geometries match the elements in pattern.
        """
        if not isinstance(pattern, six.string_types) or len(pattern) > 9:
            raise GEOSException('invalid intersection matrix pattern')
        return capi.geos_relatepattern(self.ptr, other.ptr,
                                       force_bytes(pattern))

    def touches(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is FT*******, F**T***** or F***T****.
        """
        return capi.geos_touches(self.ptr, other.ptr)

    def within(self, other):
        """
        Returns true if the DE-9IM intersection matrix for the two Geometries
        is T*F**F***.
        """
        return capi.geos_within(self.ptr, other.ptr)

    # #### SRID Routines ####
    def get_srid(self):
        "Gets the SRID for the geometry, returns None if no SRID is set."
        s = capi.geos_get_srid(self.ptr)
        if s == 0:
            return None
        else:
            return s

    def set_srid(self, srid):
        "Sets the SRID for the geometry."
        capi.geos_set_srid(self.ptr, srid)

    srid = property(get_srid, set_srid)

    # #### Output Routines ####
    @property
    def ewkt(self):
        """
        Returns the EWKT (SRID + WKT) of the Geometry. Note that Z values
        are only included in this representation if GEOS >= 3.3.0.
        """
        if self.get_srid():
            return 'SRID=%s;%s' % (self.srid, self.wkt)
        else:
            return self.wkt

    @property
    def wkt(self):
        "Returns the WKT (Well-Known Text) representation of this Geometry."
        return wkt_w(3 if self.hasz else 2).write(self).decode()

    @property
    def hex(self):
        """
        Returns the WKB of this Geometry in hexadecimal form.  Please note
        that the SRID is not included in this representation because it is not
        a part of the OGC specification (use the `hexewkb` property instead).
        """
        # A possible faster, all-python, implementation:
        #  str(self.wkb).encode('hex')
        return wkb_w(3 if self.hasz else 2).write_hex(self)

    @property
    def hexewkb(self):
        """
        Returns the EWKB of this Geometry in hexadecimal form.  This is an
        extension of the WKB specification that includes SRID value that are
        a part of this geometry.
        """
        return ewkb_w(3 if self.hasz else 2).write_hex(self)

    @property
    def json(self):
        """
        Returns GeoJSON representation of this Geometry if GDAL is installed.
        """
        if gdal.HAS_GDAL:
            return self.ogr.json
        else:
            raise GEOSException(
                'GeoJSON output only supported when GDAL is installed.')

    geojson = json

    @property
    def wkb(self):
        """
        Returns the WKB (Well-Known Binary) representation of this Geometry
        as a Python buffer.  SRID and Z values are not included, use the
        `ewkb` property instead.
        """
        return wkb_w(3 if self.hasz else 2).write(self)

    @property
    def ewkb(self):
        """
        Return the EWKB representation of this Geometry as a Python buffer.
        This is an extension of the WKB specification that includes any SRID
        value that are a part of this geometry.
        """
        return ewkb_w(3 if self.hasz else 2).write(self)

    @property
    def kml(self):
        "Returns the KML representation of this Geometry."
        gtype = self.geom_type
        return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)

    @property
    def prepared(self):
        """
        Returns a PreparedGeometry corresponding to this geometry -- it is
        optimized for the contains, intersects, and covers operations.
        """
        return PreparedGeometry(self)

    # #### GDAL-specific output routines ####
    @property
    def ogr(self):
        "Returns the OGR Geometry for this Geometry."
        if not gdal.HAS_GDAL:
            raise GEOSException('GDAL required to convert to an OGRGeometry.')
        if self.srid:
            try:
                return gdal.OGRGeometry(self.wkb, self.srid)
            except SRSException:
                pass
        return gdal.OGRGeometry(self.wkb)

    @property
    def srs(self):
        "Returns the OSR SpatialReference for SRID of this Geometry."
        if not gdal.HAS_GDAL:
            raise GEOSException(
                'GDAL required to return a SpatialReference object.')
        if self.srid:
            try:
                return gdal.SpatialReference(self.srid)
            except SRSException:
                pass
        return None

    @property
    def crs(self):
        "Alias for `srs` property."
        return self.srs

    def transform(self, ct, clone=False):
        """
        Requires GDAL. Transforms the geometry according to the given
        transformation object, which may be an integer SRID, and WKT or
        PROJ.4 string. By default, the geometry is transformed in-place and
        nothing is returned. However if the `clone` keyword is set, then this
        geometry will not be modified and a transformed clone will be returned
        instead.
        """
        srid = self.srid

        if ct == srid:
            # short-circuit where source & dest SRIDs match
            if clone:
                return self.clone()
            else:
                return

        if (srid is None) or (srid < 0):
            raise GEOSException(
                "Calling transform() with no SRID set is not supported")

        if not gdal.HAS_GDAL:
            raise GEOSException(
                "GDAL library is not available to transform() geometry.")

        # Creating an OGR Geometry, which is then transformed.
        g = self.ogr
        g.transform(ct)
        # Getting a new GEOS pointer
        ptr = wkb_r().read(g.wkb)
        if clone:
            # User wants a cloned transformed geometry returned.
            return GEOSGeometry(ptr, srid=g.srid)
        if ptr:
            # Reassigning pointer, and performing post-initialization setup
            # again due to the reassignment.
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
            self._post_init(g.srid)
        else:
            raise GEOSException('Transformed WKB was invalid.')

    # #### Topology Routines ####
    def _topology(self, gptr):
        "Helper routine to return Geometry from the given pointer."
        return GEOSGeometry(gptr, srid=self.srid)

    @property
    def boundary(self):
        "Returns the boundary as a newly allocated Geometry object."
        return self._topology(capi.geos_boundary(self.ptr))

    def buffer(self, width, quadsegs=8):
        """
        Returns a geometry that represents all points whose distance from this
        Geometry is less than or equal to distance. Calculations are in the
        Spatial Reference System of this Geometry. The optional third parameter sets
        the number of segment used to approximate a quarter circle (defaults to 8).
        (Text from PostGIS documentation at ch. 6.1.3)
        """
        return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))

    @property
    def centroid(self):
        """
        The centroid is equal to the centroid of the set of component Geometries
        of highest dimension (since the lower-dimension geometries contribute zero
        "weight" to the centroid).
        """
        return self._topology(capi.geos_centroid(self.ptr))

    @property
    def convex_hull(self):
        """
        Returns the smallest convex Polygon that contains all the points
        in the Geometry.
        """
        return self._topology(capi.geos_convexhull(self.ptr))

    def difference(self, other):
        """
        Returns a Geometry representing the points making up this Geometry
        that do not make up other.
        """
        return self._topology(capi.geos_difference(self.ptr, other.ptr))

    @property
    def envelope(self):
        "Return the envelope for this geometry (a polygon)."
        return self._topology(capi.geos_envelope(self.ptr))

    def interpolate(self, distance):
        if not isinstance(self, (LineString, MultiLineString)):
            raise TypeError(
                'interpolate only works on LineString and MultiLineString geometries'
            )
        return self._topology(capi.geos_interpolate(self.ptr, distance))

    def interpolate_normalized(self, distance):
        if not isinstance(self, (LineString, MultiLineString)):
            raise TypeError(
                'interpolate only works on LineString and MultiLineString geometries'
            )
        return self._topology(
            capi.geos_interpolate_normalized(self.ptr, distance))

    def intersection(self, other):
        "Returns a Geometry representing the points shared by this Geometry and other."
        return self._topology(capi.geos_intersection(self.ptr, other.ptr))

    @property
    def point_on_surface(self):
        "Computes an interior point of this Geometry."
        return self._topology(capi.geos_pointonsurface(self.ptr))

    def project(self, point):
        if not isinstance(point, Point):
            raise TypeError('locate_point argument must be a Point')
        if not isinstance(self, (LineString, MultiLineString)):
            raise TypeError(
                'locate_point only works on LineString and MultiLineString geometries'
            )
        return capi.geos_project(self.ptr, point.ptr)

    def project_normalized(self, point):
        if not isinstance(point, Point):
            raise TypeError('locate_point argument must be a Point')
        if not isinstance(self, (LineString, MultiLineString)):
            raise TypeError(
                'locate_point only works on LineString and MultiLineString geometries'
            )
        return capi.geos_project_normalized(self.ptr, point.ptr)

    def relate(self, other):
        "Returns the DE-9IM intersection matrix for this Geometry and the other."
        return capi.geos_relate(self.ptr, other.ptr).decode()

    def simplify(self, tolerance=0.0, preserve_topology=False):
        """
        Returns the Geometry, simplified using the Douglas-Peucker algorithm
        to the specified tolerance (higher tolerance => less points).  If no
        tolerance provided, defaults to 0.

        By default, this function does not preserve topology - e.g. polygons can
        be split, collapse to lines or disappear holes can be created or
        disappear, and lines can cross. By specifying preserve_topology=True,
        the result will have the same dimension and number of components as the
        input. This is significantly slower.
        """
        if preserve_topology:
            return self._topology(
                capi.geos_preservesimplify(self.ptr, tolerance))
        else:
            return self._topology(capi.geos_simplify(self.ptr, tolerance))

    def sym_difference(self, other):
        """
        Returns a set combining the points in this Geometry not in other,
        and the points in other not in this Geometry.
        """
        return self._topology(capi.geos_symdifference(self.ptr, other.ptr))

    def union(self, other):
        "Returns a Geometry representing all the points in this Geometry and other."
        return self._topology(capi.geos_union(self.ptr, other.ptr))

    # #### Other Routines ####
    @property
    def area(self):
        "Returns the area of the Geometry."
        return capi.geos_area(self.ptr, byref(c_double()))

    def distance(self, other):
        """
        Returns the distance between the closest points on this Geometry
        and the other. Units will be in those of the coordinate system of
        the Geometry.
        """
        if not isinstance(other, GEOSGeometry):
            raise TypeError('distance() works only on other GEOS Geometries.')
        return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))

    @property
    def extent(self):
        """
        Returns the extent of this geometry as a 4-tuple, consisting of
        (xmin, ymin, xmax, ymax).
        """
        env = self.envelope
        if isinstance(env, Point):
            xmin, ymin = env.tuple
            xmax, ymax = xmin, ymin
        else:
            xmin, ymin = env[0][0]
            xmax, ymax = env[0][2]
        return (xmin, ymin, xmax, ymax)

    @property
    def length(self):
        """
        Returns the length of this Geometry (e.g., 0 for point, or the
        circumference of a Polygon).
        """
        return capi.geos_length(self.ptr, byref(c_double()))

    def clone(self):
        "Clones this Geometry."
        return GEOSGeometry(capi.geom_clone(self.ptr), srid=self.srid)
Exemple #8
0
 def _set_cs(self):
     "Sets the coordinate sequence for this Geometry."
     if self.has_cs:
         self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
     else:
         self._cs = None
Exemple #9
0
 def _post_init(self):
     "Perform post-initialization setup."
     # Setting the coordinate sequence for the geometry (will be None on
     # geometries that do not have coordinate sequences)
     self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None
Exemple #10
0
class GEOSGeometryBase(GEOSBase):

    _GEOS_CLASSES = None

    ptr_type = GEOM_PTR
    destructor = capi.destroy_geom
    has_cs = False  # Only Point, LineString, LinearRing have coordinate sequences

    def __init__(self, ptr, cls):
        self._ptr = ptr

        # Setting the class type (e.g., Point, Polygon, etc.)
        if type(self) in (GEOSGeometryBase, GEOSGeometry):
            if cls is None:
                if GEOSGeometryBase._GEOS_CLASSES is None:
                    # Inner imports avoid import conflicts with GEOSGeometry.
                    from .linestring import LineString, LinearRing
                    from .point import Point
                    from .polygon import Polygon
                    from .collections import (
                        GeometryCollection, MultiPoint, MultiLineString, MultiPolygon,
                    )
                    GEOSGeometryBase._GEOS_CLASSES = {
                        0: Point,
                        1: LineString,
                        2: LinearRing,
                        3: Polygon,
                        4: MultiPoint,
                        5: MultiLineString,
                        6: MultiPolygon,
                        7: GeometryCollection,
                    }
                cls = GEOSGeometryBase._GEOS_CLASSES[self.geom_typeid]
            self.__class__ = cls
        self._post_init()

    def _post_init(self):
        "Perform post-initialization setup."
        # Setting the coordinate sequence for the geometry (will be None on
        # geometries that do not have coordinate sequences)
        self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz) if self.has_cs else None

    def __copy__(self):
        """
        Return a clone because the copy of a GEOSGeometry may contain an
        invalid pointer location if the original is garbage collected.
        """
        return self.clone()

    def __deepcopy__(self, memodict):
        """
        The `deepcopy` routine is used by the `Node` class of django.utils.tree;
        thus, the protocol routine needs to be implemented to return correct
        copies (clones) of these GEOS objects, which use C pointers.
        """
        return self.clone()

    def __str__(self):
        "EWKT is used for the string representation."
        return self.ewkt

    def __repr__(self):
        "Short-hand representation because WKT may be very large."
        return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))

    # Pickling support
    def __getstate__(self):
        # The pickled state is simply a tuple of the WKB (in string form)
        # and the SRID.
        return bytes(self.wkb), self.srid

    def __setstate__(self, state):
        # Instantiating from the tuple state that was pickled.
        wkb, srid = state
        ptr = wkb_r().read(memoryview(wkb))
        if not ptr:
            raise GEOSException('Invalid Geometry loaded from pickled state.')
        self.ptr = ptr
        self._post_init()
        self.srid = srid

    @classmethod
    def _from_wkb(cls, wkb):
        return wkb_r().read(wkb)

    @staticmethod
    def from_ewkt(ewkt):
        ewkt = force_bytes(ewkt)
        srid = None
        parts = ewkt.split(b';', 1)
        if len(parts) == 2:
            srid_part, wkt = parts
            match = re.match(br'SRID=(?P<srid>\-?\d+)', srid_part)
            if not match:
                raise ValueError('EWKT has invalid SRID part.')
            srid = int(match.group('srid'))
        else:
            wkt = ewkt
        if not wkt:
            raise ValueError('Expected WKT but got an empty string.')
        return GEOSGeometry(GEOSGeometry._from_wkt(wkt), srid=srid)

    @staticmethod
    def _from_wkt(wkt):
        return wkt_r().read(wkt)

    @classmethod
    def from_gml(cls, gml_string):
        return gdal.OGRGeometry.from_gml(gml_string).geos

    # Comparison operators
    def __eq__(self, other):
        """
        Equivalence testing, a Geometry may be compared with another Geometry
        or an EWKT representation.
        """
        if isinstance(other, str):
            try:
                other = GEOSGeometry.from_ewkt(other)
            except (ValueError, GEOSException):
                return False
        return isinstance(other, GEOSGeometry) and self.srid == other.srid and self.equals_exact(other)

    def __hash__(self):
        return hash((self.srid, self.wkt))

    # ### Geometry set-like operations ###
    # Thanks to Sean Gillies for inspiration:
    #  http://lists.gispython.org/pipermail/community/2007-July/001034.html
    # g = g1 | g2
    def __or__(self, other):
        "Return the union of this Geometry and the other."
        return self.union(other)

    # g = g1 & g2
    def __and__(self, other):
        "Return the intersection of this Geometry and the other."
        return self.intersection(other)

    # g = g1 - g2
    def __sub__(self, other):
        "Return the difference this Geometry and the other."
        return self.difference(other)

    # g = g1 ^ g2
    def __xor__(self, other):
        "Return the symmetric difference of this Geometry and the other."
        return self.sym_difference(other)

    # #### Coordinate Sequence Routines ####
    @property
    def coord_seq(self):
        "Return a clone of the coordinate sequence for this Geometry."
        if self.has_cs:
            return self._cs.clone()

    # #### Geometry Info ####
    @property
    def geom_type(self):
        "Return a string representing the Geometry type, e.g. 'Polygon'"
        return capi.geos_type(self.ptr).decode()

    @property
    def geom_typeid(self):
        "Return an integer representing the Geometry type."
        return capi.geos_typeid(self.ptr)

    @property
    def num_geom(self):
        "Return the number of geometries in the Geometry."
        return capi.get_num_geoms(self.ptr)

    @property
    def num_coords(self):
        "Return the number of coordinates in the Geometry."
        return capi.get_num_coords(self.ptr)

    @property
    def num_points(self):
        "Return the number points, or coordinates, in the Geometry."
        return self.num_coords

    @property
    def dims(self):
        "Return the dimension of this Geometry (0=point, 1=line, 2=surface)."
        return capi.get_dims(self.ptr)

    def normalize(self):
        "Convert this Geometry to normal form (or canonical form)."
        capi.geos_normalize(self.ptr)

    # #### Unary predicates ####
    @property
    def empty(self):
        """
        Return a boolean indicating whether the set of points in this Geometry
        are empty.
        """
        return capi.geos_isempty(self.ptr)

    @property
    def hasz(self):
        "Return whether the geometry has a 3D dimension."
        return capi.geos_hasz(self.ptr)

    @property
    def ring(self):
        "Return whether or not the geometry is a ring."
        return capi.geos_isring(self.ptr)

    @property
    def simple(self):
        "Return false if the Geometry isn't simple."
        return capi.geos_issimple(self.ptr)

    @property
    def valid(self):
        "Test the validity of this Geometry."
        return capi.geos_isvalid(self.ptr)

    @property
    def valid_reason(self):
        """
        Return a string containing the reason for any invalidity.
        """
        return capi.geos_isvalidreason(self.ptr).decode()

    # #### Binary predicates. ####
    def contains(self, other):
        "Return true if other.within(this) returns true."
        return capi.geos_contains(self.ptr, other.ptr)

    def covers(self, other):
        """
        Return True if the DE-9IM Intersection Matrix for the two geometries is
        T*****FF*, *T****FF*, ***T**FF*, or ****T*FF*. If either geometry is
        empty, return False.
        """
        return capi.geos_covers(self.ptr, other.ptr)

    def crosses(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is T*T****** (for a point and a curve,a point and an area or a line and
        an area) 0******** (for two curves).
        """
        return capi.geos_crosses(self.ptr, other.ptr)

    def disjoint(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is FF*FF****.
        """
        return capi.geos_disjoint(self.ptr, other.ptr)

    def equals(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is T*F**FFF*.
        """
        return capi.geos_equals(self.ptr, other.ptr)

    def equals_exact(self, other, tolerance=0):
        """
        Return true if the two Geometries are exactly equal, up to a
        specified tolerance.
        """
        return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))

    def intersects(self, other):
        "Return true if disjoint return false."
        return capi.geos_intersects(self.ptr, other.ptr)

    def overlaps(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
        """
        return capi.geos_overlaps(self.ptr, other.ptr)

    def relate_pattern(self, other, pattern):
        """
        Return true if the elements in the DE-9IM intersection matrix for the
        two Geometries match the elements in pattern.
        """
        if not isinstance(pattern, str) or len(pattern) > 9:
            raise GEOSException('invalid intersection matrix pattern')
        return capi.geos_relatepattern(self.ptr, other.ptr, force_bytes(pattern))

    def touches(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is FT*******, F**T***** or F***T****.
        """
        return capi.geos_touches(self.ptr, other.ptr)

    def within(self, other):
        """
        Return true if the DE-9IM intersection matrix for the two Geometries
        is T*F**F***.
        """
        return capi.geos_within(self.ptr, other.ptr)

    # #### SRID Routines ####
    @property
    def srid(self):
        "Get the SRID for the geometry. Return None if no SRID is set."
        s = capi.geos_get_srid(self.ptr)
        if s == 0:
            return None
        else:
            return s

    @srid.setter
    def srid(self, srid):
        "Set the SRID for the geometry."
        capi.geos_set_srid(self.ptr, 0 if srid is None else srid)

    # #### Output Routines ####
    @property
    def ewkt(self):
        """
        Return the EWKT (SRID + WKT) of the Geometry.
        """
        srid = self.srid
        return 'SRID=%s;%s' % (srid, self.wkt) if srid else self.wkt

    @property
    def wkt(self):
        "Return the WKT (Well-Known Text) representation of this Geometry."
        return wkt_w(dim=3 if self.hasz else 2, trim=True).write(self).decode()

    @property
    def hex(self):
        """
        Return the WKB of this Geometry in hexadecimal form. Please note
        that the SRID is not included in this representation because it is not
        a part of the OGC specification (use the `hexewkb` property instead).
        """
        # A possible faster, all-python, implementation:
        #  str(self.wkb).encode('hex')
        return wkb_w(dim=3 if self.hasz else 2).write_hex(self)

    @property
    def hexewkb(self):
        """
        Return the EWKB of this Geometry in hexadecimal form. This is an
        extension of the WKB specification that includes SRID value that are
        a part of this geometry.
        """
        return ewkb_w(dim=3 if self.hasz else 2).write_hex(self)

    @property
    def json(self):
        """
        Return GeoJSON representation of this Geometry.
        """
        return self.ogr.json
    geojson = json

    @property
    def wkb(self):
        """
        Return the WKB (Well-Known Binary) representation of this Geometry
        as a Python buffer.  SRID and Z values are not included, use the
        `ewkb` property instead.
        """
        return wkb_w(3 if self.hasz else 2).write(self)

    @property
    def ewkb(self):
        """
        Return the EWKB representation of this Geometry as a Python buffer.
        This is an extension of the WKB specification that includes any SRID
        value that are a part of this geometry.
        """
        return ewkb_w(3 if self.hasz else 2).write(self)

    @property
    def kml(self):
        "Return the KML representation of this Geometry."
        gtype = self.geom_type
        return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)

    @property
    def prepared(self):
        """
        Return a PreparedGeometry corresponding to this geometry -- it is
        optimized for the contains, intersects, and covers operations.
        """
        return PreparedGeometry(self)

    # #### GDAL-specific output routines ####
    def _ogr_ptr(self):
        return gdal.OGRGeometry._from_wkb(self.wkb)

    @property
    def ogr(self):
        "Return the OGR Geometry for this Geometry."
        return gdal.OGRGeometry(self._ogr_ptr(), self.srs)

    @property
    def srs(self):
        "Return the OSR SpatialReference for SRID of this Geometry."
        if self.srid:
            try:
                return gdal.SpatialReference(self.srid)
            except gdal.SRSException:
                pass
        return None

    @property
    def crs(self):
        "Alias for `srs` property."
        return self.srs

    def transform(self, ct, clone=False):
        """
        Requires GDAL. Transform the geometry according to the given
        transformation object, which may be an integer SRID, and WKT or
        PROJ.4 string. By default, transform the geometry in-place and return
        nothing. However if the `clone` keyword is set, don't modify the
        geometry and return a transformed clone instead.
        """
        srid = self.srid

        if ct == srid:
            # short-circuit where source & dest SRIDs match
            if clone:
                return self.clone()
            else:
                return

        if isinstance(ct, gdal.CoordTransform):
            # We don't care about SRID because CoordTransform presupposes
            # source SRS.
            srid = None
        elif srid is None or srid < 0:
            raise GEOSException("Calling transform() with no SRID set is not supported")

        # Creating an OGR Geometry, which is then transformed.
        g = gdal.OGRGeometry(self._ogr_ptr(), srid)
        g.transform(ct)
        # Getting a new GEOS pointer
        ptr = g._geos_ptr()
        if clone:
            # User wants a cloned transformed geometry returned.
            return GEOSGeometry(ptr, srid=g.srid)
        if ptr:
            # Reassigning pointer, and performing post-initialization setup
            # again due to the reassignment.
            capi.destroy_geom(self.ptr)
            self.ptr = ptr
            self._post_init()
            self.srid = g.srid
        else:
            raise GEOSException('Transformed WKB was invalid.')

    # #### Topology Routines ####
    def _topology(self, gptr):
        "Return Geometry from the given pointer."
        return GEOSGeometry(gptr, srid=self.srid)

    @property
    def boundary(self):
        "Return the boundary as a newly allocated Geometry object."
        return self._topology(capi.geos_boundary(self.ptr))

    def buffer(self, width, quadsegs=8):
        """
        Return a geometry that represents all points whose distance from this
        Geometry is less than or equal to distance. Calculations are in the
        Spatial Reference System of this Geometry. The optional third parameter sets
        the number of segment used to approximate a quarter circle (defaults to 8).
        (Text from PostGIS documentation at ch. 6.1.3)
        """
        return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))

    @property
    def centroid(self):
        """
        The centroid is equal to the centroid of the set of component Geometries
        of highest dimension (since the lower-dimension geometries contribute zero
        "weight" to the centroid).
        """
        return self._topology(capi.geos_centroid(self.ptr))

    @property
    def convex_hull(self):
        """
        Return the smallest convex Polygon that contains all the points
        in the Geometry.
        """
        return self._topology(capi.geos_convexhull(self.ptr))

    def difference(self, other):
        """
        Return a Geometry representing the points making up this Geometry
        that do not make up other.
        """
        return self._topology(capi.geos_difference(self.ptr, other.ptr))

    @property
    def envelope(self):
        "Return the envelope for this geometry (a polygon)."
        return self._topology(capi.geos_envelope(self.ptr))

    def intersection(self, other):
        "Return a Geometry representing the points shared by this Geometry and other."
        return self._topology(capi.geos_intersection(self.ptr, other.ptr))

    @property
    def point_on_surface(self):
        "Compute an interior point of this Geometry."
        return self._topology(capi.geos_pointonsurface(self.ptr))

    def relate(self, other):
        "Return the DE-9IM intersection matrix for this Geometry and the other."
        return capi.geos_relate(self.ptr, other.ptr).decode()

    def simplify(self, tolerance=0.0, preserve_topology=False):
        """
        Return the Geometry, simplified using the Douglas-Peucker algorithm
        to the specified tolerance (higher tolerance => less points).  If no
        tolerance provided, defaults to 0.

        By default, don't preserve topology - e.g. polygons can be split,
        collapse to lines or disappear holes can be created or disappear, and
        lines can cross. By specifying preserve_topology=True, the result will
        have the same dimension and number of components as the input. This is
        significantly slower.
        """
        if preserve_topology:
            return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
        else:
            return self._topology(capi.geos_simplify(self.ptr, tolerance))

    def sym_difference(self, other):
        """
        Return a set combining the points in this Geometry not in other,
        and the points in other not in this Geometry.
        """
        return self._topology(capi.geos_symdifference(self.ptr, other.ptr))

    @property
    def unary_union(self):
        "Return the union of all the elements of this geometry."
        return self._topology(capi.geos_unary_union(self.ptr))

    def union(self, other):
        "Return a Geometry representing all the points in this Geometry and other."
        return self._topology(capi.geos_union(self.ptr, other.ptr))

    # #### Other Routines ####
    @property
    def area(self):
        "Return the area of the Geometry."
        return capi.geos_area(self.ptr, byref(c_double()))

    def distance(self, other):
        """
        Return the distance between the closest points on this Geometry
        and the other. Units will be in those of the coordinate system of
        the Geometry.
        """
        if not isinstance(other, GEOSGeometry):
            raise TypeError('distance() works only on other GEOS Geometries.')
        return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))

    @property
    def extent(self):
        """
        Return the extent of this geometry as a 4-tuple, consisting of
        (xmin, ymin, xmax, ymax).
        """
        from .point import Point
        env = self.envelope
        if isinstance(env, Point):
            xmin, ymin = env.tuple
            xmax, ymax = xmin, ymin
        else:
            xmin, ymin = env[0][0]
            xmax, ymax = env[0][2]
        return (xmin, ymin, xmax, ymax)

    @property
    def length(self):
        """
        Return the length of this Geometry (e.g., 0 for point, or the
        circumference of a Polygon).
        """
        return capi.geos_length(self.ptr, byref(c_double()))

    def clone(self):
        "Clone this Geometry."
        return GEOSGeometry(capi.geom_clone(self.ptr))
Exemple #11
0
 def nearest_points(self, other):
     "Returns nearest point of other geometry to self."
     return GEOSCoordSeq(capi.nearest_points(self.ptr, other.ptr))
Exemple #12
0
class GEOSGeometry(GEOSBase, ListMixin):
    "A class that, generally, encapsulates a GEOS geometry."

    # Raise GEOSIndexError instead of plain IndexError
    # (see ticket #4740 and GEOSIndexError docstring)
    _IndexError = GEOSIndexError
    _GEOS_CLASSES = None

    ptr_type = GEOM_PTR
    has_cs = False  # Only Point, LineString, LinearRing have coordinate sequences

    def __init__(self, geo_input, srid=None):
        """
        The base constructor for GEOS geometry objects, and may take the
        following inputs:

         * strings:
            - WKT
            - HEXEWKB (a PostGIS-specific canonical form)
            - GeoJSON (requires GDAL)
         * buffer:
            - WKB

        The `srid` keyword is used to specify the Source Reference Identifier
        (SRID) number for this Geometry.  If not set, the SRID will be None.
        """
        if isinstance(geo_input, bytes):
            geo_input = force_text(geo_input)
        if isinstance(geo_input, six.string_types):
            wkt_m = wkt_regex.match(geo_input)
            if wkt_m:
                # Handling WKT input.
                if wkt_m.group('srid'):
                    srid = int(wkt_m.group('srid'))
                g = wkt_r().read(force_bytes(wkt_m.group('wkt')))
            elif hex_regex.match(geo_input):
                # Handling HEXEWKB input.
                g = wkb_r().read(force_bytes(geo_input))
            elif json_regex.match(geo_input):
                # Handling GeoJSON input.
                if not gdal.HAS_GDAL:
                    raise ValueError('Initializing geometry from JSON input requires GDAL.')
                g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb)
            else:
                raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
        elif isinstance(geo_input, GEOM_PTR):
            # When the input is a pointer to a geometry (GEOM_PTR).
            g = geo_input
        elif isinstance(geo_input, six.memoryview):
            # When the input is a buffer (WKB).
            g = wkb_r().read(geo_input)
        elif isinstance(geo_input, GEOSGeometry):
            g = capi.geom_clone(geo_input.ptr)
        else:
            # Invalid geometry type.
            raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))

        if g:
            # Setting the pointer object with a valid pointer.
            self.ptr = g
        else:
            raise GEOSException('Could not initialize GEOS Geometry with given input.')

        # Post-initialization setup.
        self._post_init(srid)

    def _post_init(self, srid):
        "Helper routine for performing post-initialization setup."
        # Setting the SRID, if given.
        if srid and isinstance(srid, int):
            self.srid = srid

        # Setting the class type (e.g., Point, Polygon, etc.)
        if GEOSGeometry._GEOS_CLASSES is None:
            # Lazy-loaded variable to avoid import conflicts with GEOSGeometry.
            from .linestring import LineString, LinearRing
            from .point import Point
            from .polygon import Polygon
            from .collections import (
                GeometryCollection, MultiPoint, MultiLineString, MultiPolygon)
            GEOSGeometry._GEOS_CLASSES = {
                0: Point,
                1: LineString,
                2: LinearRing,
                3: Polygon,
                4: MultiPoint,
                5: MultiLineString,
                6: MultiPolygon,
                7: GeometryCollection,
            }
        self.__class__ = GEOSGeometry._GEOS_CLASSES[self.geom_typeid]

        # Setting the coordinate sequence for the geometry (will be None on
        # geometries that do not have coordinate sequences)
        self._set_cs()

    def __del__(self):
        """
        Destroys this Geometry; in other words, frees the memory used by the
        GEOS C++ object.
        """
        if self._ptr and capi:
            capi.destroy_geom(self._ptr)

    def __copy__(self):
        """
        Returns a clone because the copy of a GEOSGeometry may contain an
        invalid pointer location if the original is garbage collected.
        """
        return self.clone()

    def __deepcopy__(self, memodict):
        """
        The `deepcopy` routine is used by the `Node` class of django.utils.tree;
        thus, the protocol routine needs to be implemented to return correct
        copies (clones) of these GEOS objects, which use C pointers.
        """
        return self.clone()

    def __str__(self):
        "EWKT is used for the string representation."
        return self.ewkt

    def __repr__(self):
        "Short-hand representation because WKT may be very large."
        return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))

    # Pickling support
    def __getstate__(self):
        # The pickled state is simply a tuple of the WKB (in string form)
        # and the SRID.
        return bytes(self.wkb), self.srid

    def __setstate__(self, state):
        # Instantiating from the tuple state that was pickled.
        wkb, srid = state
        ptr = wkb_r().read(six.memoryview(wkb))
        if not ptr:
            raise GEOSException('Invalid Geometry loaded from pickled state.')
        self.ptr = ptr
        self._post_init(srid)

    # Comparison operators
    def __eq__(self, other):
        """
        Equivalence testing, a Geometry may be compared with another Geometry
        or a WKT representation.
        """
        if isinstance(other, six.string_types):
            return self.wkt == other
        elif isinstance(other, GEOSGeometry):
            return self.equals_exact(other)
        else:
            return False

    def __ne__(self, other):
        "The not equals operator."
        return not (self == other)

    # ### Geometry set-like operations ###
    # Thanks to Sean Gillies for inspiration:
    #  http://lists.gispython.org/pipermail/community/2007-July/001034.html
    # g = g1 | g2
    def __or__(self, other):
        "Returns the union of this Geometry and the other."
        return self.union(other)

    # g = g1 & g2
    def __and__(self, other):
        "Returns the intersection of this Geometry and the other."
        return self.intersection(other)

    # g = g1 - g2
    def __sub__(self, other):
        "Return the difference this Geometry and the other."
        return self.difference(other)

    # g = g1 ^ g2
    def __xor__(self, other):
        "Return the symmetric difference of this Geometry and the other."
        return self.sym_difference(other)

    # #### Coordinate Sequence Routines ####
    def _set_cs(self):
        "Sets the coordinate sequence for this Geometry."
        if self.has_cs:
            self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
        else:
            self._cs = None

    @property
    def coord_seq(self):
        "Returns a clone of the coordinate sequence for this Geometry."
        if self.has_cs:
            return self._cs.clone()

    # #### Geometry Info ####
    @property
    def geom_type(self):
        "Returns a string representing the Geometry type, e.g. 'Polygon'"
        return capi.geos_type(self.ptr).decode()
                    self._checkdim(ndim)
                elif len(coord) != ndim:
                    raise TypeError('Dimension mismatch.')
            numpy_coords = False
        else:
            shape = coords.shape  # Using numpy's shape.
            if len(shape) != 2:
                raise TypeError('Too many dimensions.')
            self._checkdim(shape[1])
            ndim = shape[1]
            numpy_coords = True

        # Creating a coordinate sequence object because it is easier to
<<<<<<< HEAD
        # set the points using GEOSCoordSeq.__setitem__().
        cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim == 3))

        for i in range(ncoords):
            if numpy_coords:
                cs[i] = coords[i, :]
            elif isinstance(coords[i], Point):
                cs[i] = coords[i].tuple
            else:
                cs[i] = coords[i]

        # Calling the base geometry initialization with the returned pointer
        #  from the function.
        super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)

    def __iter__(self):
        "Allows iteration over this LineString."