def _cast(collection): """ Cast a collection to a pygeos geometry array. """ if isinstance(collection, (geopandas.GeoSeries, geopandas.GeoDataFrame)): return collection.geometry.values.data.squeeze() elif pygeos.is_geometry(collection).all(): if isinstance(collection, (numpy.ndarray, list)): return numpy.asarray(collection) else: return numpy.array([collection]) elif isinstance(collection, (numpy.ndarray, list)): return pygeos.from_shapely(collection).squeeze() else: return numpy.array([pygeos.from_shapely(collection)])
def _valid_hull(geoms, points): """Sanity check within ``alpha_shape_auto()`` to verify the generated alpha shape actually contains the original set of points (xys). Parameters ---------- geoms : GeoSeries See alpha_geoms() points : list xys parameter cast as shapely.geometry.Point objects Returns ------- flag : bool Valid hull for alpha shape [True] or not [False] """ flag = True # if there is not exactly one polygon if geoms.shape[0] != 1: return False # if any (xys) points do not intersect the polygon if HAS_PYGEOS: return pygeos.intersects(pygeos.from_shapely(geoms[0]), points).all() else: for point in points: if not point.intersects(geoms[0]): return False return True
def from_shapely(data): """ Convert a list or array of shapely objects to an object-dtype numpy array of validated geometry elements. """ # First try a fast path for pygeos if possible, but do this in a try-except # block because pygeos.from_shapely only handles Shapely objects, while # the rest of this function is more forgiving (also __geo_interface__). if compat.USE_PYGEOS and compat.PYGEOS_SHAPELY_COMPAT: if not isinstance(data, np.ndarray): arr = np.empty(len(data), dtype=object) with compat.ignore_shapely2_warnings(): arr[:] = data else: arr = data try: return pygeos.from_shapely(arr) except TypeError: pass out = [] for geom in data: if compat.USE_PYGEOS and isinstance(geom, pygeos.Geometry): out.append(geom) elif isinstance(geom, BaseGeometry): if compat.USE_PYGEOS: out.append(_shapely_to_pygeos(geom)) else: out.append(geom) elif hasattr(geom, "__geo_interface__"): geom = shapely.geometry.asShape(geom) # asShape returns GeometryProxy -> trigger actual materialization # with one of its methods geom.wkb if compat.USE_PYGEOS: out.append(_shapely_to_pygeos(geom)) else: out.append(geom) elif _isna(geom): out.append(None) else: raise TypeError( "Input must be valid geometry objects: {0}".format(geom)) if compat.USE_PYGEOS: return np.array(out, dtype=object) else: # numpy can expand geometry collections into 2D arrays, use this # two-step construction to avoid this aout = np.empty(len(data), dtype=object) with compat.ignore_shapely2_warnings(): aout[:] = out return aout
def _cast(collection): """ Cast a collection to a pygeos geometry array. """ try: import pygeos, geopandas except (ImportError, ModuleNotFoundError) as exception: raise type(exception)( "pygeos and geopandas are required for map comparison statistics.") if isinstance(collection, (geopandas.GeoSeries, geopandas.GeoDataFrame)): return collection.geometry.values.data.squeeze() elif pygeos.is_geometry(collection).all(): if isinstance(collection, (numpy.ndarray, list)): return numpy.asarray(collection) else: return numpy.array([collection]) elif isinstance(collection, (numpy.ndarray, list)): return pygeos.from_shapely(collection).squeeze() else: return numpy.array([pygeos.from_shapely(collection)])
def _shapely_to_pygeos(geom): if geom is None: return None if compat.PYGEOS_SHAPELY_COMPAT: return pygeos.from_shapely(geom) # fallback going through WKB if geom.is_empty and geom.geom_type == "Point": # empty point does not roundtrip through WKB return pygeos.from_wkt("POINT EMPTY") else: return pygeos.from_wkb(geom.wkb)
def get_gdp_values(gdf, data_path): """[summary] Args: gdf ([type]): [description] Returns: [type]: [description] """ world_pop = os.path.join(data_path, 'global_gdp', 'GDP_2015.tif') gdf['geometry'] = gdf.geometry.apply(lambda x: loads(pygeos.to_wkb(x))) gdp = list(item['sum'] for item in zonal_stats(gdf.geometry, world_pop, stats="sum")) gdp = [x if x is not None else 0 for x in gdp] gdf['geometry'] = pygeos.from_shapely(gdf.geometry) return gdp
def country_grid_gdp_filled(trans_network, country, data_path, rough_grid_split=100, from_main_graph=False): """[summary] Args: trans_network ([type]): [description] rough_grid_split (int, optional): [description]. Defaults to 100. Returns: [type]: [description] """ if from_main_graph == True: node_df = trans_network.copy() envelop = pygeos.envelope( pygeos.multilinestrings(node_df.geometry.values)) height = np.sqrt(pygeos.area(envelop) / rough_grid_split) else: node_df = trans_network.nodes.copy() node_df.geometry, approximate_crs = convert_crs(node_df) envelop = pygeos.envelope( pygeos.multilinestrings(node_df.geometry.values)) height = np.sqrt(pygeos.area(envelop) / rough_grid_split) gdf_admin = pd.DataFrame(create_grid(create_bbox(node_df), height), columns=['geometry']) #load data and convert to pygeos country_shape = gpd.read_file(os.path.join(data_path, 'GADM', 'gadm36_levels.gpkg'), layer=0) country_shape = pd.DataFrame( country_shape.loc[country_shape.GID_0 == country]) country_shape.geometry = pygeos.from_shapely(country_shape.geometry) gdf_admin = pygeos.intersection(gdf_admin, country_shape.geometry) gdf_admin = gdf_admin.loc[~pygeos.is_empty(gdf_admin.geometry)] gdf_admin['centroid'] = pygeos.centroid(gdf_admin.geometry) gdf_admin['km2'] = area(gdf_admin) gdf_admin['gdp'] = get_gdp_values(gdf_admin, data_path) gdf_admin = gdf_admin.loc[gdf_admin.gdp > 0].reset_index() gdf_admin['gdp_area'] = gdf_admin.gdp / gdf_admin['km2'] return gdf_admin
def test_build_adjacent_matrix_pygeo(): vector_path = os.path.expanduser( '~/Data/dem_processing/grid_9053_tmp_files/20140701_dem_slope_bin_patches_all_polyTouchEdge.gpkg' ) polygons = vector_gpd.read_polygons_gpd(vector_path, b_fix_invalid_polygon=False) print('read %d polygons' % len(polygons)) # polygons = [item.buffer(1.0) for item in polygons] # for complex polygons, buffer take a long time. from pygeos import area, intersection, from_shapely, touches import numpy as np geo_polygons = from_shapely(polygons) print(geo_polygons) # inter_matrix = intersection(geo_polygons[:, np.newaxis], geo_polygons[np.newaxis, :]) # print(inter_matrix) # print(touches(geo_polygons[:, np.newaxis], geo_polygons[np.newaxis, :])) print(touches(geo_polygons, geo_polygons))
def create_OD_points(): """[summary] """ #load data and convert to pygeos gdf = gpd.read_file(r'/scistor/ivm/data_catalogue/open_street_map/GADM36/gadm36_levels.gpkg',layer=0) tqdm.pandas(desc='Convert geometries to pygeos') gdf = pd.DataFrame(gdf) gdf['geometry'] = gdf.geometry.progress_apply(dumps) # create OD points save_points_per_country = [] list_countries = list([x[1] for x in gdf.iterrows()]) #multiprocess all countries with Pool(cpu_count()-1) as pool: save_points_per_country = pool.map(create_country_OD_points,list_countries,chunksize=1) df_all = pd.concat(save_points_per_country) df_all['geometry'] = df_all.geometry.progress_apply(lambda x: pygeos.from_shapely(x)) df_all.reset_index(inplace=True,drop=True) df_all.to_csv('od_points_per_country.csv')
def test_from_shapely_arr(): actual = pygeos.from_shapely([ShapelyGeometryMock(point), None]) assert pygeos.equals(point, actual[0])
def test_from_shapely_incompatible_prepared(geom): actual = pygeos.from_shapely(ShapelyPreparedMock(geom)) assert isinstance(actual, pygeos.Geometry) assert pygeos.equals(geom, actual) assert geom._ptr != actual._ptr
def test_from_shapely_incompatible_none(): actual = pygeos.from_shapely(None) assert actual is None
def verify(g, shp, thorough=False): #--------------------------------------------------------------------- logger.info(' Verify grid against coastline\n') #--------------------------------------------------------------------- lon_min = g.Dataset.SCHISM_hgrid_node_x.values.min() lon_max = g.Dataset.SCHISM_hgrid_node_x.values.max() lat_min = g.Dataset.SCHISM_hgrid_node_y.values.min() lat_max = g.Dataset.SCHISM_hgrid_node_y.values.max() c = shp.cx[lon_min:lon_max, lat_min:lat_max] # ## Test polygons d = g.Dataset x = d.SCHISM_hgrid_node_x.values y = d.SCHISM_hgrid_node_y.values tri = d.SCHISM_hgrid_face_nodes.values nodes = pd.DataFrame({'lon': x, 'lat': y}) elems = pd.DataFrame(tri, columns=['a', 'b', 'c']) bnodes = g.Dataset[['node', 'id', 'type']].to_dataframe() # ### Find the invalid nodes (that cross the coasts) cos = pygeos.from_shapely(c.geometry) cos_ = pygeos.set_operations.union_all(cos) gps = pygeos.points(list(nodes.values)) gtree = pygeos.STRtree(gps) invs = gtree.query(cos_, predicate='contains').tolist() #--------------------------------------------------------------------- logger.info('Number of nodes within the coastlines {}\n'.format(len(invs))) #--------------------------------------------------------------------- nps = len(invs) nels = 1 if thorough: # ### Find invalid elements (that cross land) # cells to polygons ap = nodes.loc[elems.a] bp = nodes.loc[elems.b] cp = nodes.loc[elems.c] elems['ap'] = ap.values.tolist() elems['bp'] = bp.values.tolist() elems['cp'] = cp.values.tolist() n = 2 al = elems.ap + elems.bp + elems.cp + elems.ap coords = [[l[i:i + n] for i in range(0, len(l), n)] for l in al] elems['coordinates'] = coords jig = pygeos.polygons(coords) jtree = pygeos.STRtree(jig) jig_ = pygeos.set_operations.union_all(jig) cross = pygeos.set_operations.intersection(jig_, cos_) # #### convert to dataframe fd = pd.DataFrame({'overlap': pygeos.to_wkt(cross)}, index=[0]) fd['overlap'] = fd['overlap'].apply(shapely.wkt.loads) gover = gp.GeoDataFrame(fd, geometry='overlap') # #### Reject small injuctions ipols = gover.explode().loc[0] ipols.columns = ['geometry'] mask = ipols.area.values == 0. ipols = ipols[~mask].reset_index(drop=True) ipols = gp.GeoDataFrame(ipols) #--------------------------------------------------------------------- logger.info( 'Number of elements intersecting the coastlines {}\n'.format( ipols.shape[0])) #--------------------------------------------------------------------- nels = ipols.shape[0] if nps == 0 and nels == 0: #--------------------------------------------------------------------- logger.info('Grid is verified against the coastline') #--------------------------------------------------------------------- return True elif nps == 0: #--------------------------------------------------------------------- logger.info('Grid is node verified against the coastline') #--------------------------------------------------------------------- return True else: #--------------------------------------------------------------------- logger.warning('Grid is not verified against the coastline') #--------------------------------------------------------------------- return False
def global_shapefiles(data_path, regionalized=False, assigned_level=1): """ This function will simplify shapes and add necessary columns, to make further processing more quickly For now, we will make use of the latest GADM data, split by level: https://gadm.org/download_world.html Optional Arguments: *regionalized* : Default is **False**. Set to **True** will also create the global_regions.shp file. """ gadm_path = os.path.join(data_path, 'GADM36', 'gadm36_levels.gpkg') cleaned_shapes_path = os.path.join(data_path, 'cleaned_shapes') if not os.path.exists(cleaned_shapes_path): os.makedirs(cleaned_shapes_path) # path to country GADM file if regionalized == False: # load country file gadm_level0 = pandas.DataFrame( geopandas.read_file(gadm_path, layer='level0')) #convert to pygeos tqdm.pandas(desc='Convert geometries to pygeos') gadm_level0['geometry'] = gadm_level0.geometry.progress_apply( lambda x: pygeos.from_shapely(x)) # remove antarctica, no roads there anyways gadm_level0 = gadm_level0.loc[~gadm_level0['NAME_0']. isin(['Antarctica'])] # remove tiny shapes to reduce size substantially tqdm.pandas(desc='Remove tiny shapes') gadm_level0['geometry'] = gadm_level0.progress_apply( remove_tiny_shapes, axis=1) #simplify geometry tqdm.pandas(desc='Simplify geometry') gadm_level0.geometry = gadm_level0.geometry.progress_apply( lambda x: pygeos.simplify(pygeos.buffer( pygeos.simplify(x, tolerance=0.005, preserve_topology=True), 0.01), tolerance=0.005, preserve_topology=True)) #save to new country file glob_ctry_path = os.path.join(cleaned_shapes_path, 'global_countries.gpkg') tqdm.pandas(desc='Convert geometries back to shapely') gadm_level0.geometry = gadm_level0.geometry.progress_apply( lambda x: loads(pygeos.to_wkb(x))) geopandas.GeoDataFrame(gadm_level0).to_file(glob_ctry_path, layer='level0', driver="GPKG") else: # this is dependent on the country file, so check whether that one is already created: glob_ctry_path = os.path.join(cleaned_shapes_path, 'global_countries.gpkg') if os.path.exists(glob_ctry_path): gadm_level0 = geopandas.read_file(os.path.join(glob_ctry_path), layer='level0') else: print('ERROR: You need to create the country file first') return None # load region file gadm_level_x = pandas.DataFrame( geopandas.read_file(gadm_path, layer='level{}'.format(assigned_level))) #convert to pygeos tqdm.pandas(desc='Convert geometries to pygeos') gadm_level_x['geometry'] = gadm_level_x.geometry.progress_apply( lambda x: pygeos.from_shapely(x)) # remove tiny shapes to reduce size substantially tqdm.pandas(desc='Remove tiny shapes') gadm_level_x['geometry'] = gadm_level_x.progress_apply( remove_tiny_shapes, axis=1) #simplify geometry tqdm.pandas(desc='Simplify geometry') gadm_level_x.geometry = gadm_level_x.geometry.progress_apply( lambda x: pygeos.simplify(pygeos.buffer( pygeos.simplify(x, tolerance=0.005, preserve_topology=True), 0.01), tolerance=0.005, preserve_topology=True)) # add some missing geometries from countries with no subregions get_missing_countries = list( set(list(gadm_level0.GID_0.unique())).difference( list(gadm_level_x.GID_0.unique()))) #TO DO: GID_2 and lower tiers should first be filled by a tier above, rather then by the country file mis_country = gadm_level0.loc[gadm_level0['GID_0'].isin( get_missing_countries)] # if assigned_level == 1: mis_country['GID_1'] = mis_country['GID_0'] + '.' + str( 0) + '_' + str(1) elif assigned_level == 2: mis_country['GID_2'] = mis_country['GID_0'] + '.' + str( 0) + '.' + str(0) + '_' + str(1) elif assigned_level == 3: mis_country['GID_3'] = mis_country['GID_0'] + '.' + str( 0) + '.' + str(0) + '.' + str(0) + '_' + str(1) elif assigned_level == 4: mis_country['GID_4'] = mis_country['GID_0'] + '.' + str( 0) + '.' + str(0) + '.' + str(0) + '.' + str(0) + '_' + str(1) elif assigned_level == 5: mis_country['GID_5'] = mis_country['GID_0'] + '.' + str( 0) + '.' + str(0) + '.' + str(0) + '.' + str(0) + '.' + str( 0) + '_' + str(1) tqdm.pandas(desc='Convert geometries back to shapely') gadm_level_x.geometry = gadm_level_x.geometry.progress_apply( lambda x: loads(pygeos.to_wkb(x))) # concat missing country to gadm levels gadm_level_x = geopandas.GeoDataFrame( pandas.concat([gadm_level_x, mis_country], ignore_index=True)) gadm_level_x.reset_index(drop=True, inplace=True) #save to new country file gadm_level_x.to_file(os.path.join(cleaned_shapes_path, 'global_regions.gpkg'), layer='level{}'.format(assigned_level), driver="GPKG")
[30.78, 60.10], [75.21, 58.93], [79.26, 7.68], [8.23, 39.93], [98.73, 77.17], [89.78, 42.53], [65.19, 92.08], [54.46, 8.48], ] ) tree = spatial.cKDTree(points) chull = spatial.ConvexHull(points) ashape = alpha_shape_auto(points) pygeos_ashape = pygeos.from_shapely(ashape) bbox = numpy.asarray((*points.min(axis=0), *points.max(axis=0))) support = numpy.linspace(0, 100, num=15) d_self = spatial.distance.pdist(points) D_self = spatial.distance.squareform(d_self) try: numpy.random.seed(2478879) random_pattern = random.poisson(bbox, size=500) D_other = spatial.distance.cdist(points, random_pattern) except: # will cause failures in all ripley tests later from NameErrors about D_other # If D_other is missing, then test_simulate should also fail. pass
def test_from_shapely_error(geom): with pytest.raises(TypeError): pygeos.from_shapely(geom)
def test_from_shapely_incompatible_versions(): with pytest.raises(ImportError): pygeos.from_shapely(point)
def check(g, shp, bad): # ## Read Grid # g = pg.grid(type='tri2d',grid_file='/eos/jeodpp/data/projects/FLOODS-COAST/floods-coast/OPER/grids/eur_fixed.gr3') lon_min = g.Dataset.SCHISM_hgrid_node_x.values.min() lon_max = g.Dataset.SCHISM_hgrid_node_x.values.max() lat_min = g.Dataset.SCHISM_hgrid_node_y.values.min() lat_max = g.Dataset.SCHISM_hgrid_node_y.values.max() c = shp.cx[lon_min:lon_max, lat_min:lat_max] # ## Test polygons d = g.Dataset x = d.SCHISM_hgrid_node_x.values y = d.SCHISM_hgrid_node_y.values tri = d.SCHISM_hgrid_face_nodes.values nodes = pd.DataFrame({'lon': x, 'lat': y}) elems = pd.DataFrame(tri, columns=['a', 'b', 'c']) bnodes = g.Dataset[['node', 'id', 'type']].to_dataframe() # drop bad dpoints = [bad] # get corresponding elements ma = elems.a.isin(dpoints) mb = elems.b.isin(dpoints) mc = elems.c.isin(dpoints) ids1 = elems[ma].index ids2 = elems[mb].index ids3 = elems[mc].index dropels = list( np.hstack([ids1, ids2, ids3]) ) #these are the elements to be dropped - the ones which have dropped nodes dropels = np.unique(dropels) #take care of the boundary nodes dns = np.unique(elems.loc[dropels, ['a', 'b', 'c']].values.flatten()) #get boundary nodes ibs = [x for x in dns if x not in dpoints] maxb = bnodes.id.min() # check if new boundary nodes merge if bnodes.node.isin(ibs).sum() > 0: if bnodes.node.isin(ibs).sum() > 0: ids_ = np.unique(bnodes.loc[bnodes.node.isin(ibs), 'id'].values) ids_.sort() if ids_.shape[0] > 1: itype = bnodes.loc[bnodes.id == ids_[-1], ['type']].values[0] bnodes.loc[bnodes.id.isin(ids_[:-1]), 'id'] = ids_[-1] ibs = pd.DataFrame({ 'node': ibs, 'type': itype, 'id': ids_[-1] }, index=np.arange(len(ibs))) else: itype, iid = bnodes.loc[bnodes.node.isin(ibs), ['type', 'id']].values[0] ibs = pd.DataFrame({ 'node': ibs, 'type': itype, 'id': iid }, index=np.arange(len(ibs))) else: maxb -= 1 ibs = pd.DataFrame({ 'node': ibs, 'type': 1, 'id': maxb }, index=np.arange(len(ibs))) bnodes = pd.concat([bnodes, ibs], ignore_index=True) bnodes = bnodes.drop_duplicates() #remove nodes, elems = nreduce(dpoints, dropels, nodes, elems) bnodes = bnodes.drop(bnodes.loc[bnodes.node.isin(dpoints)].index) sg = nodes.reset_index().set_index('tag') idx = bnodes.node.values, 'index' nvs = sg.loc[idx].values bnodes.node = nvs bnodes = bnodes.reset_index(drop=True) bnodes.index.name = 'bnodes' bnodes = bnodes.drop_duplicates('node') nodes = nodes.drop('tag', axis=1) # #### Check for hanging nodes # Look for hanging nodes tri3 = elems.values[:, :3] q = np.unique(tri3.flatten()) # all the unique nodes in elements dq = list(set(range(nodes.shape[0])) - set(q)) # the ones that are in gcrop but not in elems dq.sort() if len(dq) > 0: nodes, elems, bnodes = drop(nodes, elems, bnodes, dq) # ### Find the invalid nodes (that cross the coasts) cos = pygeos.from_shapely(c.geometry) cos_ = pygeos.set_operations.union_all(cos) gps = pygeos.points(list(nodes.values)) gtree = pygeos.STRtree(gps) invs = gtree.query(cos_, predicate='contains').tolist() # ### Find invalid elements (that cross land) # cells to polygons ap = nodes.loc[elems.a] bp = nodes.loc[elems.b] cp = nodes.loc[elems.c] elems['ap'] = ap.values.tolist() elems['bp'] = bp.values.tolist() elems['cp'] = cp.values.tolist() n = 2 al = elems.ap + elems.bp + elems.cp + elems.ap coords = [[l[i:i + n] for i in range(0, len(l), n)] for l in al] elems['coordinates'] = coords jig = pygeos.polygons(coords) jtree = pygeos.STRtree(jig) jig_ = pygeos.set_operations.union_all(jig) cross = pygeos.set_operations.intersection(jig_, cos_) # #### convert to dataframe fd = pd.DataFrame({'overlap': pygeos.to_wkt(cross)}, index=[0]) fd['overlap'] = fd['overlap'].apply(shapely.wkt.loads) gover = gp.GeoDataFrame(fd, geometry='overlap') # #### Reject small injuctions ipols = gover.explode().loc[0] ipols.columns = ['geometry'] mask = ipols.area.values == 0. ipols = ipols[~mask].reset_index(drop=True) ipols = gp.GeoDataFrame(ipols) # Sometimes the edges cross the coastlines but the nodes not # ## Get overlapping elements for each contour jcos = pygeos.from_shapely(ipols.geometry) maxb = bnodes.id.min() if len(jcos) > 0: m = 0 for l in tqdm(range(len(jcos))): con = jcos[l] m += 1 jig = pygeos.polygons(elems.coordinates.to_list()) jtree = pygeos.STRtree(jig) ci = jtree.query(con, predicate='intersects').tolist() if not ci: continue delems = elems.loc[ci].index.values dnodes = np.unique(elems.loc[ci, ['a', 'b', 'c']].values.flatten()) # print (ci, dnodes, delems) inp = pygeos.points(list(nodes.loc[dnodes].values)) itree = pygeos.STRtree(inp) ipoints = itree.query(cos_, predicate='contains').tolist() ipoints = nodes.loc[dnodes].index[ipoints].values #find elements that use these points ma = elems.a.isin(ipoints) mb = elems.b.isin(ipoints) mc = elems.c.isin(ipoints) ids1 = elems[ma].index ids2 = elems[mb].index ids3 = elems[mc].index dropels = list( np.hstack([ids1, ids2, ids3]) ) #these are the elements to be dropped - the ones which have dropped nodes dropels = np.unique(dropels) dds = np.unique(np.concatenate((delems, dropels), axis=0)) #get all nodes dns = np.unique(elems.loc[dds, ['a', 'b', 'c']].values.flatten()) #get boundary nodes ibs = [x for x in dns if x not in ipoints] # print (ipoints, ibs) # check if new boundary nodes merge if bnodes.node.isin(ibs).sum() > 0: ids_ = np.unique(bnodes.loc[bnodes.node.isin(ibs), 'id'].values) ids_.sort() if ids_.shape[0] > 1: itype = bnodes.loc[bnodes.id == ids_[-1], ['type']].values[0] bnodes.loc[bnodes.id.isin(ids_[:-1]), 'id'] = ids_[-1] ibs = pd.DataFrame( { 'node': ibs, 'type': itype, 'id': ids_[-1] }, index=np.arange(len(ibs))) else: itype, iid = bnodes.loc[bnodes.node.isin(ibs), ['type', 'id']].values[0] ibs = pd.DataFrame({ 'node': ibs, 'type': itype, 'id': iid }, index=np.arange(len(ibs))) else: maxb -= 1 ibs = pd.DataFrame({ 'node': ibs, 'type': 1, 'id': maxb }, index=np.arange(len(ibs))) bnodes = pd.concat([bnodes, ibs], ignore_index=True) bnodes = bnodes.drop_duplicates() nodes, elems = nreduce(ipoints, dds, nodes, elems) # Renumber boundary nodes if ipoints.size > 0: bnodes = bnodes.drop( bnodes.loc[bnodes.node.isin(ipoints)].index) sg = nodes.reset_index().set_index('tag') # print(ipoints.size) idx = bnodes.node.values, 'index' nvs = sg.loc[idx].values bnodes.node = nvs nodes = nodes.drop('tag', axis=1) # #### Check for hanging nodes # Look for hanging nodes tri3 = elems.values[:, :3] q = np.unique(tri3.flatten()) # all the unique nodes in elements dq = list(set(range(nodes.shape[0])) - set(q)) # the ones that are in gcrop but not in elems dq.sort() if len(dq) > 0: nodes, elems, bnodes = drop(nodes, elems, bnodes, dq) # Get the largest continous area jels = pygeos.polygons(elems.coordinates.values.tolist()) wat = pygeos.set_operations.coverage_union_all(jels) w = pd.DataFrame({'overlap': pygeos.to_wkt(wat)}, index=[0]) w['overlap'] = w['overlap'].apply(shapely.wkt.loads) gw = gp.GeoDataFrame(w, geometry='overlap') gw = gw.explode() gw = gw.droplevel(0) gw.columns = ['geometry'] gw['length'] = gw['geometry'][:].length gw = gw.sort_values(by='length', ascending=0) #optional gw = gw.reset_index(drop=True) # indentify the elements of the large polygon cgw = pygeos.from_shapely(gw.loc[1:].geometry) cgw_ = pygeos.set_operations.union_all(cgw) jtree_ = pygeos.STRtree(jels) invs = jtree_.query(cgw_, predicate='intersects').tolist() if len(invs) > 0: #Sort the elements (some shouldn't be there) qnodes = np.unique( [elems.loc[x, ['a', 'b', 'c']].values.astype(int) for x in invs]) nnodes = pygeos.points(list(nodes.loc[qnodes].values)) ntree = pygeos.STRtree(nnodes) nels_ = pygeos.from_shapely(gw.loc[0].geometry.buffer(.00001)) nevs = ntree.query(nels_, predicate='intersects').tolist() pns = qnodes[nevs] dpoints = [x for x in qnodes if x not in pns] #find elements that use these points ma = elems.a.isin(dpoints) mb = elems.b.isin(dpoints) mc = elems.c.isin(dpoints) ids1 = elems[ma].index ids2 = elems[mb].index ids3 = elems[mc].index dropels = list( np.hstack([ids1, ids2, ids3]) ) #these are the elements to be dropped - the ones which have dropped nodes dropels = np.unique(dropels) #Remove the invalid elements nodes, elems = nreduce(dpoints, dropels, nodes, elems) #reindex boundary nodes bnodes = bnodes.drop(bnodes.loc[bnodes.node.isin(dpoints)].index) sg = nodes.reset_index().set_index('tag') idx = bnodes.node.values, 'index' nvs = sg.loc[idx].values bnodes.node = nvs bnodes = bnodes.reset_index(drop=True) bnodes.index.name = 'bnodes' nodes = nodes.drop('tag', axis=1) # Look for hanging nodes tri3 = elems.values[:, :3] q = np.unique(tri3.flatten()) # all the unique nodes in elements dq = list(set(range(nodes.shape[0])) - set(q)) # the ones that are in gcrop but not in elems dq.sort() if len(dq) > 0: nodes, elems, bnodes = drop(nodes, elems, bnodes, dq) # check if empty boundary lk = -1 for k in range(-1, bnodes.id.min().astype(int) - 1, -1): if not bnodes.loc[bnodes.id == k].empty: bnodes.loc[bnodes.id == k, 'id'] = lk lk -= 1 bnodes = bnodes.drop_duplicates('node') # ### create the new dataset nod = nodes.loc[:, ['lon', 'lat']].to_xarray().rename({ 'index': 'nSCHISM_hgrid_node', 'lon': 'SCHISM_hgrid_node_x', 'lat': 'SCHISM_hgrid_node_y' }) nod = nod.drop_vars('nSCHISM_hgrid_node') depx = xr.Dataset({ 'depth': (['nSCHISM_hgrid_node'], np.zeros(nod.nSCHISM_hgrid_node.shape[0])) }) elsx = xr.DataArray( elems.loc[:, ['a', 'b', 'c']].values, dims=['nSCHISM_hgrid_face', 'nMaxSCHISM_hgrid_face_nodes'], name='SCHISM_hgrid_face_nodes') ngr = xr.merge([nod, depx, elsx, bnodes.to_xarray()]) # total return ngr
def _morphological_tessellation(self, gdf, unique_id, limit, shrink, segment, verbose, check=True): objects = gdf.copy() if isinstance(limit, (gpd.GeoSeries, gpd.GeoDataFrame)): limit = limit.unary_union if isinstance(limit, BaseGeometry): limit = pygeos.from_shapely(limit) bounds = pygeos.bounds(limit) centre_x = (bounds[0] + bounds[2]) / 2 centre_y = (bounds[1] + bounds[3]) / 2 objects["geometry"] = objects["geometry"].translate(xoff=-centre_x, yoff=-centre_y) if shrink != 0: print("Inward offset...") if verbose else None mask = objects.type.isin(["Polygon", "MultiPolygon"]) objects.loc[mask, "geometry"] = objects[mask].buffer(-shrink, cap_style=2, join_style=2) objects = objects.reset_index(drop=True).explode() objects = objects.set_index(unique_id) print("Generating input point array...") if verbose else None points, ids = self._dense_point_array(objects.geometry.values.data, distance=segment, index=objects.index) # add convex hull buffered large distance to eliminate infinity issues series = gpd.GeoSeries(limit, crs=gdf.crs).translate(xoff=-centre_x, yoff=-centre_y) width = bounds[2] - bounds[0] leng = bounds[3] - bounds[1] hull = series.geometry[[0]].buffer(2 * width if width > leng else 2 * leng) # pygeos bug fix if (hull.type == "MultiPolygon").any(): hull = hull.explode() hull_p, hull_ix = self._dense_point_array( hull.values.data, distance=pygeos.length(limit) / 100, index=hull.index) points = np.append(points, hull_p, axis=0) ids = ids + ([-1] * len(hull_ix)) print("Generating Voronoi diagram...") if verbose else None voronoi_diagram = Voronoi(np.array(points)) print("Generating GeoDataFrame...") if verbose else None regions_gdf = self._regions(voronoi_diagram, unique_id, ids, crs=gdf.crs) print("Dissolving Voronoi polygons...") if verbose else None morphological_tessellation = regions_gdf[[unique_id, "geometry" ]].dissolve(by=unique_id, as_index=False) morphological_tessellation = gpd.clip(morphological_tessellation, series) morphological_tessellation["geometry"] = morphological_tessellation[ "geometry"].translate(xoff=centre_x, yoff=centre_y) if check: self._check_result(morphological_tessellation, gdf, unique_id=unique_id) return morphological_tessellation
def test_from_shapely(geom): actual = pygeos.from_shapely(ShapelyGeometryMock(geom)) assert isinstance(geom, pygeos.Geometry) assert pygeos.equals(geom, actual) assert geom._ptr != actual._ptr
def line_merge(x): if x.geom_type == 'MultiLineString': return shapely.wkb.loads( pygeos.to_wkb(pygeos.linear.line_merge(pygeos.from_shapely(x)))) else: return x
def __init__( self, gdf, unique_id, limit=None, shrink=0.4, segment=0.5, verbose=True, enclosures=None, enclosure_id="eID", threshold=0.05, use_dask=True, n_chunks=None, **kwargs, ): self.gdf = gdf self.id = gdf[unique_id] self.limit = limit self.shrink = shrink self.segment = segment self.enclosure_id = enclosure_id if limit is not None and enclosures is not None: raise ValueError( "Both `limit` and `enclosures` cannot be passed together. " "Pass `limit` for morphological tessellation or `enclosures` " "for enclosed tessellation.") gdf = gdf.copy() if enclosures is not None: enclosures = enclosures.copy() bounds = enclosures.total_bounds centre_x = (bounds[0] + bounds[2]) / 2 centre_y = (bounds[1] + bounds[3]) / 2 gdf.geometry = gdf.geometry.translate(xoff=-centre_x, yoff=-centre_y) enclosures.geometry = enclosures.geometry.translate(xoff=-centre_x, yoff=-centre_y) self.tessellation = self._enclosed_tessellation( gdf, enclosures, unique_id, enclosure_id, threshold, use_dask, n_chunks, ) else: if isinstance(limit, (gpd.GeoSeries, gpd.GeoDataFrame)): limit = limit.unary_union if isinstance(limit, BaseGeometry): limit = pygeos.from_shapely(limit) bounds = pygeos.bounds(limit) centre_x = (bounds[0] + bounds[2]) / 2 centre_y = (bounds[1] + bounds[3]) / 2 gdf.geometry = gdf.geometry.translate(xoff=-centre_x, yoff=-centre_y) # add convex hull buffered large distance to eliminate infinity issues limit = (gpd.GeoSeries(limit, crs=gdf.crs).translate( xoff=-centre_x, yoff=-centre_y).values.data[0]) self.tessellation = self._morphological_tessellation( gdf, unique_id, limit, shrink, segment, verbose) self.tessellation["geometry"] = self.tessellation[ "geometry"].translate(xoff=centre_x, yoff=centre_y)
def enclosures(primary_barriers, limit=None, additional_barriers=None, enclosure_id="eID"): """ Generate enclosures based on passed barriers. Enclosures are areas enclosed from all sides by at least one type of a barrier. Barriers are typically roads, railways, natural features like rivers and other water bodies or coastline. Enclosures are a result of polygonization of the ``primary_barrier`` and ``limit`` and its subdivision based on additional_barriers. Parameters ---------- primary_barriers : GeoDataFrame, GeoSeries GeoDataFrame or GeoSeries containing primary barriers. (Multi)LineString geometry is expected. limit : GeoDataFrame, GeoSeries (default None) GeoDataFrame or GeoSeries containing external limit of enclosures, i.e. the area which gets partitioned. If None is passed, the internal area of ``primary_barriers`` will be used. additional_barriers : GeoDataFrame GeoDataFrame or GeoSeries containing additional barriers. (Multi)LineString geometry is expected. enclosure_id : str (default 'eID') name of the enclosure_id (to be created). Returns ------- enclosures : GeoDataFrame GeoDataFrame containing enclosure geometries and enclosure_id Examples -------- >>> enclosures = mm.enclosures(streets, admin_boundary, [railway, rivers]) """ if limit is not None: if limit.geom_type.isin(["Polygon", "MultiPolygon"]).any(): limit = limit.boundary barriers = pd.concat([primary_barriers.geometry, limit.geometry]) else: barriers = primary_barriers unioned = barriers.unary_union polygons = polygonize(unioned) enclosures = gpd.GeoSeries(list(polygons), crs=primary_barriers.crs) if additional_barriers is not None: if not isinstance(additional_barriers, list): raise TypeError( "`additional_barriers` expects a list of GeoDataFrames or GeoSeries." f"Got {type(additional_barriers)}.") additional = pd.concat([gdf.geometry for gdf in additional_barriers]) inp, res = enclosures.sindex.query_bulk(additional.geometry, predicate="intersects") unique = np.unique(res) new = [] for i in unique: poly = enclosures.values.data[i] # get enclosure polygon crossing = inp[res == i] # get relevant additional barriers buf = pygeos.buffer(poly, 0.01) # to avoid floating point errors crossing_ins = pygeos.intersection( buf, additional.values.data[crossing] ) # keeping only parts of additional barriers within polygon union = pygeos.union_all( np.append(crossing_ins, pygeos.boundary(poly))) # union polygons = np.array(list(polygonize( _pygeos_to_shapely(union)))) # polygonize within = pygeos.covered_by( pygeos.from_shapely(polygons), buf) # keep only those within original polygon new += list(polygons[within]) final_enclosures = (gpd.GeoSeries(enclosures).drop(unique).append( gpd.GeoSeries(new)).reset_index(drop=True)).set_crs( primary_barriers.crs) return gpd.GeoDataFrame({enclosure_id: range(len(final_enclosures))}, geometry=final_enclosures) return gpd.GeoDataFrame({enclosure_id: range(len(enclosures))}, geometry=enclosures)
def test_from_shapely_none(): actual = pygeos.from_shapely(None) assert actual is None