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)
def test_simplify(): line = pygeos.linestrings([[0, 0], [0.1, 1], [0, 2]]) actual = pygeos.simplify(line, [0, 1.0]) assert pygeos.get_num_points(actual).tolist() == [3, 2]
def find_dam_face_from_waterbody(waterbody, drain_pt): total_area = pg.area(waterbody) ring = pg.get_exterior_ring(pg.normalize(waterbody)) total_length = pg.length(ring) num_pts = pg.get_num_points(ring) - 1 # drop closing coordinate vertices = pg.get_point(ring, range(num_pts)) ### Extract line segments that are no more than 1/3 coordinates of polygon # starting from the vertex nearest the drain # note: lower numbers are to the right tree = pg.STRtree(vertices) ix = tree.nearest(drain_pt)[1][0] side_width = min(num_pts // 3, MAX_SIDE_PTS) left_ix = ix + side_width right_ix = ix - side_width # extract these as a left-to-write line; pts = vertices[max(right_ix, 0):min(num_pts, left_ix)][::-1] if left_ix >= num_pts: pts = np.append(vertices[0:left_ix - num_pts][::-1], pts) if right_ix < 0: pts = np.append(pts, vertices[num_pts + right_ix:num_pts][::-1]) coords = pg.get_coordinates(pts) if len(coords) > 2: # first run a simplification process to extract the major shape and bends # then run the straight line algorithm simp_coords, simp_ix = simplify_vw( coords, min(MAX_SIMPLIFY_AREA, total_area / 100)) if len(simp_coords) > 2: keep_coords, ix = extract_straight_segments( simp_coords, max_angle=MAX_STRAIGHT_ANGLE, loops=5) keep_ix = simp_ix.take(ix) else: keep_coords = simp_coords keep_ix = simp_ix else: keep_coords = coords keep_ix = np.arange(len(coords)) ### Calculate the length of each run and drop any that are not sufficiently long lengths = segment_length(keep_coords) ix = (lengths >= MIN_DAM_WIDTH) & (lengths / total_length < MAX_WIDTH_RATIO) pairs = np.dstack([keep_ix[:-1][ix], keep_ix[1:][ix]])[0] # since ranges are ragged, we have to do this in a loop instead of vectorized segments = [] for start, end in pairs: segments.append(pg.linestrings(coords[start:end + 1])) segments = np.array(segments) # only keep the segments that are close to the drain segments = segments[ pg.intersects(segments, pg.buffer(drain_pt, MAX_DRAIN_DIST)), ] if not len(segments): return segments # only keep those where the drain is interior to the line pos = pg.line_locate_point(segments, drain_pt) lengths = pg.length(segments) ix = (pos >= MIN_INTERIOR_DIST) & (pos <= (lengths - MIN_INTERIOR_DIST)) return segments[ix]
def test_get_point(geom): n = pygeos.get_num_points(geom) actual = pygeos.get_point(geom, [0, -n, n, -(n + 1)]) assert pygeos.equals(actual[0], actual[1]).all() assert pygeos.is_missing(actual[2:4]).all()
def test_get_num_points(): actual = pygeos.get_num_points(all_types + (None, )).tolist() assert actual == [0, 3, 5, 0, 0, 0, 0, 0, 0, 0]
def test_get_num_points(): assert pygeos.get_num_points(line_string) == 3