def iterative_cl_pt_mapping(cl, bufdists, side): mapper = [] lines = [] plt.close() old = cl for i, bd in enumerate(bufdists): new = shapely_offset_ls(cl, bd, side) Co, Ao, so = cu.curvars(old.coords.xy[0], old.coords.xy[1]) Cn, An, sn = cu.curvars(new.coords.xy[0], new.coords.xy[1]) Ao = np.insert(Ao, 0, 0) An = np.insert(An, 0, 0) distance, path = fastdtw(Ao, An, dist=euclidean) path = np.array(path) mapper.append(path) lines.append(new) old = new return lines, mapper
def test_inflection_pts(): """Test inflection_points().""" xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ys = [1, 4, 9, 16, 25, 16, 9, 4, 1, 0] C, _, _ = cu.curvars(xs, ys, unwrap=False) # run function infs = cu.inflection_points(C) # make assertion assert np.all(infs == np.array([3, 5]))
def C(self, x=None, y=None): """ Important: curvatures are negativized to match the zs approach """ if x is None: x, y, _ = self.__get_x_and_y() Cs, _, _ = cu.curvars(x, y, unwrap=True) Cs = np.insert(Cs, 0, 0) return -Cs
def test_curvars(): """Test unwrap==False.""" xs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ys = [1, 4, 9, 16, 25, 16, 9, 4, 1, 0] C, Areturn, s = cu.curvars(xs, ys, unwrap=False) # make assertions assert np.shape(C) == (9,) assert np.shape(Areturn) == (9,) assert np.shape(s) == (9,) assert C[0] == pytest.approx(-0.03131767154315412) assert Areturn[4] == pytest.approx(-1.460139105621001) assert s[7] == pytest.approx(48.775500247528115)
def shapely_offset_ls(ls, dist, side): """ Just a wrapper around shapely's offset_linestring() function. That function adds little barbs sometimes to the end of the offset linestring. This function detects and removes those. """ offset = cu.offset_linestring(ls, dist, side) # Look for barbs by finding abrupt angle changes _, A, _ = cu.curvars(offset.coords.xy[0], offset.coords.xy[1]) possibles = np.where( np.abs(np.diff(A)) > 1.5)[0] # Threshold set at 1.5 radians if len(possibles) == 0: return offset else: st_idx = 0 en_idx = len(offset.coords) - 1 for p in possibles: if p < len(offset.coords) / 2: st_idx = max(st_idx, p + 1) elif p > len(offset.coords) / 2: en_idx = min(en_idx, p) offset = LineString(offset.coords[st_idx:en_idx]) # elif len(possibles) == 1: # Determine if it's the upstream or downstream that's barbed # if possibles[0] > len(A)/2: # Downstream # offset = LineString(zip(offset.coords.xy[0][:possibles[0]], offset.coords.xy[1][:possibles[0]])) # else: # Upstream # offset = LineString(zip(offset.coords.xy[0][possibles[0]:], offset.coords.xy[1][possibles[0]:])) # elif len(possibles) == 2: # offset = LineString(zip(offset.coords.xy[0][possibles[0]:possibles[1]], offset.coords.xy[1][possibles[0]:possibles[1]])) # else: # # import pdb; pdb.set_trace() # raise Warning('Barbs could not be removed from centerline offset: dist={}, side={}.'.format(dist,side)) return offset
def test_sine_curvature(): """Use a sine wave to compute curvature.""" xs = np.linspace(0, 100, 101) ys = np.sin(xs) + 5 C, Areturn, sdist = cu.curvars(xs, ys) # make some simple assertions about shape of outputs assert C.shape == (100,) assert Areturn.shape == (100,) assert sdist.shape == (100,) # now define this as a centerline CL = centerline(xs, ys) # smooth the centerline CL.window_cl = 10 CL.smooth(n=2) # make some assertions about the smoothing assert CL.xs.shape == (101,) assert CL.ys.shape == (101,) assert np.sum(CL.xs != xs) > 0 assert np.sum(CL.ys != ys) > 0 # resample the centerline to 50 points CL.resample(50) # assert resampled dimensions are as expected assert CL.xrs.shape == (50,) assert CL.yrs.shape == (50,)
def centerline_mesh(coords, width_chan, meshwidth, grid_spacing, smoothing_param=1): """ Generate a centerline mesh. Generates a centerline mesh. Differs from :func:`valleyline_mesh` in that it draws perpendiculars rather than offsetting the valley line to compute mesh polygons. This method is more effective for narrower channels that don't require an exceptionally wide mesh (i.e. not much change). Parameters ---------- coords : 2xN list, tuple, np.array (xs, ys) of coordinates defining centerline width_chan : width of the river in same units of coords mesh_dist : how wide should the mesh be, in same units of coords grid_spacing : how far apart should mesh cells be, in same units of coords """ # coords = ken.centerline # width_chan = ken.width_chans # meshwidth = ken.max_valley_width_pixels * ken.pixlen * 1.1 # grid_spacing = meshwidth/10 # smoothing_param = 1 if np.shape(coords)[0] == 2 and np.size(coords) != 4: coords = np.transpose(coords) # Get lengths along centerline s, ds = cu.s_ds(coords[:, 0], coords[:, 1]) # Mirror centerline manually since scipy f***s it up - only flip the axis that has the largest displacement # Mirroring done to avoid edge effects when smoothing npad = int(width_chan / np.mean(ds) * 10) # Padding fixed at 10 channel widths xs_m, ys_m = mirror_line_ends(coords[:, 0], coords[:, 1], npad) # A smoothing filter of one-channel width will be passed over the centerline coordinates window_len = int(width_chan / np.mean(ds) * smoothing_param) if window_len % 2 == 0: # Window must be odd window_len = window_len + 1 # Smooth xs_sm = signal.savgol_filter(xs_m, window_length=window_len, polyorder=3, mode='interp') ys_sm = signal.savgol_filter(ys_m, window_length=window_len, polyorder=3, mode='interp') # plt.close('all') # plt.plot(xs_sm, ys_sm) # plt.plot(xs_m, ys_m) # plt.axis('equal') # Re-sample centerline to even spacing s, _ = cu.s_ds(xs_sm, ys_sm) npts = int(s[-1] / grid_spacing) xy_rs, _ = cu.evenly_space_line(xs_sm, ys_sm, npts) xs_rs = xy_rs[0] ys_rs = xy_rs[1] # Get angles at each point along centerline C, A, s = cu.curvars(xs_rs, ys_rs, unwrap=True) # Draw perpendiculars at each centerline point mesh_hwidth = meshwidth / 2 # Compute slope of perpendicular (w/ref to dx/dy and dy/dx) m_inv_xy = -1 / (np.diff(xs_rs) / np.diff(ys_rs)) m_inv_yx = -1 / (np.diff(ys_rs) / np.diff(xs_rs)) # For storing perpendicular points perps = [] for ic in range(len(m_inv_xy)): # Compute perpendicular lines based on largest of dx, dy (reduces distortion) if m_inv_yx[ic] > m_inv_xy[ic]: dx = np.sqrt(mesh_hwidth**2 / (1 + m_inv_yx[ic]**2)) dy = dx * m_inv_yx[ic] else: dy = np.sqrt(mesh_hwidth**2 / (1 + m_inv_xy[ic]**2)) dx = dy * m_inv_xy[ic] upper_pt = (xs_rs[ic] + dx, ys_rs[ic] + dy) lower_pt = (xs_rs[ic] - dx, ys_rs[ic] - dy) perps.append((upper_pt, lower_pt)) # Now orient perpendiculars so that both sides are continuous # NOTE: this method is not guaranteed to work when the grid spacing is much # larger than the buffer width (it likely will be fine, but for highly- # curved bends failure is possible). There are more robust ways to separate # points into left/right bank, but this is quick, dirty, and works for most # applications. perp_aligned = [perps[0]] for ip in range(1, len(perps)): left_pre, right_pre = perp_aligned[ip - 1] p0 = perps[ip][0] p1 = perps[ip][1] if np.sqrt((p0[0] - left_pre[0])**2 + (p0[1] - left_pre[1])**2) < np.sqrt( (p1[0] - left_pre[0])**2 + (p1[1] - left_pre[1])**2): perp_aligned.append((p0, p1)) else: perp_aligned.append((p1, p0)) # plt.close('all') # plt.plot(xs_rs, ys_rs,'.') # plt.axis('equal') # for p in perp_aligned: # plt.plot(p[0][0], p[0][1], 'k.') # plt.plot(p[1][0], p[1][1], 'r.') # Trim the centerline to remove the mirrored portions start_idx = np.argmin( np.sqrt((coords[0, 0] - xs_rs)**2 + (coords[0, 1] - ys_rs)**2)) - 1 end_idx = np.argmin( np.sqrt((coords[-1, 0] - xs_rs)**2 + (coords[-1, 1] - ys_rs)**2)) + 1 # Build the polygon mesh polys = [] for i in range(start_idx, end_idx + 1): polys.append([ perp_aligned[i][0], perp_aligned[i][1], perp_aligned[i + 1][1], perp_aligned[i + 1][0], perp_aligned[i][0] ]) perps_out = perp_aligned[start_idx:end_idx + 1] cl_resampled = np.r_['1,2,0', xs_rs, ys_rs] s_out = s[start_idx:end_idx + 1] - s[start_idx] return perps_out, polys, cl_resampled, s_out