Example #1
0
    def _split_line_with_line(line, splitter):
        """Split a LineString with another (Multi)LineString or (Multi)Polygon"""

        # if splitter is a polygon, pick it's boundary
        if splitter.type in ("Polygon", "MultiPolygon"):
            splitter = splitter.boundary

        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, LineString) and not isinstance(
                splitter, MultiLineString):
            raise GeometryTypeError(
                "Second argument must be either a LineString or a MultiLineString"
            )

        # |    s\l   | Interior | Boundary | Exterior |
        # |----------|----------|----------|----------|
        # | Interior |  0 or F  |    *     |    *     |   At least one of these two must be 0
        # | Boundary |  0 or F  |    *     |    *     |   So either '0********' or '[0F]**0*****'
        # | Exterior |    *     |    *     |    *     |   No overlapping interiors ('1********')
        relation = splitter.relate(line)
        if relation[0] == "1":
            # The lines overlap at some segment (linear intersection of interiors)
            raise ValueError(
                "Input geometry segment overlaps with the splitter.")
        elif relation[0] == "0" or relation[3] == "0":
            # The splitter crosses or touches the line's interior --> return multilinestring from the split
            return line.difference(splitter)
        else:
            # The splitter does not cross or touch the line's interior --> return collection with identity line
            return [line]
Example #2
0
    def _split_line_with_multipoint(line, splitter):
        """Split a LineString with a MultiPoint"""

        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, MultiPoint):
            raise GeometryTypeError("Second argument must be a MultiPoint")

        chunks = [line]
        for pt in splitter.geoms:
            new_chunks = []
            for chunk in filter(lambda x: not x.is_empty, chunks):
                # add the newly split 2 lines or the same line if not split
                new_chunks.extend(SplitOp._split_line_with_point(chunk, pt))
            chunks = new_chunks

        return chunks
Example #3
0
def shape(context):
    """
    Returns a new, independent geometry with coordinates *copied* from the
    context. Changes to the original context will not be reflected in the
    geometry object.

    Parameters
    ----------
    context :
        a GeoJSON-like dict, which provides a "type" member describing the type
        of the geometry and "coordinates" member providing a list of coordinates,
        or an object which implements __geo_interface__.

    Returns
    -------
    Geometry object

    Examples
    --------
    Create a Point from GeoJSON, and then create a copy using __geo_interface__.

    >>> context = {'type': 'Point', 'coordinates': [0, 1]}
    >>> geom = shape(context)
    >>> geom.type == 'Point'
    True
    >>> geom.wkt
    'POINT (0 1)'
    >>> geom2 = shape(geom)
    >>> geom == geom2
    True
    """
    if hasattr(context, "__geo_interface__"):
        ob = context.__geo_interface__
    else:
        ob = context
    geom_type = ob.get("type").lower()
    if "coordinates" in ob and _is_coordinates_empty(ob["coordinates"]):
        return _empty_shape_for_no_coordinates(geom_type)
    elif geom_type == "point":
        return Point(ob["coordinates"])
    elif geom_type == "linestring":
        return LineString(ob["coordinates"])
    elif geom_type == "linearring":
        return LinearRing(ob["coordinates"])
    elif geom_type == "polygon":
        return Polygon(ob["coordinates"][0], ob["coordinates"][1:])
    elif geom_type == "multipoint":
        return MultiPoint(ob["coordinates"])
    elif geom_type == "multilinestring":
        return MultiLineString(ob["coordinates"])
    elif geom_type == "multipolygon":
        return MultiPolygon([[c[0], c[1:]] for c in ob["coordinates"]])
    elif geom_type == "geometrycollection":
        geoms = [shape(g) for g in ob.get("geometries", [])]
        return GeometryCollection(geoms)
    else:
        raise GeometryTypeError("Unknown geometry type: %s" % geom_type)
Example #4
0
def shared_paths(g1, g2):
    """Find paths shared between the two given lineal geometries

    Returns a GeometryCollection with two elements:
     - First element is a MultiLineString containing shared paths with the
       same direction for both inputs.
     - Second element is a MultiLineString containing shared paths with the
       opposite direction for the two inputs.

    Parameters
    ----------
    g1 : geometry
        The first geometry
    g2 : geometry
        The second geometry
    """
    if not isinstance(g1, LineString):
        raise GeometryTypeError("First geometry must be a LineString")
    if not isinstance(g2, LineString):
        raise GeometryTypeError("Second geometry must be a LineString")
    return shapely.shared_paths(g1, g2)
Example #5
0
    def _split_polygon_with_line(poly, splitter):
        """Split a Polygon with a LineString"""
        if not isinstance(poly, Polygon):
            raise GeometryTypeError("First argument must be a Polygon")
        if not isinstance(splitter, LineString):
            raise GeometryTypeError("Second argument must be a LineString")

        union = poly.boundary.union(splitter)

        # greatly improves split performance for big geometries with many
        # holes (the following contains checks) with minimal overhead
        # for common cases
        poly = prep(poly)

        # some polygonized geometries may be holes, we do not want them
        # that's why we test if the original polygon (poly) contains
        # an inner point of polygonized geometry (pg)
        return [
            pg for pg in polygonize(union)
            if poly.contains(pg.representative_point())
        ]
Example #6
0
    def _split_line_with_point(line, splitter):
        """Split a LineString with a Point"""
        if not isinstance(line, LineString):
            raise GeometryTypeError("First argument must be a LineString")
        if not isinstance(splitter, Point):
            raise GeometryTypeError("Second argument must be a Point")

        # check if point is in the interior of the line
        if not line.relate_pattern(splitter, "0********"):
            # point not on line interior --> return collection with single identity line
            # (REASONING: Returning a list with the input line reference and creating a
            # GeometryCollection at the general split function prevents unnecessary copying
            # of linestrings in multipoint splitting function)
            return [line]
        elif line.coords[0] == splitter.coords[0]:
            # if line is a closed ring the previous test doesn't behave as desired
            return [line]

        # point is on line, get the distance from the first point on line
        distance_on_line = line.project(splitter)
        coords = list(line.coords)
        # split the line at the point and create two new lines
        current_position = 0.0
        for i in range(len(coords) - 1):
            point1 = coords[i]
            point2 = coords[i + 1]
            dx = point1[0] - point2[0]
            dy = point1[1] - point2[1]
            segment_length = (dx**2 + dy**2)**0.5
            current_position += segment_length
            if distance_on_line == current_position:
                # splitter is exactly on a vertex
                return [LineString(coords[:i + 2]), LineString(coords[i + 1:])]
            elif distance_on_line < current_position:
                # splitter is between two vertices
                return [
                    LineString(coords[:i + 1] + [splitter.coords[0]]),
                    LineString([splitter.coords[0]] + coords[i + 1:]),
                ]
        return [line]
Example #7
0
def dump_coords(geom):
    """Dump coordinates of a geometry in the same order as data packing"""
    if not isinstance(geom, BaseGeometry):
        raise ValueError("Must be instance of a geometry class; found " +
                         geom.__class__.__name__)
    elif geom.type in ("Point", "LineString", "LinearRing"):
        return geom.coords[:]
    elif geom.type == "Polygon":
        return geom.exterior.coords[:] + [i.coords[:] for i in geom.interiors]
    elif geom.type.startswith("Multi") or geom.type == "GeometryCollection":
        # Recursive call
        return [dump_coords(part) for part in geom.geoms]
    else:
        raise GeometryTypeError("Unhandled geometry type: " + repr(geom.type))
Example #8
0
def _empty_shape_for_no_coordinates(geom_type):
    """Return empty counterpart for geom_type"""
    if geom_type == "point":
        return Point()
    elif geom_type == "multipoint":
        return MultiPoint()
    elif geom_type == "linestring":
        return LineString()
    elif geom_type == "multilinestring":
        return MultiLineString()
    elif geom_type == "polygon":
        return Polygon()
    elif geom_type == "multipolygon":
        return MultiPolygon()
    else:
        raise GeometryTypeError("Unknown geometry type: %s" % geom_type)
Example #9
0
 def __getitem__(self, key):
     m = self.__len__()
     if isinstance(key, integer_types):
         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_geom_item(i)
     elif isinstance(key, slice):
         if type(self) == HeterogeneousGeometrySequence:
             raise GeometryTypeError(
                 "Heterogeneous geometry collections are not sliceable")
         res = []
         start, stop, stride = key.indices(m)
         for i in range(start, stop, stride):
             res.append(self._get_geom_item(i))
         return type(self.__p__)(res or None)
     else:
         raise TypeError("key must be an index or slice")
Example #10
0
def asShape(context):
    """
    Adapts the context to a geometry interface. The coordinates remain
    stored in the context, and changes to them will be reflected in the
    returned geometry object.

    .. deprecated:: 1.8
       The proxy geometries (adapter classes) created by this function are
       deprecated, and this function will be removed in Shapely 2.0.
       Use the `shape` function instead to convert a GeoJSON-like dict
       to a Shapely geometry.

    Parameters
    ----------
    context :
        a GeoJSON-like dict, which provides a "type" member describing the type
        of the geometry and "coordinates" member providing a list of coordinates,
        or an object which implements __geo_interface__.

    Returns
    -------
    Geometry object

    Notes
    -----
    The Adapter classes returned by this function trade performance for
    reduced storage of coordinate values. In general, the shape() function
    should be used instead.

    Example
    -------
    Create a Point and Polygon from GeoJSON, change the coordinates of the Point's
    context and show that the corresponding geometry is changed, as well.

    >>> point_context = {'type': 'Point', 'coordinates': [0.5, 0.5]}
    >>> poly_context = {'type': 'Polygon', 'coordinates': [[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]]}
    >>> point, poly = asShape(point_context), asShape(poly_context)
    >>> poly.intersects(point)
    True
    >>> point_context['coordinates'][0] = 1.5
    >>> poly.intersects(point)
    False
    """
    if hasattr(context, "__geo_interface__"):
        ob = context.__geo_interface__
    else:
        ob = context

    try:
        geom_type = ob.get("type").lower()
    except AttributeError:
        raise ValueError("Context does not provide geo interface")

    if geom_type == "point":
        return asPoint(ob["coordinates"])
    elif geom_type == "linestring":
        return asLineString(ob["coordinates"])
    elif geom_type == "polygon":
        return asPolygon(ob["coordinates"][0], ob["coordinates"][1:])
    elif geom_type == "multipoint":
        return asMultiPoint(ob["coordinates"])
    elif geom_type == "multilinestring":
        return asMultiLineString(ob["coordinates"])
    elif geom_type == "multipolygon":
        return MultiPolygonAdapter(ob["coordinates"], context_type='geojson')
    elif geom_type == "geometrycollection":
        geoms = [asShape(g) for g in ob.get("geometries", [])]
        if len(geoms) == 0:
            # in this case no asShape call already raised the warning
            warnings.warn(
                "The proxy geometries (through the 'asShape()' constructor) "
                "are deprecated and will be removed in Shapely 2.0. Use the "
                "'shape()' function instead.",
                ShapelyDeprecationWarning, stacklevel=2)
        return GeometryCollection(geoms)
    else:
        raise GeometryTypeError("Unknown geometry type: %s" % geom_type)
Example #11
0
def substring(geom, start_dist, end_dist, normalized=False):
    """Return a line segment between specified distances along a LineString

    Negative distance values are taken as measured in the reverse
    direction from the end of the geometry. Out-of-range index
    values are handled by clamping them to the valid range of values.

    If the start distance equals the end distance, a Point is returned.

    If the start distance is actually beyond the end distance, then the
    reversed substring is returned such that the start distance is
    at the first coordinate.

    Parameters
    ----------
    geom : LineString
        The geometry to get a substring of.
    start_dist : float
        The distance along `geom` of the start of the substring.
    end_dist : float
        The distance along `geom` of the end of the substring.
    normalized : bool, False
        Whether the distance parameters are interpreted as a
        fraction of the geometry's length.

    Returns
    -------
    Union[Point, LineString]
        The substring between `start_dist` and `end_dist` or a Point
        if they are at the same location.

    Raises
    ------
    TypeError
        If `geom` is not a LineString.

    Examples
    --------
    >>> from shapely.geometry import LineString
    >>> from shapely.ops import substring
    >>> ls = LineString((i, 0) for i in range(6))
    >>> ls.wkt
    'LINESTRING (0 0, 1 0, 2 0, 3 0, 4 0, 5 0)'
    >>> substring(ls, start_dist=1, end_dist=3).wkt
    'LINESTRING (1 0, 2 0, 3 0)'
    >>> substring(ls, start_dist=3, end_dist=1).wkt
    'LINESTRING (3 0, 2 0, 1 0)'
    >>> substring(ls, start_dist=1, end_dist=-3).wkt
    'LINESTRING (1 0, 2 0)'
    >>> substring(ls, start_dist=0.2, end_dist=-0.6, normalized=True).wkt
    'LINESTRING (1 0, 2 0)'

    Returning a `Point` when `start_dist` and `end_dist` are at the
    same location.

    >>> substring(ls, 2.5, -2.5).wkt
    'POINT (2.5 0)'
    """

    if not isinstance(geom, LineString):
        raise GeometryTypeError(
            "Can only calculate a substring of LineString geometries. A %s was provided."
            % geom.type)

    # Filter out cases in which to return a point
    if start_dist == end_dist:
        return geom.interpolate(start_dist, normalized)
    elif not normalized and start_dist >= geom.length and end_dist >= geom.length:
        return geom.interpolate(geom.length, normalized)
    elif not normalized and -start_dist >= geom.length and -end_dist >= geom.length:
        return geom.interpolate(0, normalized)
    elif normalized and start_dist >= 1 and end_dist >= 1:
        return geom.interpolate(1, normalized)
    elif normalized and -start_dist >= 1 and -end_dist >= 1:
        return geom.interpolate(0, normalized)

    if normalized:
        start_dist *= geom.length
        end_dist *= geom.length

    # Filter out cases where distances meet at a middle point from opposite ends.
    if start_dist < 0 < end_dist and abs(start_dist) + end_dist == geom.length:
        return geom.interpolate(end_dist)
    elif end_dist < 0 < start_dist and abs(
            end_dist) + start_dist == geom.length:
        return geom.interpolate(start_dist)

    start_point = geom.interpolate(start_dist)
    end_point = geom.interpolate(end_dist)

    if start_dist < 0:
        start_dist = geom.length + start_dist  # Values may still be negative,
    if end_dist < 0:  # but only in the out-of-range
        end_dist = geom.length + end_dist  # sense, not the wrap-around sense.

    reverse = start_dist > end_dist
    if reverse:
        start_dist, end_dist = end_dist, start_dist

    if start_dist < 0:
        start_dist = 0  # to avoid duplicating the first vertex

    if reverse:
        vertex_list = [(end_point.x, end_point.y)]
    else:
        vertex_list = [(start_point.x, start_point.y)]

    coords = list(geom.coords)
    current_distance = 0
    for p1, p2 in zip(coords, coords[1:]):
        if start_dist < current_distance < end_dist:
            vertex_list.append(p1)
        elif current_distance >= end_dist:
            break

        current_distance += ((p2[0] - p1[0])**2 + (p2[1] - p1[1])**2)**0.5

    if reverse:
        vertex_list.append((start_point.x, start_point.y))
        # reverse direction result
        vertex_list = reversed(vertex_list)
    else:
        vertex_list.append((end_point.x, end_point.y))

    return LineString(vertex_list)
Example #12
0
    def split(geom, splitter):
        """
        Splits a geometry by another geometry and returns a collection of geometries. This function is the theoretical
        opposite of the union of the split geometry parts. If the splitter does not split the geometry, a collection
        with a single geometry equal to the input geometry is returned.
        The function supports:
          - Splitting a (Multi)LineString by a (Multi)Point or (Multi)LineString or (Multi)Polygon
          - Splitting a (Multi)Polygon by a LineString

        It may be convenient to snap the splitter with low tolerance to the geometry. For example in the case
        of splitting a line by a point, the point must be exactly on the line, for the line to be correctly split.
        When splitting a line by a polygon, the boundary of the polygon is used for the operation.
        When splitting a line by another line, a ValueError is raised if the two overlap at some segment.

        Parameters
        ----------
        geom : geometry
            The geometry to be split
        splitter : geometry
            The geometry that will split the input geom

        Example
        -------
        >>> pt = Point((1, 1))
        >>> line = LineString([(0,0), (2,2)])
        >>> result = split(line, pt)
        >>> result.wkt
        'GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))'
        """

        if geom.type in ("MultiLineString", "MultiPolygon"):
            return GeometryCollection([
                i for part in geom.geoms
                for i in SplitOp.split(part, splitter).geoms
            ])

        elif geom.type == "LineString":
            if splitter.type in (
                    "LineString",
                    "MultiLineString",
                    "Polygon",
                    "MultiPolygon",
            ):
                split_func = SplitOp._split_line_with_line
            elif splitter.type in ("Point"):
                split_func = SplitOp._split_line_with_point
            elif splitter.type in ("MultiPoint"):
                split_func = SplitOp._split_line_with_multipoint
            else:
                raise GeometryTypeError(
                    "Splitting a LineString with a %s is not supported" %
                    splitter.type)

        elif geom.type == "Polygon":
            if splitter.type == "LineString":
                split_func = SplitOp._split_polygon_with_line
            else:
                raise GeometryTypeError(
                    "Splitting a Polygon with a %s is not supported" %
                    splitter.type)

        else:
            raise GeometryTypeError("Splitting %s geometry is not supported" %
                                    geom.type)

        return GeometryCollection(split_func(geom, splitter))
Example #13
0
def transform(func, geom):
    """Applies `func` to all coordinates of `geom` and returns a new
    geometry of the same type from the transformed coordinates.

    `func` maps x, y, and optionally z to output xp, yp, zp. The input
    parameters may iterable types like lists or arrays or single values.
    The output shall be of the same type. Scalars in, scalars out.
    Lists in, lists out.

    For example, here is an identity function applicable to both types
    of input.

      def id_func(x, y, z=None):
          return tuple(filter(None, [x, y, z]))

      g2 = transform(id_func, g1)

    Using pyproj >= 2.1, this example will accurately project Shapely geometries:

      import pyproj

      wgs84 = pyproj.CRS('EPSG:4326')
      utm = pyproj.CRS('EPSG:32618')

      project = pyproj.Transformer.from_crs(wgs84, utm, always_xy=True).transform

      g2 = transform(project, g1)

    Note that the always_xy kwarg is required here as Shapely geometries only support
    X,Y coordinate ordering.

    Lambda expressions such as the one in

      g2 = transform(lambda x, y, z=None: (x+1.0, y+1.0), g1)

    also satisfy the requirements for `func`.
    """
    if geom.is_empty:
        return geom
    if geom.type in ("Point", "LineString", "LinearRing", "Polygon"):

        # First we try to apply func to x, y, z sequences. When func is
        # optimized for sequences, this is the fastest, though zipping
        # the results up to go back into the geometry constructors adds
        # extra cost.
        try:
            if geom.type in ("Point", "LineString", "LinearRing"):
                return type(geom)(zip(*func(*zip(*geom.coords))))
            elif geom.type == "Polygon":
                shell = type(geom.exterior)(
                    zip(*func(*zip(*geom.exterior.coords))))
                holes = list(
                    type(ring)(zip(*func(*zip(*ring.coords))))
                    for ring in geom.interiors)
                return type(geom)(shell, holes)

        # A func that assumes x, y, z are single values will likely raise a
        # TypeError, in which case we'll try again.
        except TypeError:
            if geom.type in ("Point", "LineString", "LinearRing"):
                return type(geom)([func(*c) for c in geom.coords])
            elif geom.type == "Polygon":
                shell = type(
                    geom.exterior)([func(*c) for c in geom.exterior.coords])
                holes = list(
                    type(ring)([func(*c) for c in ring.coords])
                    for ring in geom.interiors)
                return type(geom)(shell, holes)

    elif geom.type.startswith("Multi") or geom.type == "GeometryCollection":
        return type(geom)([transform(func, part) for part in geom.geoms])
    else:
        raise GeometryTypeError("Type %r not recognized" % geom.type)
Example #14
0
def affine_transform(geom, matrix):
    r"""Returns a transformed geometry using an affine transformation matrix.

    The coefficient matrix is provided as a list or tuple with 6 or 12 items
    for 2D or 3D transformations, respectively.

    For 2D affine transformations, the 6 parameter matrix is::

        [a, b, d, e, xoff, yoff]

    which represents the augmented matrix::

        [x']   / a  b xoff \ [x]
        [y'] = | d  e yoff | [y]
        [1 ]   \ 0  0   1  / [1]

    or the equations for the transformed coordinates::

        x' = a * x + b * y + xoff
        y' = d * x + e * y + yoff

    For 3D affine transformations, the 12 parameter matrix is::

        [a, b, c, d, e, f, g, h, i, xoff, yoff, zoff]

    which represents the augmented matrix::

        [x']   / a  b  c xoff \ [x]
        [y'] = | d  e  f yoff | [y]
        [z']   | g  h  i zoff | [z]
        [1 ]   \ 0  0  0   1  / [1]

    or the equations for the transformed coordinates::

        x' = a * x + b * y + c * z + xoff
        y' = d * x + e * y + f * z + yoff
        z' = g * x + h * y + i * z + zoff
    """
    if geom.is_empty:
        return geom
    if len(matrix) == 6:
        ndim = 2
        a, b, d, e, xoff, yoff = matrix
        if geom.has_z:
            ndim = 3
            i = 1.0
            c = f = g = h = zoff = 0.0
            matrix = a, b, c, d, e, f, g, h, i, xoff, yoff, zoff
    elif len(matrix) == 12:
        ndim = 3
        a, b, c, d, e, f, g, h, i, xoff, yoff, zoff = matrix
        if not geom.has_z:
            ndim = 2
            matrix = a, b, d, e, xoff, yoff
    else:
        raise ValueError("'matrix' expects either 6 or 12 coefficients")

    def affine_pts(pts):
        """Internal function to yield affine transform of coordinate tuples"""
        if ndim == 2:
            for x, y in pts:
                xp = a * x + b * y + xoff
                yp = d * x + e * y + yoff
                yield (xp, yp)
        elif ndim == 3:
            for x, y, z in pts:
                xp = a * x + b * y + c * z + xoff
                yp = d * x + e * y + f * z + yoff
                zp = g * x + h * y + i * z + zoff
                yield (xp, yp, zp)

    # Process coordinates from each supported geometry type
    if geom.type in ('Point', 'LineString', 'LinearRing'):
        return type(geom)(list(affine_pts(geom.coords)))
    elif geom.type == 'Polygon':
        ring = geom.exterior
        shell = type(ring)(list(affine_pts(ring.coords)))
        holes = list(geom.interiors)
        for pos, ring in enumerate(holes):
            holes[pos] = type(ring)(list(affine_pts(ring.coords)))
        return type(geom)(shell, holes)
    elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection':
        # Recursive call
        # TODO: fix GeometryCollection constructor
        return type(geom)(
            [affine_transform(part, matrix) for part in geom.geoms])
    else:
        raise GeometryTypeError('Type %r not recognized' % geom.type)
Example #15
0
 def _validate_line(self, ob):
     super()._validate(ob)
     if not ob.geom_type in ['LinearRing', 'LineString', 'MultiLineString']:
         raise GeometryTypeError("Only linear types support this operation")