Ejemplo n.º 1
0
def interpolate(data, distance, normalized=False):
    if compat.USE_PYGEOS:
        try:
            return pygeos.line_interpolate_point(data, distance, normalized=normalized)
        except TypeError:  # support for pygeos<0.9
            return pygeos.line_interpolate_point(data, distance, normalize=normalized)
    else:
        out = np.empty(len(data), dtype=object)
        if isinstance(distance, np.ndarray):
            if len(distance) != len(data):
                raise ValueError(
                    "Length of distance sequence does not match "
                    "length of the GeoSeries"
                )
            with compat.ignore_shapely2_warnings():
                out[:] = [
                    geom.interpolate(dist, normalized=normalized)
                    for geom, dist in zip(data, distance)
                ]
            return out

        with compat.ignore_shapely2_warnings():
            out[:] = [
                geom.interpolate(distance, normalized=normalized) for geom in data
            ]
        return out
Ejemplo n.º 2
0
def test_line_interpolate_point_empty(geom, normalized):
    # These geometries segfault in some versions of GEOS (in 3.8.0, still
    # some of them segfault). Instead, we patched this to return POINT EMPTY.
    # This matches GEOS 3.8.0 behavior on simple empty geometries.
    assert pygeos.equals(
        pygeos.line_interpolate_point(geom, 0.2, normalized=normalized),
        empty_point)
Ejemplo n.º 3
0
def test_line_interpolate_point_geom_array_normalized():
    actual = pygeos.line_interpolate_point(
        [line_string, linear_ring, multi_line_string], 1, normalized=True
    )
    assert pygeos.equals(actual[0], pygeos.Geometry("POINT (1 1)"))
    assert pygeos.equals(actual[1], pygeos.Geometry("POINT (0 0)"))
    assert pygeos.equals(actual[2], pygeos.Geometry("POINT (1 2)"))
Ejemplo n.º 4
0
    def _dense_point_array(self, geoms, distance, index):
        """
        geoms - array of pygeos lines
        """
        # interpolate lines to represent them as points for Voronoi
        points = np.empty((0, 2))
        ids = []

        if pygeos.get_type_id(geoms[0]) not in [1, 2, 5]:
            lines = pygeos.boundary(geoms)
        else:
            lines = geoms
        lengths = pygeos.length(lines)
        for ix, line, length in zip(index, lines, lengths):
            if length > distance:  # some polygons might have collapsed
                pts = pygeos.line_interpolate_point(
                    line,
                    np.linspace(0.1,
                                length - 0.1,
                                num=int((length - 0.1) // distance)),
                )  # .1 offset to keep a gap between two segments
                points = np.append(points, pygeos.get_coordinates(pts), axis=0)
                ids += [ix] * len(pts)

        return points, ids
Ejemplo n.º 5
0
def cut_line_at_points(line, cut_points, tolerance=1e-6):
    """Cut a pygeos line geometry at points.
    If there are no interior points, the original line will be returned.

    Parameters
    ----------
    line : pygeos Linestring
    cut_points : list-like of pygeos Points
        will be projected onto the line; those interior to the line will be
        used to cut the line in to new segments.
    tolerance : float, optional (default: 1e-6)
        minimum distance from endpoints to consider the points interior
        to the line.

    Returns
    -------
    MultiLineStrings (or LineString, if unchanged)
    """
    if not pg.get_type_id(line) == 1:
        raise ValueError("line is not a single linestring")

    vertices = pg.get_point(line, range(pg.get_num_points(line)))
    offsets = pg.line_locate_point(line, vertices)
    cut_offsets = pg.line_locate_point(line, cut_points)
    # only keep those that are interior to the line and ignore those very close
    # to endpoints or beyond endpoints
    cut_offsets = cut_offsets[(cut_offsets > tolerance)
                              & (cut_offsets < offsets[-1] - tolerance)]

    if len(cut_offsets) == 0:
        # nothing to cut, return original
        return line

    # get coordinates of new vertices from the cut points (interpolated onto the line)
    cut_offsets.sort()

    # add in the last coordinate of the line
    cut_offsets = np.append(cut_offsets, offsets[-1])

    # TODO: convert this to a pygos ufunc
    coords = pg.get_coordinates(line)
    cut_coords = pg.get_coordinates(
        pg.line_interpolate_point(line, cut_offsets))
    lines = []
    orig_ix = 0
    for cut_ix in range(len(cut_offsets)):
        offset = cut_offsets[cut_ix]

        segment = []
        if cut_ix > 0:
            segment = [cut_coords[cut_ix - 1]]
        while offsets[orig_ix] < offset:
            segment.append(coords[orig_ix])
            orig_ix += 1

        segment.append(cut_coords[cut_ix])
        lines.append(pg.linestrings(segment))

    return pg.multilinestrings(lines)
Ejemplo n.º 6
0
def test_line_interpolate_point_geom_array():
    actual = pygeos.line_interpolate_point(
        [line_string, linear_ring, multi_line_string], -1)
    assert pygeos.equals(actual[0], pygeos.Geometry("POINT (1 0)"))
    assert pygeos.equals(actual[1], pygeos.Geometry("POINT (0 1)"))
    assert pygeos.equals_exact(actual[2],
                               pygeos.Geometry("POINT (0.5528 1.1056)"),
                               tolerance=0.001)
Ejemplo n.º 7
0
def interpolate(data, distance, normalized=False):
    if compat.USE_PYGEOS:
        return pygeos.line_interpolate_point(data, distance, normalize=normalized)
    else:
        out = np.empty(len(data), dtype=object)
        if isinstance(distance, np.ndarray):
            if len(distance) != len(data):
                raise ValueError(
                    "Length of distance sequence does not match "
                    "length of the GeoSeries"
                )
            out[:] = [
                geom.interpolate(dist, normalized=normalized)
                for geom, dist in zip(data, distance)
            ]
            return out

        out[:] = [geom.interpolate(distance, normalized=normalized) for geom in data]
        return out
Ejemplo n.º 8
0
def street_profile(streets, buildings, distance=3, tick_length=50):

    pygeos_lines = streets.geometry.values.data

    list_points = np.empty((0, 2))
    ids = []

    lengths = pygeos.length(pygeos_lines)
    for ix, (line, length) in enumerate(zip(pygeos_lines, lengths)):

        pts = pygeos.line_interpolate_point(
            line, np.linspace(0, length, num=int((length) // distance))
        )  # .1 offset to keep a gap between two segments
        list_points = np.append(list_points, pygeos.get_coordinates(pts), axis=0)
        ids += [ix] * len(pts) * 2


    ticks = []
    for num, pt in enumerate(list_points, 1):
        # start chainage 0
        if num == 1:
            angle = _getAngle(pt, list_points[num])
            line_end_1 = _getPoint1(pt, angle, tick_length / 2)
            angle = _getAngle(line_end_1, pt)
            line_end_2 = _getPoint2(line_end_1, angle, tick_length)
            ticks.append([line_end_1, pt])
            ticks.append([line_end_2, pt])

        # everything in between
        if num < len(list_points) - 1:
            angle = _getAngle(pt, list_points[num])
            line_end_1 = _getPoint1(
                list_points[num], angle, tick_length / 2
            )
            angle = _getAngle(line_end_1, list_points[num])
            line_end_2 = _getPoint2(line_end_1, angle, tick_length)
            ticks.append([line_end_1, list_points[num]])
            ticks.append([line_end_2, list_points[num]])

        # end chainage
        if num == len(list_points):
            angle = _getAngle(list_points[num - 2], pt)
            line_end_1 = _getPoint1(pt, angle, tick_length / 2)
            angle = _getAngle(line_end_1, pt)
            line_end_2 = _getPoint2(line_end_1, angle, tick_length)
            ticks.append([line_end_1, pt])
            ticks.append([line_end_2, pt])

    ticks = pygeos.linestrings(ticks)
    inp, res = pygeos.STRtree(ticks).query_bulk(buildings.geometry.values.data, predicate='intersects')
    intersections = pygeos.intersection(ticks[res], buildings.geometry.values.data[inp])
    distances = pygeos.distance(intersections, pygeos.points(list_points[res // 2]))

    dists = np.zeros((len(ticks),))
    dists[:] = np.nan
    dists[res] = distances

    ids = np.array(ids)
    widths = []
    openness = []
    deviations = []
    for i in range(len(streets)):
        f = ids == i
        s = dists[f]
        lefts = s[::2]
        rights = s[1::2]
        left_mean = np.nanmean(lefts) if ~np.isnan(lefts).all() else tick_length / 2
        right_mean = np.nanmean(rights) if ~np.isnan(rights).all() else tick_length / 2
        widths.append(np.mean([left_mean, right_mean]) * 2)
        openness.append(np.isnan(s).sum() / (f).sum())
        deviations.append(np.nanstd(s))
    
    return (widths, deviations, openness)
Ejemplo n.º 9
0
def test_line_interpolate_point_nan():
    assert pygeos.line_interpolate_point(line_string, np.nan) is None
Ejemplo n.º 10
0
def test_line_interpolate_point_none():
    assert pygeos.line_interpolate_point(None, 0.2) is None
Ejemplo n.º 11
0
def test_line_interpolate_point_empty():
    assert pygeos.equals(
        pygeos.line_interpolate_point(empty_line_string, 0.2), empty_point
    )
Ejemplo n.º 12
0
def test_line_interpolate_point_float_array():
    actual = pygeos.line_interpolate_point(line_string, [0.2, 1.5, -0.2])
    assert pygeos.equals(actual[0], pygeos.Geometry("POINT (0.2 0)"))
    assert pygeos.equals(actual[1], pygeos.Geometry("POINT (1 0.5)"))
    assert pygeos.equals(actual[2], pygeos.Geometry("POINT (1 0.8)"))
Ejemplo n.º 13
0
def test_line_interpolate_point_geom_array():
    actual = pygeos.line_interpolate_point([line_string, linear_ring], -1)
    assert pygeos.equals(actual[0], pygeos.Geometry("POINT (1 0)"))
    assert pygeos.equals(actual[1], pygeos.Geometry("POINT (0 1)"))
Ejemplo n.º 14
0
    def __init__(self,
                 left,
                 right,
                 heights=None,
                 distance=10,
                 tick_length=50,
                 verbose=True):
        self.left = left
        self.right = right
        self.distance = distance
        self.tick_length = tick_length

        pygeos_lines = left.geometry.values.data

        list_points = np.empty((0, 2))
        ids = []
        end_markers = []

        lengths = pygeos.length(pygeos_lines)
        for ix, (line, length) in enumerate(zip(pygeos_lines, lengths)):

            pts = pygeos.line_interpolate_point(
                line, np.linspace(0, length, num=int((length) // distance)))
            list_points = np.append(list_points,
                                    pygeos.get_coordinates(pts),
                                    axis=0)
            if len(pts) > 1:
                ids += [ix] * len(pts) * 2
                markers = [True] + ([False] * (len(pts) - 2)) + [True]
                end_markers += markers
            elif len(pts) == 1:
                end_markers += [True]
                ids += [ix] * 2

        ticks = []
        for num, (pt, end) in enumerate(zip(list_points, end_markers), 1):
            if end:
                ticks.append([pt, pt])
                ticks.append([pt, pt])

            else:
                angle = self._getAngle(pt, list_points[num])
                line_end_1 = self._getPoint1(pt, angle, tick_length / 2)
                angle = self._getAngle(line_end_1, pt)
                line_end_2 = self._getPoint2(line_end_1, angle, tick_length)
                ticks.append([line_end_1, pt])
                ticks.append([line_end_2, pt])

        ticks = pygeos.linestrings(ticks)

        inp, res = right.sindex.query_bulk(ticks, predicate="intersects")
        intersections = pygeos.intersection(ticks[inp],
                                            right.geometry.values.data[res])
        distances = pygeos.distance(intersections,
                                    pygeos.points(list_points[inp // 2]))
        inp_uni, inp_cts = np.unique(inp, return_counts=True)
        splitter = np.cumsum(inp_cts)[:-1]
        dist_per_res = np.split(distances, splitter)
        inp_per_res = np.split(res, splitter)

        min_distances = []
        min_inds = []
        for dis, ind in zip(dist_per_res, inp_per_res):
            min_distances.append(np.min(dis))
            min_inds.append(ind[np.argmin(dis)])

        dists = np.zeros((len(ticks), ))
        dists[:] = np.nan
        dists[inp_uni] = min_distances

        if heights is not None:
            if isinstance(heights, str):
                heights = self.heights = right[heights]
            elif not isinstance(heights, pd.Series):
                heights = self.heights = pd.Series(heights)

            blgs = np.zeros((len(ticks), ))
            blgs[:] = None
            blgs[inp_uni] = min_inds
            do_heights = True
        else:
            do_heights = False

        ids = np.array(ids)
        widths = []
        openness = []
        deviations = []
        heights_list = []
        heights_deviations_list = []

        for i in range(len(left)):
            f = ids == i
            s = dists[f]
            lefts = s[::2]
            rights = s[1::2]
            left_mean = np.nanmean(
                lefts) if ~np.isnan(lefts).all() else tick_length / 2
            right_mean = (np.nanmean(rights)
                          if ~np.isnan(rights).all() else tick_length / 2)
            widths.append(np.mean([left_mean, right_mean]) * 2)
            openness.append(np.isnan(s).sum() / (f).sum())
            deviations.append(np.nanstd(s))

            if do_heights:
                b = blgs[f]
                h = heights.iloc[b[~np.isnan(b)]]
                heights_list.append(h.mean())
                heights_deviations_list.append(h.std())

        self.w = pd.Series(widths, index=left.index)
        self.wd = pd.Series(deviations, index=left.index).fillna(
            0)  # fill for empty intersections
        self.o = pd.Series(openness, index=left.index).fillna(1)

        if do_heights:
            self.h = pd.Series(heights_list, index=left.index).fillna(
                0)  # fill for empty intersections
            self.hd = pd.Series(heights_deviations_list,
                                index=left.index).fillna(
                                    0)  # fill for empty intersections
            self.p = self.h / self.w.replace(0,
                                             np.nan)  # replace to avoid np.inf
Ejemplo n.º 15
0
def test_line_interpolate_point_invalid_type(geom, normalized):
    with pytest.raises(TypeError):
        assert pygeos.line_interpolate_point(geom, 0.2, normalized=normalized)
Ejemplo n.º 16
0
def snap_to_flowlines(df, to_snap):
    """Snap to nearest flowline, within tolerance

    Updates df with snapping results, and returns to_snap as set of dams still
    needing to be snapped after this operation.

    Parameters
    ----------
    df : GeoDataFrame
        master dataset, this is where all snapping gets recorded
    to_snap : DataFrame
        data frame containing pygeos geometries to snap ("geometry")
        and snapping tolerance ("snap_tolerance")

    Returns
    -------
    tuple of (GeoDataFrame, DataFrame)
        (df, to_snap)
    """

    for region, HUC2s in list(REGION_GROUPS.items()):
        region_start = time()

        print("\n----- {} ------\n".format(region))

        print("Reading flowlines...")
        flowlines = from_geofeather(
            nhd_dir / "clean" / region / "flowlines.feather"
        ).set_index("lineID")

        in_region = to_snap.loc[to_snap.HUC2.isin(HUC2s)]
        print(
            "Selected {:,} barriers in region to snap against {:,} flowlines".format(
                len(in_region), len(flowlines)
            )
        )

        if len(in_region) == 0:
            print("No barriers in region to snap")
            continue

        print("Finding nearest flowlines...")
        # TODO: can use near instead of nearest, and persist list of near lineIDs per barrier
        # so that we can construct subnetworks with just those
        lines = nearest(
            in_region.geometry, flowlines.geometry, in_region.snap_tolerance
        )
        lines = lines.join(in_region.geometry).join(
            flowlines.geometry.rename("line"), on="lineID",
        )

        # project the point to the line,
        # find out its distance on the line,
        # then interpolate its new coordinates
        lines["geometry"] = pg.line_interpolate_point(
            lines.line, pg.line_locate_point(lines.line, lines.geometry)
        )

        ix = lines.index
        df.loc[ix, "snapped"] = True
        df.loc[ix, "geometry"] = lines.geometry
        df.loc[ix, "snap_dist"] = lines.distance
        df.loc[ix, "snap_ref_id"] = lines.lineID
        df.loc[ix, "lineID"] = lines.lineID
        df.loc[ix, "snap_log"] = ndarray_append_strings(
            "snapped: within ",
            to_snap.loc[ix].snap_tolerance,
            "m tolerance of flowline",
        )

        to_snap = to_snap.loc[~to_snap.index.isin(ix)].copy()

        print(
            "{:,} barriers snapped in region in {:.2f}s".format(
                len(ix), time() - region_start
            )
        )

    # TODO: flag those that joined to loops

    return df, to_snap
Ejemplo n.º 17
0
def snap_to_flowlines(df, to_snap):
    """Snap to nearest flowline, within tolerance

    Updates df with snapping results, and returns to_snap as set of dams still
    needing to be snapped after this operation.

    If dams are within SNAP_ENDPOINT_TOLERANCE of the endpoints of the line, they
    will be snapped to the endpoint instead of closest point on line.

    Parameters
    ----------
    df : GeoDataFrame
        master dataset, this is where all snapping gets recorded
    to_snap : DataFrame
        data frame containing pygeos geometries to snap ("geometry")
        and snapping tolerance ("snap_tolerance")

    Returns
    -------
    tuple of (GeoDataFrame, DataFrame)
        (df, to_snap)
    """

    print("=================\nSnapping to flowlines...")

    for huc2 in sorted(to_snap.HUC2.unique()):
        region_start = time()

        print(f"\n----- {huc2} ------")
        in_huc2 = to_snap.loc[to_snap.HUC2 == huc2].copy()
        flowlines = gp.read_feather(
            nhd_dir / "clean" / huc2 / "flowlines.feather",
            columns=["geometry", "lineID"],
        ).set_index("lineID")

        print(
            f"HUC {huc2} selected {len(in_huc2):,} barriers in region to snap against {len(flowlines):,} flowlines"
        )

        lines = nearest(
            pd.Series(in_huc2.geometry.values.data, index=in_huc2.index),
            pd.Series(flowlines.geometry.values.data, index=flowlines.index),
            in_huc2.snap_tolerance.values,
        )
        lines = lines.join(in_huc2.geometry).join(
            flowlines.geometry.rename("line"),
            on="lineID",
        )

        # project the point to the line,
        # find out its distance on the line,
        lines["line_pos"] = pg.line_locate_point(lines.line.values.data,
                                                 lines.geometry.values.data)

        # if within tolerance of start point, snap to start
        ix = lines["line_pos"] <= SNAP_ENDPOINT_TOLERANCE
        lines.loc[ix, "line_pos"] = 0

        # if within tolerance of endpoint, snap to end
        end = pg.length(lines.line.values.data)
        ix = lines["line_pos"] >= end - SNAP_ENDPOINT_TOLERANCE
        lines.loc[ix, "line_pos"] = end[ix]

        # then interpolate its new coordinates
        lines["geometry"] = pg.line_interpolate_point(lines.line.values.data,
                                                      lines["line_pos"])

        ix = lines.index
        df.loc[ix, "snapped"] = True
        df.loc[ix, "geometry"] = lines.geometry
        df.loc[ix, "snap_dist"] = lines.distance
        df.loc[ix, "snap_ref_id"] = lines.lineID
        df.loc[ix, "lineID"] = lines.lineID
        df.loc[ix, "snap_log"] = ndarray_append_strings(
            "snapped: within ",
            to_snap.loc[ix].snap_tolerance,
            "m tolerance of flowline",
        )

        to_snap = to_snap.loc[~to_snap.index.isin(ix)].copy()

        print("{:,} barriers snapped in region in {:.2f}s".format(
            len(ix),
            time() - region_start))

    # TODO: flag those that joined to loops

    return df, to_snap