def test_invalid_filter_type(test_pbf): from pyrosm import OSM osm = OSM(filepath=test_pbf) try: osm.get_network("MyNetwork") except ValueError: pass except Exception as e: raise e
def test_getting_nodes_and_edges(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import Point, LineString osm = OSM(filepath=test_pbf) nodes, edges = osm.get_network(nodes=True) nodes = nodes.reset_index(drop=True) assert isinstance(edges, GeoDataFrame) assert isinstance(edges.loc[0, 'geometry'], LineString) assert isinstance(nodes, GeoDataFrame) assert isinstance(nodes.loc[0, 'geometry'], Point) # Test shape assert edges.shape == (1215, 23) assert nodes.shape == (1147, 8) # Edges should have "u" and "v" columns required = ["u", "v", "length"] ecols = edges.columns for col in required: assert col in ecols # Nodes should have (at least) "id", "lat", and "lon" columns required = ["id", "lat", "lon"] ncols = nodes.columns for col in required: assert col in ncols
def test_parse_network_with_shapely_bbox(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import MultiLineString, box bounds = box(*[26.94, 60.525, 26.96, 60.535]) # Init with bounding box osm = OSM(filepath=test_pbf, bounding_box=bounds) gdf = osm.get_network() assert isinstance(gdf.loc[0, 'geometry'], MultiLineString) assert isinstance(gdf, GeoDataFrame) # Test shape assert gdf.shape == (74, 21) required_cols = [ 'access', 'bridge', 'foot', 'highway', 'lanes', 'lit', 'maxspeed', 'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry', 'tags', 'osm_type', 'length' ] for col in required_cols: assert col in gdf.columns # Should not include 'motorway' ways by default assert "motorway" not in gdf["highway"].unique() # The total bounds of the result should not be larger than the filter # (allow some rounding error) result_bounds = gdf.total_bounds for coord1, coord2 in zip(bounds.bounds, result_bounds): assert round(coord2, 3) >= round(coord1, 3)
def test_saving_network_to_shapefile(test_pbf, test_output_dir): import os from pyrosm import OSM import geopandas as gpd import shutil if not os.path.exists(test_output_dir): os.makedirs(test_output_dir) temp_path = os.path.join(test_output_dir, "pyrosm_test.shp") osm = OSM(filepath=test_pbf) gdf = osm.get_network(network_type="cycling") gdf.to_file(temp_path) # Ensure it can be read and matches with original one gdf2 = gpd.read_file(temp_path) cols = gdf.columns for col in cols: # Geometry col might contain different types of geoms # (due to saving MultiLineGeometries which might be read as a "single") if col == "geometry": continue assert gdf[col].tolist() == gdf2[col].tolist() # Clean up shutil.rmtree(test_output_dir)
def test_directed_edge_generator(test_pbf): from geopandas import GeoDataFrame from pyrosm.graphs import generate_directed_edges from pyrosm import OSM osm = OSM(test_pbf) nodes, edges = osm.get_network(nodes=True) # Calculate the number of edges that should be oneway + bidirectional mask = edges[oneway_col].isin(oneway_values) oneway_edge_cnt = len(edges.loc[mask]) twoway_edge_cnt = len(edges.loc[~mask]) # Bidirectional edges bidir_edges = generate_directed_edges(edges, direction="oneway", from_id_col="u", to_id_col="v", force_bidirectional=True) assert len(bidir_edges) == 2 * len(edges) # Directed edges according the rules in "oneway" column dir_edges = generate_directed_edges(edges, direction="oneway", from_id_col="u", to_id_col="v", force_bidirectional=False) assert len(dir_edges) == oneway_edge_cnt + twoway_edge_cnt * 2
def bike_nodes_and_edges(): from pyrosm import OSM # UlanBator is good small dataset for testing # (unmodified, i.e. not cropped) pbf_path = get_data("ulanbator") osm = OSM(pbf_path) return osm.get_network(nodes=True, network_type="cycling")
def test_adding_extra_attribute(helsinki_pbf): from pyrosm import OSM from geopandas import GeoDataFrame osm = OSM(filepath=helsinki_pbf) gdf = osm.get_network() extra_col = "wikidata" extra = osm.get_network(extra_attributes=[extra_col]) # The extra should have one additional column compared to the original one assert extra.shape[1] == gdf.shape[1] + 1 # Should have same number of rows assert extra.shape[0] == gdf.shape[0] assert extra_col in extra.columns assert len(extra[extra_col].dropna().unique()) > 0 assert isinstance(gdf, GeoDataFrame)
def test_getting_nodes_and_edges_with_bbox(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import Point, LineString, box bounds = [26.94, 60.525, 26.96, 60.535] # Init with bounding box osm = OSM(filepath=test_pbf, bounding_box=bounds) nodes, edges = osm.get_network(nodes=True) nodes = nodes.reset_index(drop=True) assert isinstance(edges, GeoDataFrame) assert isinstance(edges.loc[0, 'geometry'], LineString) assert isinstance(nodes, GeoDataFrame) assert isinstance(nodes.loc[0, 'geometry'], Point) # Test shape assert edges.shape == (321, 23) assert nodes.shape == (317, 8) # Edges should have "u" and "v" columns required = ["u", "v", "length"] ecols = edges.columns for col in required: assert col in ecols # Nodes should have (at least) "id", "lat", and "lon" columns required = ["id", "lat", "lon"] ncols = nodes.columns for col in required: assert col in ncols
def compute_geometry(self, bbox, filename=None): """ Parse OSM file (area in bbox) to retrieve information about geometry. :param Sequence[float] bbox: area to be parsed in format (min_lon, min_lat, max_lon, max_lat) :param Optional[str] filename: map file in .osm.pbf format or None (map will be downloaded) """ assert len(bbox) == 4 self.bbox_size = (fabs(bbox[2] - bbox[0]), fabs(bbox[3] - bbox[1])) if filename is None: converter = OsmConverter(bbox) filename = converter.filename osm = OSM(filename, bounding_box=bbox) multipolygons = GeoDataFrame(columns=['tag', 'geometry']) natural = osm.get_natural() if natural is not None: natural = natural.loc[:, ['natural', 'geometry']].rename( columns={'natural': 'tag'}) self.polygons = self.polygons.append( natural.loc[natural.geometry.type == 'Polygon']) multipolygons = multipolygons.append( natural.loc[natural.geometry.type == 'MultiPolygon']) natural.drop(natural.index, inplace=True) landuse = osm.get_landuse() if landuse is not None: landuse = landuse.loc[:, ['landuse', 'geometry']].rename( columns={'landuse': 'tag'}) self.polygons = self.polygons.append( landuse.loc[landuse.geometry.type == 'Polygon']) multipolygons = multipolygons.append( landuse.loc[landuse.geometry.type == 'MultiPolygon']) landuse.drop(landuse.index, inplace=True) # splitting multipolygons to polygons for i in range(multipolygons.shape[0]): tag = multipolygons.tag.iloc[i] for polygon in multipolygons.geometry.iloc[i].geoms: self.polygons = self.polygons.append( { 'tag': tag, 'geometry': polygon }, ignore_index=True) roads = osm.get_network() if roads is not None: roads = self.__dissolve(roads[["highway", "geometry"]]) self.multilinestrings = GeoDataFrame( roads.loc[roads.geometry.type == 'MultiLineString']).rename( columns={'highway': 'tag'}) self.tag_value.eval(self.polygons, self.multilinestrings, "tag")
def test_pdgraph_connectivity(): from pyrosm.graphs import to_pandana import pandas as pd from pyrosm import OSM osm = OSM(get_data("helsinki_pbf")) nodes, edges = osm.get_network(nodes=True) # Prerare some test data for aggregations restaurants = osm.get_pois(custom_filter={"amenity": ["restaurant"]}) restaurants = restaurants.loc[restaurants["osm_type"] == "node"] restaurants["employee_cnt"] = 1 x = restaurants["lon"] y = restaurants["lat"] g = to_pandana(nodes, edges, retain_all=False) # Nodes and edges should be in DataFrames assert isinstance(g.nodes_df, pd.DataFrame) assert isinstance(g.edges_df, pd.DataFrame) # Precompute up to 1000 meters g.precompute(1000) # Link restaurants to graph g.set_pois("restaurants", 1000, 5, x, y) # Find the distance to nearest 5 restaurants from each node nearest_restaurants = g.nearest_pois(1000, "restaurants", num_pois=5) assert isinstance(nearest_restaurants, pd.DataFrame) assert nearest_restaurants.shape == (5750, 5) # Get closest node_ids for each restaurant node_ids = g.get_node_ids(x, y) assert isinstance(node_ids, pd.Series) assert node_ids.min() > 0 restaurants["node_id"] = node_ids # Attach employee counts to the graph g.set(node_ids, variable=restaurants.employee_cnt, name="employee_cnt") # Aggregate the number of employees within 500 meters from each node access = g.aggregate(500, type="sum", decay="linear", name="employee_cnt") assert isinstance(access, pd.Series) assert len(access) == 5750 # Test shortest path calculations shortest_distances = g.shortest_path_lengths(node_ids[0:100], node_ids[100:200], imp_name="length") assert isinstance(shortest_distances, list) assert len(shortest_distances) == 100 shortest_distances = pd.Series(shortest_distances) assert shortest_distances.min().round(0) == 22 assert shortest_distances.max().round(0) == 2453 assert shortest_distances.mean().round(0) == 856
def test_to_graph_api(test_pbf): from pyrosm import OSM import networkx as nx import igraph import pandana osm = OSM(test_pbf) nodes, edges = osm.get_network(nodes=True) # igraph is the default ig = osm.to_graph(nodes, edges) nxg = osm.to_graph(nodes, edges, graph_type="networkx") pdg = osm.to_graph(nodes, edges, graph_type="pandana") assert isinstance(nxg, nx.MultiDiGraph) assert isinstance(ig, igraph.Graph) assert isinstance(pdg, pandana.Network)
def test_passing_incorrect_net_type(test_pbf): from pyrosm import OSM osm = OSM(filepath=test_pbf) try: osm.get_network("wrong_network") except ValueError as e: if "'network_type' should be one of the following" in str(e): pass else: raise (e) except Exception as e: raise e try: osm.get_network(42) except ValueError as e: if "'network_type' should be one of the following" in str(e): pass else: raise (e) except Exception as e: raise e
def test_nxgraph_immutable_counts(test_pbf): from geopandas import GeoDataFrame from pyrosm.graphs import to_networkx import networkx as nx from pyrosm import OSM osm = OSM(test_pbf) nodes, edges = osm.get_network(nodes=True) g = to_networkx(nodes, edges, retain_all=True) n_nodes = len(nodes) assert isinstance(g, nx.MultiDiGraph) # Check that the edge count matches assert nx.number_of_edges(g) == 2430 assert nx.number_of_nodes(g) == n_nodes
def test_graph_exports_correct_number_of_nodes(test_pbf): """ Check issue: #97 """ from pyrosm import OSM osm = OSM(test_pbf) # NetworkX nodes, edges = osm.get_network(nodes=True) node_cnt = len(nodes) nxg = osm.to_graph(nodes, edges, graph_type="networkx", osmnx_compatible=False, retain_all=True) assert node_cnt == nxg.number_of_nodes()
def test_reading_network_from_area_without_data(helsinki_pbf): from pyrosm import OSM from geopandas import GeoDataFrame # Bounding box for area that does not have any data bbox = [24.940514, 60.173849, 24.942, 60.175892] osm = OSM(filepath=helsinki_pbf, bounding_box=bbox) # The tool should warn if no buildings were found with pytest.warns(UserWarning) as w: gdf = osm.get_network() # Check the warning text if "could not find any network data" in str(w): pass # Result should be None assert gdf is None
def test_igraph_immutable_counts(test_pbf): """ A simple check to ensure that the graph shape is always the same with unmutable data. """ from geopandas import GeoDataFrame from pyrosm.graphs import to_igraph import igraph from pyrosm import OSM osm = OSM(test_pbf) nodes, edges = osm.get_network(nodes=True) g = to_igraph(nodes, edges, retain_all=True) n_nodes = len(nodes) assert isinstance(g, igraph.Graph) # Check that the edge count matches assert g.ecount() == 2430 assert g.vcount() == n_nodes
def test_filter_network_by_all(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import MultiLineString osm = OSM(filepath=test_pbf) gdf = osm.get_network(network_type="all") assert isinstance(gdf.loc[0, 'geometry'], MultiLineString) assert isinstance(gdf, GeoDataFrame) # Test shape assert gdf.shape == (331, 22) required_cols = [ 'access', 'bicycle', 'bridge', 'foot', 'highway', 'lanes', 'lit', 'maxspeed', 'name', 'oneway', 'ref', 'service', 'surface', 'tunnel', 'id', 'geometry', 'tags', 'osm_type', 'length' ] for col in required_cols: assert col in gdf.columns
def test_filter_network_by_walking(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import MultiLineString osm = OSM(filepath=test_pbf) gdf = osm.get_network(network_type="walking") assert isinstance(gdf.loc[0, 'geometry'], MultiLineString) assert isinstance(gdf, GeoDataFrame) # Test shape assert gdf.shape == (265, 21) required_cols = [ 'access', 'bridge', 'foot', 'highway', 'lanes', 'lit', 'maxspeed', 'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry', 'tags', 'osm_type', 'length' ] for col in required_cols: assert col in gdf.columns # Should not include 'motorway' ways by default assert "motorway" not in gdf["highway"].unique()
def test_saving_network_to_shapefile(test_pbf, test_output_dir): import os from pyrosm import OSM import geopandas as gpd import shutil if not os.path.exists(test_output_dir): os.makedirs(test_output_dir) temp_path = os.path.join(test_output_dir, "pyrosm_test.shp") osm = OSM(filepath=test_pbf) gdf = osm.get_network(network_type="cycling") gdf.to_file(temp_path) # Ensure it can be read and matches with original one gdf2 = gpd.read_file(temp_path) cols = gdf.columns for col in cols: assert gdf[col].tolist() == gdf2[col].tolist() # Clean up shutil.rmtree(test_output_dir)
def test_filter_network_by_driving_with_service_roads(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame from shapely.geometry import LineString osm = OSM(filepath=test_pbf) gdf = osm.get_network(network_type="driving+service") assert isinstance(gdf.loc[0, 'geometry'], LineString) assert isinstance(gdf, GeoDataFrame) # Test shape assert gdf.shape == (207, 18) required_cols = [ 'access', 'bridge', 'highway', 'int_ref', 'lanes', 'lit', 'maxspeed', 'name', 'oneway', 'ref', 'service', 'surface', 'id', 'geometry', 'tags', 'osm_type' ] for col in required_cols: assert col in gdf.columns # Should not include 'footway' or 'path' ways by default assert "footway" not in gdf["highway"].unique() assert "path" not in gdf["highway"].unique()
def immutable_nodes_and_edges(): from pyrosm import OSM pbf_path = get_data("test_pbf") osm = OSM(pbf_path) return osm.get_network(nodes=True)
def driving_nodes_and_edges(): from pyrosm import OSM pbf_path = get_data("ulanbator") osm = OSM(pbf_path) return osm.get_network(network_type="driving", nodes=True)
import osmnx as ox from pyrosm import OSM # get real map in PBF fromat from https://extract.bbbike.org/ # install pyrosm and all dependencies via pip install pyrosm osmnx networkx # Initialize the reader osm = OSM("cpii.osm.pbf") # Get all walkable roads and the nodes nodes, edges = osm.get_network(nodes=True, network_type="driving") # Create NetworkX graph G = osm.to_graph(nodes, edges, graph_type="networkx") ccc = edges.head(40) print(ccc.geometry) ox.plot_graph(G)
def test_network(test_pbf): from pyrosm import OSM from geopandas import GeoDataFrame osm = OSM(test_pbf) gdf = osm.get_network() assert isinstance(gdf, GeoDataFrame)
class OSMMap(Map): def __init__(self, region): super().__init__() self.region = region fp = get_data(region) self.osm = OSM(fp) self.osm.keep_node_info = True self.network = self.osm.get_network( "all", extra_attributes=[ "lanes:forward", "lanes:backward", "cycleway:left", "cycleway:right", ], ) self.node_tags = (self.create_node_tags_lookup() ) # Used to find traffic signals, all-way stops self.intersections = GeometricDict(crs="EPSG:4326") self.build_intersections(self.intersections) self.links = Links(crs="EPSG:4326") self.build_links(self.links) self.look_for_stop_signs() logging.info("Generated %s intersections and %s links." % (len(self.intersections), len(self.links))) def create_node_tags_lookup(self): ids = np.concatenate([group["id"] for group in self.osm._nodes]) tags = np.concatenate([group["tags"] for group in self.osm._nodes]) return {ids[i]: tags[i] for i in range(0, len(ids))} def build_intersections(self, intersections): # Find all intersections by going through all the ways in the network and counting how often a node is referenced. If it is referenced more than once, it is an intersection. counter = Counter() for nodes in self.network.nodes: for node in nodes: counter[node] += 1 intersections_list = [ key for key, count in counter.items() if count >= 2 ] logging.info("Building intersections...") for intersection in tqdm(intersections_list): name = intersection coordinates = self.osm._node_coordinates[intersection] geometry = Point(coordinates["lon"], coordinates["lat"]) if (self.node_tags[intersection] and "highway" in self.node_tags[intersection] and self.node_tags[intersection]["highway"] == "traffic_signals"): type_ = "Signal" elif (self.node_tags[intersection] and "highway" in self.node_tags[intersection] and self.node_tags[intersection]["highway"] == "stop"): type_ = "All-way Stop" else: type_ = "Yield" intersections[intersection] = Intersection(name, geometry, type_=type_) @staticmethod def speed_converter(speed): if speed is None: return DEFAULT_SPEED speed_split = speed.split() if len(speed_split) == 2: if speed_split[1] == "mph": speed_unit = units.imperial.mile / units.hour elif speed_split[1] == "km/h": speed_unit = units.kilometer / units.hour else: speed_unit = DEFAULT_SPEED_UNIT return speed_split[0] * speed_unit else: logging.warning("No speed unit: %s" % (speed, )) return speed_split[0] * DEFAULT_SPEED_UNIT def create_link_segment(self, nodes, link_id): linestring = [] for node in nodes: coordinates = self.osm._node_coordinates[node] linestring.append(Point(coordinates["lon"], coordinates["lat"])) link_geometry = LineString(linestring) if nodes[0] in self.intersections.keys(): start_node = self.intersections[nodes[0]] else: start_node = None if nodes[-1] in self.intersections.keys(): end_node = self.intersections[nodes[-1]] else: end_node = None return { "link_geometry": link_geometry, "start_node": start_node, "end_node": end_node, "link_id": link_id, } def build_link_segments(self, link): link_segments = [] link_id = 0 nodes = [link.nodes[0]] for node in link.nodes[1:]: nodes.append(node) if node in self.intersections.keys(): link_segments.append(self.create_link_segment(nodes, link_id)) link_id += 1 nodes = [node] if len(nodes) > 1: link_segments.append(self.create_link_segment(nodes, link_id)) return link_segments def lane_calculator(self, link): if link["oneway"] == "yes": if link["lanes:forward"]: number_of_lanes_forward = int(link["lanes:forward"]) elif link["lanes"]: number_of_lanes_forward = int(link["lanes"]) else: number_of_lanes_forward = 1 number_of_lanes_backward = 0 elif link["lanes:forward"]: number_of_lanes_forward = int(link["lanes:forward"]) if link["lanes:backward"]: number_of_lanes_backward = int(link["lanes:backward"]) if link["lanes"]: if number_of_lanes_forward + number_of_lanes_backward != int( link["lanes"]): logging.warning( "lanes:forward + lanes:backward != lanes %s" % link["name"]) elif link["lanes"]: number_of_lanes_backward = int( link["lanes"]) - number_of_lanes_forward else: number_of_lanes_backward = 1 elif link["lanes"]: if link["lanes"] == "1": logging.warning("two way link with only one lane %s" % link["id"]) number_of_lanes_forward = 1 number_of_lanes_backward = 1 number_of_lanes_forward = math.ceil(int(link["lanes"]) / 2) number_of_lanes_backward = int(link["lanes"]) // 2 else: number_of_lanes_forward = 1 number_of_lanes_backward = 1 return number_of_lanes_forward, number_of_lanes_backward def cycleway_calculator(self, link): cycleway_types = ["lane", "track"] if link["oneway"] == "yes": if link["cycleway"] in cycleway_types: cycleway_forward = link["cycleway"] cycleway_backward = None elif link["cycleway:right"] in cycleway_types: cycleway_forward = link["cycleway:right"] cycleway_backward = None elif link["cycleway:left"] in cycleway_types: cycleway_forward = link["cycleway:left"] cycleway_backward = None else: cycleway_forward = None cycleway_backward = None else: if link["cycleway"] in cycleway_types: cycleway_forward = link["cycleway"] cycleway_backward = link["cycleway"] elif link["cycleway:right"] in cycleway_types: cycleway_forward = link["cycleway:right"] if link["cycleway:left"] in cycleway_types: cycleway_backward = link["cycleway:left"] else: cycleway_backward = None elif link["cycleway:left"] in cycleway_types: cycleway_backward = link["cycleway:left"] cycleway_forward = None else: cycleway_forward = None cycleway_backward = None return cycleway_forward, cycleway_backward def sidewalk_calculator(self, link): if hasattr(link, "foot"): if link["foot"] == "no": return False else: return True if hasattr(link, "highway"): if link["highway"] == "motorway": return False return True def banned_modes(self, link): """ Determine if any modes are banned on a link. Not used for now: we will trust the routing engine." """ banned_modes = [] if hasattr(link, "bicycle"): if link["bicycle"] == "no": banned_modes.append("bicycle") if hasattr(link, "highway"): if link["highway"] == "pedestrian": banned_modes.append("bicycle") banned_modes.append("driver") return set(banned_modes) def build_links(self, links): logging.info("Building links...") for _, link in tqdm(self.network.iterrows(), total=len(self.network)): number_of_lanes_forward, number_of_lanes_backward = self.lane_calculator( link) link_segments = self.build_link_segments(link) cycleway_forward, cycleway_backward = self.cycleway_calculator( link) sidewalk = self.sidewalk_calculator(link) highway = link["highway"] for segment in link_segments: link_id = str(link["id"]) + ":S" + str( segment["link_id"]) + ":D0" links[link_id] = Link( link_id, segment["link_geometry"], segment["start_node"], segment["end_node"], max_speed=self.speed_converter(link["maxspeed"]), highway=highway, ) links[link_id].update_segments_from_osm( number_of_lanes=number_of_lanes_forward, cycleway=cycleway_forward, sidewalk=sidewalk, ) if number_of_lanes_backward or cycleway_backward: reversed_segment_link_geometry = LineString( segment["link_geometry"].coords[::-1] ) # Flip it around for the other direction link_id = str(link["id"]) + ":S" + str( segment["link_id"]) + ":D1" links[link_id] = Link( link_id, reversed_segment_link_geometry, segment["end_node"], segment["start_node"], max_speed=self.speed_converter(link["maxspeed"]), highway=highway, ) links[link_id].update_segments_from_osm( number_of_lanes=number_of_lanes_backward, cycleway=cycleway_backward, sidewalk=sidewalk, ) def look_for_stop_signs(self): for intersection in self.intersections.values(): if intersection.type_ == "Yield": link_values = list( map( lambda link: HIGHWAY_HIERARCHY.index(link.highway), intersection.input_links, )) if link_values: highest_link_value = min(link_values) priority_links = [] for link in intersection.input_links: if HIGHWAY_HIERARCHY.index( link.highway) == highest_link_value: priority_links.append(link) intersection.priority_links = priority_links