Beispiel #1
0
def close_gaps(df, tolerance):
    """Close gaps in LineString geometry where it should be contiguous.

    Snaps both lines to a centroid of a gap in between.

    """
    geom = df.geometry.values.data
    coords = pygeos.get_coordinates(geom)
    indices = pygeos.get_num_coordinates(geom)

    # generate a list of start and end coordinates and create point geometries
    edges = [0]
    i = 0
    for ind in indices:
        ix = i + ind
        edges.append(ix - 1)
        edges.append(ix)
        i = ix
    edges = edges[:-1]
    points = pygeos.points(np.unique(coords[edges], axis=0))

    buffered = pygeos.buffer(points, tolerance)

    dissolved = pygeos.union_all(buffered)

    exploded = [
        pygeos.get_geometry(dissolved, i)
        for i in range(pygeos.get_num_geometries(dissolved))
    ]

    centroids = pygeos.centroid(exploded)

    snapped = pygeos.snap(geom, pygeos.union_all(centroids), tolerance)

    return snapped
Beispiel #2
0
def test_get_parts(geom):
    expected_num_parts = pygeos.get_num_geometries(geom)
    expected_parts = pygeos.get_geometry(geom, range(0, expected_num_parts))

    parts = pygeos.get_parts(geom)
    assert len(parts) == expected_num_parts
    assert np.all(pygeos.equals_exact(parts, expected_parts))
def explode(df):
    """Explodes multipart geometries to single parts.  Attributes are copied
    to each individual geometry.

    NOTE: Faster method not yet supported in pygeos, in https://github.com/pygeos/pygeos/pull/130
    This branch must be checked out and built for this functionality.

    Parameters
    ----------
    df : GeoDataFrame

    Returns
    -------
    GeoDataFrame
    """

    # Fast method:
    # ix, parts = pg.get_parts(df.geometry.values.data)
    # series = pd.Series(parts, index=df.index[ix], name="geometry")
    # return df.drop(columns=["geometry"]).join(series)

    # Slower method
    geometries = df.geometry.values.data
    ix = []
    parts = []
    for i in range(len(df)):
        num_parts = pg.get_num_geometries(geometries[i])
        ix.extend(np.repeat(df.index[i], num_parts))
        parts.extend(pg.get_geometry(geometries[i], range(num_parts)))

    return gp.GeoDataFrame({
        "geometry": parts
    }, index=ix, crs=df.crs).join(df.drop(columns=["geometry"]))
Beispiel #4
0
    def time_get_parts_python(self):
        """Python / ufuncs version of get_parts"""

        parts = []
        for i in range(len(self.multipolygons)):
            num_parts = pygeos.get_num_geometries(self.multipolygons[i])
            parts.append(pygeos.get_geometry(self.multipolygons[i], range(num_parts)))

        parts = np.concatenate(parts)
Beispiel #5
0
def get_angles(collection, return_indices=False):
    """
    Get the angles pertaining to each vertex of a set of polygons.
    This assumes the input are polygons.

    Arguments
    ---------
    ga  :   pygeos geometry array
        array of polygons/multipolygons
    return_indices  :   bool (Default: False)
        whether to return the indices relating each geometry to a polygon

    Returns
    -------
    angles between triples of points on each geometry, as well as the indices
    relating angles to input geometries (if requested).

    See the Notes for information on the shape of angles and indices.

    Notes
    -------
    If a geometry has n coordinates and k parts, the array will be n - k.
    If each geometry has n_i coordinates, then let N be a vector storing
    those counts (computed, for example, using pygeos.get_num_coordinates(ga)).
    Likewise, let K be a vector storing the number of parts each geometry has, k_i
    (computed, for example, using pygeos.get_num_geometries(ga))

    Then, the output is of shape (N - K).sum()

    """
    ga = _cast(collection)
    exploded = pygeos.get_parts(ga)
    coords = pygeos.get_coordinates(exploded)
    n_coords_per_geom = pygeos.get_num_coordinates(exploded)
    n_parts_per_geom = pygeos.get_num_geometries(exploded)
    angles = numpy.asarray(_get_angles(coords, n_coords_per_geom))
    if return_indices:
        return angles, numpy.repeat(
            numpy.arange(len(ga)),
            pygeos.get_num_coordinates(ga) - pygeos.get_num_geometries(ga),
        )
    else:
        return angles
Beispiel #6
0
def test_get_parts_geometry_collection_multi():
    """On the first pass, the individual Multi* geometry objects are returned
    from the collection.  On the second pass, the individual singular geometry
    objects within those are returned.
    """
    geom = pygeos.geometrycollections([multi_point, multi_line_string, multi_polygon])
    expected_num_parts = pygeos.get_num_geometries(geom)
    expected_parts = pygeos.get_geometry(geom, range(0, expected_num_parts))

    parts = pygeos.get_parts(geom)
    assert len(parts) == expected_num_parts
    assert np.all(pygeos.equals_exact(parts, expected_parts))

    expected_subparts = []
    for g in np.asarray(expected_parts):
        for i in range(0, pygeos.get_num_geometries(g)):
            expected_subparts.append(pygeos.get_geometry(g, i))

    subparts = pygeos.get_parts(parts)
    assert len(subparts) == len(expected_subparts)
    assert np.all(pygeos.equals_exact(subparts, expected_subparts))
Beispiel #7
0
def test_get_parts_array():
    # note: this also verifies that None is handled correctly
    # in the mix; internally it returns -1 for count of geometries
    geom = np.array([None, empty_line_string, multi_point, point, multi_polygon])
    expected_parts = []
    for g in geom:
        for i in range(0, pygeos.get_num_geometries(g)):
            expected_parts.append(pygeos.get_geometry(g, i))

    parts = pygeos.get_parts(geom)
    assert len(parts) == len(expected_parts)
    assert np.all(pygeos.equals_exact(parts, expected_parts))
Beispiel #8
0
def test_get_parts_return_index():
    geom = np.array([multi_point, point, multi_polygon])
    expected_parts = []
    expected_index = []
    for i, g in enumerate(geom):
        for j in range(0, pygeos.get_num_geometries(g)):
            expected_parts.append(pygeos.get_geometry(g, j))
            expected_index.append(i)

    parts, index = pygeos.get_parts(geom, return_index=True)
    assert len(parts) == len(expected_parts)
    assert np.all(pygeos.equals_exact(parts, expected_parts))
    assert np.array_equal(index, expected_index)
Beispiel #9
0
def close_gaps(gdf, tolerance):
    """Close gaps in LineString geometry where it should be contiguous.

    Snaps both lines to a centroid of a gap in between.

    Parameters
    ----------
    gdf : GeoDataFrame, GeoSeries
        GeoDataFrame  or GeoSeries containing LineString representation of a network.
    tolerance : float
        nodes within a tolerance will be snapped together

    Returns
    -------
    GeoSeries
    
    See also
    --------
    momepy.extend_lines
    momepy.remove_false_nodes

    """
    geom = gdf.geometry.values.data
    coords = pygeos.get_coordinates(geom)
    indices = pygeos.get_num_coordinates(geom)

    # generate a list of start and end coordinates and create point geometries
    edges = [0]
    i = 0
    for ind in indices:
        ix = i + ind
        edges.append(ix - 1)
        edges.append(ix)
        i = ix
    edges = edges[:-1]
    points = pygeos.points(np.unique(coords[edges], axis=0))

    buffered = pygeos.buffer(points, tolerance / 2)

    dissolved = pygeos.union_all(buffered)

    exploded = [
        pygeos.get_geometry(dissolved, i)
        for i in range(pygeos.get_num_geometries(dissolved))
    ]

    centroids = pygeos.centroid(exploded)

    snapped = pygeos.snap(geom, pygeos.union_all(centroids), tolerance)

    return gpd.GeoSeries(snapped, crs=gdf.crs)
Beispiel #10
0
def explode(series):
    """Convert multipart geometries to a list of geometries

    Parameters
    ----------
    series : Series

    Returns
    -------
    Series

    """
    return series.apply(
        lambda g: [pg.get_geometry(g, i) for i in range(0, pg.get_num_geometries(g))]
    )
def to_dict(geometry):
    """Convert pygeos Geometry object to a dictionary representation.
    Equivalent to structure of GeoJSON.

    Parameters
    ----------
    geometry : pygeos Geometry object (singular)

    Returns
    -------
    dict
        GeoJSON dict representation of geometry
    """
    geometry = pg.normalize(geometry)

    def get_ring_coords(polygon):
        # outer ring must be reversed to be counterclockwise[::-1]
        coords = [pg.get_coordinates(pg.get_exterior_ring(polygon)).tolist()]
        for i in range(pg.get_num_interior_rings(polygon)):
            # inner rings must be reversed to be clockwise[::-1]
            coords.append(
                pg.get_coordinates(pg.get_interior_ring(polygon, i)).tolist())

        return coords

    geom_type = GEOJSON_TYPE[pg.get_type_id(geometry)]
    coords = []

    if geom_type == "MultiPolygon":
        coords = []
        geoms = pg.get_geometry(geometry,
                                range(pg.get_num_geometries(geometry)))
        for geom in geoms:
            coords.append(get_ring_coords(geom))

    elif geom_type == "Polygon":
        coords = get_ring_coords(geometry)

    else:
        raise NotImplementedError("Not built")

    return {"type": geom_type, "coordinates": coords}
def _clean_multi_geometries(object_array):
    """Cleanup a sequence of geometries to remove multi geometries.

    Args:
        object_array (numpy.ndarray): The object array to cleanup

    Returns:
        numpy.ndarray: Cleaned-up object array.

    """
    # Handle multi-geometries
    geometries = object_array[:, 0]
    num_geometries = get_num_geometries(geometries)
    for index in np.nonzero(num_geometries > 1)[0]:
        split_geometries = [
            np.concatenate((get_geometry(geometries[index],
                                         i), object_array[index, 1:]))
            for i in range(num_geometries[index])
        ]
        object_array[index] = split_geometries[0]
        object_array = np.concatenate((object_array, split_geometries[1:, :]))

    return geometries
Beispiel #13
0
def second_areal_moment(collection):
    """
    Using equation listed on en.wikipedia.org/Second_Moment_of_area, the second
    moment of area is actually the cross-moment of area between the X and Y
    dimensions:

    I_xy = (1/24)\sum^{i=N}^{i=1} (x_iy_{i+1} + 2*x_iy_i + 2*x_{i+1}y_{i+1} +
    x_{i+1}y_i)(x_iy_i - x_{i+1}y_i)

    where x_i, y_i is the current point and x_{i+1}, y_{i+1} is the next point,
    and where x_{n+1} = x_1, y_{n+1} = 1.

    This relation is known as the:
    - second moment of area
    - moment of inertia of plane area
    - area moment of inertia
    - second area moment

    and is *not* the mass moment of inertia, a property of the distribution of
    mass around a shape.
    """
    ga = _cast(collection)
    result = numpy.zeros(len(ga))
    n_holes_per_geom = pygeos.get_num_interior_rings(ga)
    for i, geometry in enumerate(ga):
        n_holes = n_holes_per_geom[i]
        for hole_ix in range(n_holes):
            hole = pygeos.get_coordinates(pygeos.get_interior_ring(
                ga, hole_ix))
            result[i] -= _second_moa_ring(hole)
        n_parts = pygeos.get_num_geometries(geometry)
        for part in pygeos.get_parts(geometry):
            result[i] += _second_moa_ring(pygeos.get_coordinates(part))
    # must divide everything by 24 and flip if polygon is clockwise.
    signflip = numpy.array([-1, 1])[pygeos.is_ccw(ga).astype(int)]
    return result * (1 / 24) * signflip
Beispiel #14
0
def occult(lines: LineCollection, tolerance: float) -> LineCollection:
    """
    Remove occulted lines.

    The order of the geometries in 'lines' matters, see example below.

    'tolerance' controls the distance tolerance between the first and last points
    of a geometry to consider it closed.

    Examples:
        $ vpype line 0 0 5 5 rect 2 2 1 1 occult show  # line is occulted by rect

        $ vpype rect 2 2 1 1 line 0 0 5 5 occult show  # line is NOT occulted by rect,
        as the line is drawn after the rectangle.
    """

    line_arr = np.array(
        [pygeos.linestrings(list(zip(line.real, line.imag))) for line in lines]
    )

    for i, line in enumerate(line_arr):
        coords = pygeos.get_coordinates(line)

        if math.hypot(coords[-1, 0] - coords[0, 0], coords[-1, 1] - coords[0, 1]) < tolerance:
            tree = pygeos.STRtree(line_arr[:i])
            p = pygeos.polygons(coords)
            geom_idx = tree.query(p, predicate="intersects")
            line_arr[geom_idx] = pygeos.set_operations.difference(line_arr[geom_idx], p)

    new_lines = LineCollection()
    for geom in line_arr:
        for i in range(pygeos.get_num_geometries(geom)):
            coords = pygeos.get_coordinates(pygeos.get_geometry(geom, i))
            new_lines.append(coords[:, 0] + coords[:, 1] * 1j)

    return new_lines
def test_delaunay_triangles_only_edges():
    original = Geometry("MULTIPOINT (50 30, 60 30, 100 100, 10 150, 110 120)")
    actual = pygeos.delaunay_triangles(original, only_edges=True)
    assert pygeos.get_num_geometries(actual) == 7
Beispiel #16
0
def test_create_collection_skips_none(func, sub_geom):
    actual = func([sub_geom, None, None, sub_geom])
    assert pygeos.get_num_geometries(actual) == 2
Beispiel #17
0
def test_create_collection(func, sub_geom):
    actual = func([sub_geom, sub_geom])
    assert pygeos.get_num_geometries(actual) == 2
Beispiel #18
0
def test_get_num_geometries():
    actual = pygeos.get_num_geometries(all_types + (None, )).tolist()
    assert actual == [1, 1, 1, 1, 2, 1, 2, 2, 0, 0]
def test_voronoi_polygons():
    original = Geometry("MULTIPOINT (50 30, 60 30, 100 100, 10 150, 110 120)")
    actual = pygeos.voronoi_polygons(original)
    assert pygeos.get_num_geometries(actual) == 5
def test_voronoi_polygons_only_edges():
    # example from PostGIS docs
    original = Geometry("MULTIPOINT (50 30, 60 30, 100 100, 10 150, 110 120)")
    actual = pygeos.voronoi_polygons(original, only_edges=True)
    assert pygeos.get_num_geometries(actual) == 7
Beispiel #21
0
def test_get_geometry_collection(geom):
    n = pygeos.get_num_geometries(geom)
    actual = pygeos.get_geometry(geom, [0, -n, n, -(n + 1)])
    assert pygeos.equals(actual[0], actual[1]).all()
    assert pygeos.is_missing(actual[2:4]).all()