def test_set_operation_prec_array(a, func, grid_size): actual = func([a, a], point, grid_size=grid_size) assert actual.shape == (2, ) assert isinstance(actual[0], Geometry) # results should match the operation when the precision is previously set # to same grid_size b = pygeos.set_precision(a, grid_size=grid_size) point2 = pygeos.set_precision(point, grid_size=grid_size) expected = func([b, b], point2) assert pygeos.equals(pygeos.normalize(actual), pygeos.normalize(expected)).all()
def test_set_precision_intersection(): """Operations should use the most precise presision grid size of the inputs""" box1 = pygeos.normalize(pygeos.box(0, 0, 0.9, 0.9)) box2 = pygeos.normalize(pygeos.box(0.75, 0, 1.75, 0.75)) assert pygeos.get_precision(pygeos.intersection(box1, box2)) == 0 # GEOS will use and keep the most precise precision grid size box1 = pygeos.set_precision(box1, 0.5) box2 = pygeos.set_precision(box2, 1) out = pygeos.intersection(box1, box2) assert pygeos.get_precision(out) == 0.5 assert pygeos.equals(out, pygeos.Geometry("LINESTRING (1 1, 1 0)"))
def normalize(data): if compat.USE_PYGEOS: return pygeos.normalize(data) else: out = np.empty(len(data), dtype=object) with compat.ignore_shapely2_warnings(): out[:] = [ _shapely_normalize(geom) if geom is not None else None for geom in data ] return out
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 constructive(arr, operation, *args, **kwargs): if operation == 'boundary': geometries = pg.boundary(pg.from_wkb(arr), **kwargs) elif operation == 'buffer': geometries = pg.buffer(pg.from_wkb(arr), *args, **kwargs) elif operation == 'build_area': geometries = pg.build_area(pg.from_wkb(arr), **kwargs) elif operation == 'centroid': geometries = pg.centroid(pg.from_wkb(arr), **kwargs) elif operation == 'clip_by_rect': geometries = pg.clip_by_rect(pg.from_wkb(arr), *args, **kwargs) elif operation == 'convex_hull': geometries = pg.convex_hull(pg.from_wkb(arr), **kwargs) elif operation == 'delaunay_triangles': geometries = pg.delaunay_triangles(pg.from_wkb(arr), **kwargs) elif operation == 'envelope': geometries = pg.envelope(pg.from_wkb(arr), **kwargs) elif operation == 'extract_unique_points': geometries = pg.extract_unique_points(pg.from_wkb(arr), **kwargs) elif operation == 'make_valid': geometries = pg.make_valid(pg.from_wkb(arr), **kwargs) elif operation == 'normalize': geometries = pg.normalize(pg.from_wkb(arr), **kwargs) elif operation == 'offset_curve': geometries = pg.offset_curve(pg.from_wkb(arr), *args, **kwargs) elif operation == 'point_on_surface': geometries = pg.point_on_surface(pg.from_wkb(arr), **kwargs) elif operation == 'reverse': geometries = pg.reverse(pg.from_wkb(arr), **kwargs) elif operation == 'simplify': geometries = pg.simplify(pg.from_wkb(arr), *args, **kwargs) elif operation == 'snap': geometries = pg.snap(pg.from_wkb(arr), *args, **kwargs) elif operation == 'voronoi_polygons': geometries = pg.voronoi_polygons(pg.from_wkb(arr), **kwargs) else: warnings.warn(f'Operation {operation} not supported.') return None return pg.to_wkb(geometries)
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_normalize(geom, expected): actual = pygeos.normalize(geom) assert actual == expected
def test_make_valid_1d(geom, expected): actual = pygeos.make_valid(geom) # normalize needed to handle variation in output across GEOS versions assert np.all(pygeos.normalize(actual) == pygeos.normalize(expected))
def test_make_valid(geom, expected): actual = pygeos.make_valid(geom) assert actual is not expected # normalize needed to handle variation in output across GEOS versions assert pygeos.normalize(actual) == expected
bnd_df.to_feather(out_dir / "region_boundary.feather") bnd = bnd_df.geometry.values.data[0] sarp_bnd = bnd_df.loc[bnd_df.id == "se"].geometry.values.data[0] bnd_geo = bnd_df.to_crs(GEO_CRS) bnd_geo["bbox"] = pg.bounds(bnd_geo.geometry.values.data).round(2).tolist() # used to render maps with open(ui_dir / "region_bounds.json", "w") as out: out.write(bnd_geo[["id", "bbox"]].to_json(orient="records")) # create mask world = pg.box(-180, -85, 180, 85) bnd_mask = bnd_geo.copy() bnd_mask["geometry"] = pg.normalize( pg.difference(world, bnd_mask.geometry.values.data)) write_dataframe(bnd_mask, out_dir / "region_mask.gpkg") ### Extract HUC4 units that intersect boundaries print("Extracting HUC2...") # First determine the HUC2s that overlap the region huc2_df = (read_dataframe( wbd_gdb, layer="WBDHU2", columns=["huc2"]).to_crs(CRS).rename(columns={"huc2": "HUC2"})) tree = pg.STRtree(huc2_df.geometry.values.data) # First extract SARP HUC2 sarp_ix = tree.query(sarp_bnd, predicate="intersects") sarp_huc2_df = huc2_df.iloc[sarp_ix].reset_index(drop=True)
bnd_df = gp.GeoDataFrame( geometry=[pg.union_all(pg.make_valid(bnd_df.geometry.values.data))], index=[0], crs=bnd_df.crs, ) bnd_df.to_feather(out_dir / "se_boundary.feather") write_dataframe(bnd_df, data_dir / "boundaries/se_boundary.fgb") # create GeoJSON for tiling bnd_geo = bnd_df.to_crs(GEO_CRS) write_dataframe(bnd_geo, tile_dir / "se_boundary.geojson", driver="GeoJSONSeq") ### Create mask by cutting SA bounds out of world bounds print("Creating mask...") world = pg.box(-180, -85, 180, 85) mask = pg.normalize(pg.difference(world, bnd_geo.geometry.values.data)) write_dataframe( gp.GeoDataFrame({"geometry": mask}, index=[0], crs=GEO_CRS), tile_dir / "se_mask.geojson", driver="GeoJSONSeq", ) ### Extract counties within SA bounds print("Extracting states and counties...") states = (read_dataframe( src_dir / "boundaries/tl_2019_us_state.shp", read_geometry=False, columns=["STATEFP", "NAME"], ).rename(columns={ "NAME": "state"
data_dir = Path("data") out_dir = data_dir / "inputs/boundaries" # used as inputs for other steps tile_dir = data_dir / "for_tiles" sa_df = read_dataframe(src_dir / "boundaries/SABlueprint2020_Extent.shp") ### Create mask by cutting SA bounds out of world bounds print("Creating mask...") world = pg.box(-180, -85, 180, 85) # boundary has self-intersections and 4 geometries, need to clean up bnd = pg.union_all(pg.make_valid(sa_df.geometry.values.data)) bnd_geo = pg.union_all( pg.make_valid(sa_df.to_crs(GEO_CRS).geometry.values.data)) mask = pg.normalize(pg.difference(world, bnd_geo)) gp.GeoDataFrame(geometry=[bnd], crs=DATA_CRS).to_feather(out_dir / "sa_boundary.feather") write_dataframe( gp.GeoDataFrame({"geometry": bnd_geo}, index=[0], crs=GEO_CRS), tile_dir / "sa_boundary.geojson", driver="GeoJSONSeq", ) write_dataframe( gp.GeoDataFrame({"geometry": mask}, index=[0], crs=GEO_CRS), tile_dir / "sa_mask.geojson", driver="GeoJSONSeq", )