def custom_graph_from_x( *args: List[Any], graph_from_x: Callable = osmnx.graph_from_place, node_tags: Optional[List[str]] = None, edge_tags: Optional[List[str]] = None, api_key: Optional[str] = None, ) -> networkx.DiGraph: """ Download map from OSM. """ with osmnx_config(node_tags or [], edge_tags or []): graph = graph_from_x( *args, network_type="drive", retain_all=False, # only keep biggest connected network truncate_by_edge= True, # Keep entirety of edges, rather than cropping at distance limit simplify= False, # Do not correct and simplify street network topology ) # add additional attributes logger.info("Enhance map with additional attributes") logger.info("> Tag node as roundabout") tag_roundabout_nodes(graph) logger.info("> Adding street count to nodes") street_count_data = osmnx.utils_graph.count_streets_per_node(graph) for node_id, street_count in street_count_data.items(): graph.nodes[node_id]["street_count"] = street_count if api_key: logger.info("> Adding elevation") osmnx.add_node_elevations(graph, api_key, precision=5) logger.info("> Add edge grades") osmnx.elevation.add_edge_grades(graph) logger.info("> Adding bearing") osmnx.bearing.add_edge_bearings(graph) logger.info("> Adding edge speeds") osmnx.speed.add_edge_speeds(graph, hwy_speeds=None, fallback=30) logger.info("> Adding travel time") osmnx.speed.add_edge_travel_times(graph) return graph
def get_map_data_with_elevation(self, city_name, key): # get model data from osmnx of 2d and elevation #city_name = 'Amherst' # 'Springfield' place_query = { 'city': city_name, 'state': 'Massachusetts', 'country': 'USA' } graph_orig = ox.graph_from_place(place_query, network_type='walk') #add Elevation data from GoogleMaps key = self.get_key() graph_orig = ox.add_node_elevations( graph_orig, api_key=key ) # 'AIzaSyDVqjj0iKq0eNNHlmslH4fjoFgRj7n-5gs') # from elevation graph_orig = ox.add_edge_grades(graph_orig) pkl.dump(graph_orig, open("data/" + city_name + "_city_graph.pkl", "wb")) #project map on to 2D space graph_project = ox.project_graph(graph_orig) pkl.dump(graph_project, open("data/" + city_name + "_city_graph_projected.pkl", "wb")) #print ("pkl: ", type(graph_orig)) return graph_project, graph_orig
def _get_graph_not_memoized(latitude: float, longitude: float, radius: float, google_maps_api_key: str) -> igraph.Graph: # calculate extent of the graph radius_latlong = radius / 60 north_lat = latitude + radius_latlong south_lat = latitude - radius_latlong east_long = longitude + radius_latlong west_long = longitude - radius_latlong # download graph graph = osmnx.graph_from_bbox(north_lat, south_lat, east_long, west_long, network_type='walk') graph = osmnx.add_node_elevations(graph, api_key=google_maps_api_key) graph = osmnx.add_edge_grades(graph) # convert node labels graph = networkx.relabel.convert_node_labels_to_integers(graph) # populate igraph graph_ig = igraph.Graph(directed=True) graph_ig.add_vertices(list(graph.nodes())) graph_ig.add_edges(list(graph.edges())) for attr in ('osmid', 'x', 'y'): graph_ig.vs[attr] = list( networkx.get_node_attributes(graph, attr).values()) for attr in ('length', 'grade'): graph_ig.es[attr] = list( networkx.get_edge_attributes(graph, attr).values()) graph_ig.es['streetname'] = [ str(s) for s in networkx.get_edge_attributes(graph, 'name').values() ] return graph_ig
def render_graph(self): Graph = ox.graph_from_address(self.address, network_type='walk', distance=self.run_distance / 2) Graph = ox.add_node_elevations(Graph, api_key=self.api_key) Graph = ox.add_edge_grades(Graph) return Graph
def get_graph(): key = "<<put key here>>" place_query = ['Natick, Massachusetts, USA'] G = ox.graph_from_place(place_query, network_type='walk') G = ox.add_node_elevations(G, key) G = ox.add_edge_grades(G) # G_projected = ox.project_graph(G) ox.save_graphml(G, filename='natick.graphml') print("done")
def get_elevation(self,graph_orig, city_name, key): # get evelation data only key = self.get_key() graph_orig = ox.add_node_elevations(graph_orig, api_key=key) graph_orig = ox.add_edge_grades(graph_orig) pkl.dump(graph_orig, open("data/" + city_name+"_city_graph.pkl","wb")) #projecting map on to 2D space graph_project = ox.project_graph(graph_orig) pkl.dump(graph_project, open("data/" + city_name+"_city_graph_projected.pkl","wb")) return graph_project, graph_orig
def get_graph_with_elevation(self, G): """ Returns networkx graph G with eleveation data appended to each node and rise/fall grade to each edge. Params: bbox:tuple (n,s,e,w) Returns: G: networkx graph """ G = ox.add_node_elevations(G, api_key=self.GOOGLEAPIKEY) return G
def download_Map(self): #Download Map from OSMNX around the fixed center(can be changed in config.py) if cache map is not available self.logger.warning("Downloading the Map") self.G = ox.graph_from_point(config.MAP_CENTER, dist=20000, network_type='walk') self.G = ox.add_node_elevations(self.G, api_key=self.GOOGLEAPIKEY) pkl.dump(self.G, open(constants.CACHED_MAP_FILENAME, "wb")) self.cached = True self.logger.info("The Graph has been saved")
def add_elevation(self, G): ''' Add elevation attribute to the nodes of the input graph ''' G = ox.add_node_elevations(G, api_key=self.gmap_key) self.highest = 0 self.highest_node = None for node in G.nodes: if G.nodes[node]['elevation'] > self.highest : self.highest = G.nodes[node]['elevation'] self.highest_node = node self.graph = G.copy()
def get_map(place='Boston', key, new_place=False): if new_place == False: return pkl.load(open("graph_projected.pkl", "rb")), pkl.load(open("graph.pkl", "rb")) # Downloading Local map place_query = {'city': place, 'state': 'Massachusetts', 'country': 'USA'} graph_orig = ox.graph_from_place(place_query, network_type='drive') # Adding Elevation data from graph_origoogleMaps graph_orig = ox.add_node_elevations(graph_orig, api_key=key) graph_orig = ox.add_edge_grades(graph_orig) pkl.dump(graph_orig, open("graph.pkl", "wb")) # projecting map on to 2D space graph_projected = ox.project_graph(graph_orig) pkl.dump(graph_projected, open("graph_projected.pkl", "wb")) return graph_projected, graph_orig
def get_graph_with_elevation(self, bbox): """ Returns networkx graph G with eleveation data appended to each node and rise/fall grade to each edge. Params: bbox:tuple (n,s,e,w) Returns: G: networkx graph """ G = ox.graph_from_bbox(bbox[0], bbox[1], bbox[2], bbox[3], network_type='drive') G = ox.add_node_elevations(G, api_key=self.GOOGLEAPIKEY) return G
def get_map(place='San Francisco', newPlace=False): if newPlace == False: return pkl.load(open("graph_projected.pkl", "rb")), pkl.load(open("graph.pkl", "rb")) # Downloading Local map place_query = {'city': place, 'state': 'California', 'country': 'USA'} G = ox.graph_from_place(place_query, network_type='drive') # Adding Elevation data from GoogleMaps G = ox.add_node_elevations( G, api_key='AIzaSyDrIKxLj0P90vzCUJ_6huBSDxiq8wYo9LM') G = ox.add_edge_grades(G) pkl.dump(G, open("graph.pkl", "wb")) # projecting map on to 2D space G_proj = ox.project_graph(G) pkl.dump(G_proj, open("graph_projected.pkl", "wb")) return G_proj, G
def add_elevation_info(self, api_key=None): ''' update the elevation and grade information for nodes and edges Parameters: api_key (str): Google API key for the elevation information ''' if api_key is None: with open("api_key.txt", "r") as f: api_key = f.readline() self.G = ox.add_node_elevations(self.G, api_key=api_key) self.G = ox.add_edge_grades(self.G) for e in self.G.edges: # change grade from ratio to value self.G.edges[e]['grade_abs'] *= self.G.edges[e]['length'] # calculate inverse grade_abs for maximum elevation path self.G.edges[e]['inv_grade_abs'] = 1 / ( self.G.edges[e]['grade_abs'] + self._eps)
def get_map(city, state): file_name = "%s%s.pkl" % (city, state) projected_file_name = "%s%s_projected.pkl" % (city, state) projected_file_path = Path(projected_file_name) file_path = Path(file_name) if projected_file_path.is_file() and file_path.is_file(): graph = pkl.load(open(file_name, 'rb')) return pkl.load(open(file_name, "rb")), pkl.load(open(projected_file_name, "rb")) else: query = {'city': city, 'state': state, 'country': 'USA'} graph = ox.graph_from_place(query, network_type='bike') graph = ox.add_node_elevations(graph, "AIzaSyCPFbx7dhPoxlSRRN4okxhibuu0E_7DUWI") graph = ox.add_edge_grades(graph) pkl.dump(graph, open(file_name, "wb")) graph_proj = ox.project_graph(graph) pkl.dump(graph_proj, open(projected_file_name, "wb")) return graph, graph_proj
def get_map(self, place='Boston', new_place=False): # if new_place == False: # return pkl.load(open("graph_projected.pkl","rb")), pkl.load(open("graph.pkl","rb")) #downloading local map place = 'Boston' place_query = { 'city': 'Boston', 'state': 'Massachusetts', 'country': 'USA' } graph_orig = ox.graph_from_place(place_query, network_type='drive') #adding elevation data from GoogleMaps #Enter the API key here key = self.model.get_key() graph_orig = ox.add_node_elevations(graph_orig, api_key=key) graph_orig = ox.add_edge_grades(graph_orig) pkl.dump(graph_orig, open("graph.pkl", "wb")) #projecting map on to 2D space graph_projection = ox.project_graph(graph_orig) pkl.dump(graph_projection, open("graph_projected.pkl", "wb")) return graph_projection, graph_orig
def add_elevation(self, G): ''' Add elevation attribute to the nodes of the input graph ''' # Add elevation using osmnx module method G = ox.add_node_elevations(G, api_key=self._gmap_key)
def elevation_graph(self, G): # Returns networkx graph with eleveation data and rise or fall grade. G = ox.add_node_elevations(G, api_key=self.GOOGLEAPIKEY) return G
def enrich_network(network, clean_dead_ends=True, elevation_api_key=None, drop_keys=[ 'place_id', 'license', 'osm_type', 'osmid', ' lat', 'lon', 'display_name', 'country', 'country_code', 'state', 'state_district', 'county', 'city' ], email=None, postcode_delim=' '): """ Enrich a street network by adding further attributes to the edges in the network. These can then be used in clustering, compression, graph embeddings, shortest paths, etc. Depending on the size of the network, this method may incur a large number of requests and time to run. If anprx.settings['cache'] is set to True, as is by default, responses will be cached and subsequent calls to this method, for the same or intersecting networks, should be faster. Parameters ---------- network : nx.MultiDiGraph a street network clean_dead_ends : bool true if dead end nodes should be removed from the graph elevation_api_key : string Google API key necessary to access the Elevation API. If None, elevation. drop_keys: list keys to ignore from the nominatim response containing address details email : string Valid email address in case you are making a large number of requests. postcode_delim : string postcode delimiter used to split the main postcode into two parts: outer and inner. Use None to skip postcode splitting. Returns ------- network : nx.MultiDiGraph The same network, but with additional edge attributes """ start_time = time.time() log("Enriching network with {} nodes and {} edges. This may take a while.."\ .format(len(network), network.number_of_edges()), level = lg.INFO) if clean_dead_ends: remove_dead_end_nodes(network) # Add bearings network = ox.add_edge_bearings(network) # Elevation if elevation_api_key: start_time_local = time.time() # add elevation to each of the nodes,using the google elevation API network = ox.add_node_elevations(network, api_key=elevation_api_key) # then calculate edge grades network = ox.add_edge_grades(network) log("Added node elevations and edge grades in {:,.3f} seconds"\ .format(time.time() - start_time_local), level = lg.INFO) # lookup addresses network = add_address_details(network, drop_keys, email) # Split post code into outward and inward # assume that there is a space that can be used for string split for (u, v, k, postcode) in network.edges(keys=True, data='postcode'): if postcode: postcode_l = postcode.split(postcode_delim) if len(postcode_l) != 2: log("Could not split postcode {}".format(postcode), level=lg.WARNING) else: network[u][v][k]['out_postcode'] = postcode_l[0] network[u][v][k]['in_postcode'] = postcode_l[1] log("Enriched network in {:,.3f} seconds"\ .format(time.time() - start_time), level = lg.INFO) return network
google_elevation_api_key="AIzaSyCkCrJwj0gzVp1kGjo5lz6VKDyaFUEVbGk" # In[20]: place = 'Amherst' place_query = {'city':'Amherst', 'state':'Massachusetts', 'country':'USA'} G = ox.graph_from_place(place_query, network_type='drive') # In[21]: G = ox.add_node_elevations(G, api_key=google_elevation_api_key) G = ox.add_edge_grades(G) # In[22]: edge_grades = [data['grade_abs'] for u, v, k, data in ox.get_undirected(G).edges(keys=True, data=True)] # In[23]: avg_grade = np.mean(edge_grades) print('Average street grade in {} is {:.1f}%'.format(place, avg_grade*100))
def collect_data(center_point, dist, known_trail=None): filter_ = '["highway"]["area"!~"yes"]["highway"!~"service|motor|proposed|construction|abandoned|platform|raceway"]["foot"!~"no"]["service"!~"private"]' G = ox.graph_from_point(center_point=center_point, dist=dist, dist_type='bbox', custom_filter=filter_) G = G.to_undirected() # Add marked paths, cycleways, and user-specified trails edge_colors = [] trail_color = '#F1C232' #'#674EA7' for edge in G.edges(data=True): try: name = edge[2]['name'] if name in known_trail or (isinstance( name, list) and len(known_trail.intesection(name)) != 0): edge_colors.append(trail_color) continue except: pass highway = edge[2]['highway'] if highway == 'path' or (isinstance(highway, list) and 'path' in highway): edge_colors.append(trail_color) elif highway == 'cycleway' or (isinstance(highway, list) and 'cycleway' in highway): edge_colors.append(trail_color) else: edge_colors.append('#999999') # Get water features tags = {'natural': 'water'} pois_water = ox.pois_from_point(center_point, tags, dist=dist) pois_water_list = list(pois_water['name'][pd.notna(pois_water['name'])]) pois_water_list.sort() pois_water_list = [el for el in pois_water_list if 'Lake' in el] # Get park features tags = {'leisure': 'park'} park_pois = ox.pois_from_point(center_point, tags, dist=dist) # Add elevation data google_maps_api_key = os.environ.get( 'GOOGLEAPIKEY') # You'll need your own API key :) G = ox.add_node_elevations(G, api_key=google_maps_api_key) G = ox.add_edge_grades(G) grade_data = [] for u, v, data in G.edges(keys=False, data=True): u_lat = G.nodes[u]['y'] u_lng = G.nodes[u]['x'] u_el = G.nodes[u]['elevation'] v_lat = G.nodes[v]['y'] v_lng = G.nodes[v]['x'] v_el = G.nodes[v]['elevation'] elevation_change = G.nodes[v]["elevation"] - G.nodes[u]["elevation"] center_lat = 0.5 * (u_lat + v_lat) center_lng = 0.5 * (u_lng + v_lng) grade_datum = np.array([ data['length'], data['grade_abs'], data['length'] * data['grade_abs'] ]) grade_data.append(grade_datum) grade_data = np.vstack(grade_data) # Build edge data # https://stackoverflow.com/questions/639695/how-to-convert-latitude-or-longitude-to-meters import math def lat_lon_to_dist(lat1, lon1, lat2, lon2): # pt[0] = long, pt[1] = lat R = 6378.137 dLat = lat2 * math.pi / 180 - lat1 * math.pi / 180 dLon = lon2 * math.pi / 180 - lon1 * math.pi / 180 a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos( lat1 * math.pi / 180) * math.cos(lat2 * math.pi / 180) * math.sin( dLon / 2) * math.sin(dLon / 2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) d = R * c return d * 1000 # meters def get_min_dist(pt, pois_geom): pt_np = np.array(pt) min_ = float('inf') for obj in pois_geom: if isinstance(obj, Point) or isinstance(obj, tuple): obj_np = np.array(obj) dist = lat_lon_to_dist(pt_np[1], pt_np[0], obj_np[1], obj_np[0]) if dist < min_: min_ = dist min_poi_pt = obj_np if isinstance(obj, Polygon): if obj.contains(pt) == True: min_ = 0 return min_, None coords = list(obj.exterior.coords) dist, poi_pt = get_min_dist(pt, coords) if dist < min_: min_ = dist min_poi_pt = poi_pt if isinstance(obj, MultiPolygon): for poly in obj: if poly.contains(pt) == True: min_ = 0 return min_, None coords = list(poly.exterior.coords) dist, poi_pt = get_min_dist(pt, coords) if dist < min_: min_ = dist min_poi_pt = poi_pt return min_, min_poi_pt # Get edge data for parks print('Getting park edge data') nature_dist_edge = [] for edge in G.edges(data=True): edge_nodes = [edge[0], edge[1]] center = np.array([ sum([G.nodes[node_id]['x'] for node_id in edge_nodes]) / 2, sum([G.nodes[node_id]['y'] for node_id in edge_nodes]) / 2 ]) pt = Point(center[0], center[1]) dist, min_pt = get_min_dist(pt, park_pois['geometry']) nature_dist_edge.append((dist, np.array(pt), min_pt)) # Get edge data for water print('Getting water edge data') water_dist_edge = [] for edge in G.edges(data=True): edge_nodes = [edge[0], edge[1]] center = np.array([ sum([G.nodes[node_id]['x'] for node_id in edge_nodes]) / 2, sum([G.nodes[node_id]['y'] for node_id in edge_nodes]) / 2 ]) pt = Point(center[0], center[1]) dist, min_pt = get_min_dist(pt, pois_water['geometry']) water_dist_edge.append((dist, np.array(pt), min_pt)) # Rescale edge data based on statistical analysis def sigma(x): return 1. / (1 + np.exp(-(x - 200) / 30)) # - sigma(0) lengths = grade_data[:, 0] impedence = 10 * grade_data[:, 2] water_dist_rescaled = map(lambda x: sigma(x[0]), water_dist_edge) water_dist_impedence = [ x * y for x, y in zip(water_dist_rescaled, lengths) ] nature_dist_rescaled = map(lambda x: sigma(x[0]), nature_dist_edge) nature_dist_impedence = [ x * y for x, y in zip(nature_dist_rescaled, lengths) ] is_not_trail_penalty = [ length * float(color != trail_color) for color, length in zip(edge_colors, lengths) ] lengths = list(lengths) impedence = list(impedence) data = list( zip(lengths, impedence, water_dist_impedence, nature_dist_impedence, is_not_trail_penalty)) edge_nodes = [(u, v) for u, v, data in list(G.edges(keys=False, data=True))] all_data = [(a[0], a[1], b[0], b[1], b[2], b[3], b[4]) for a, b in zip(edge_nodes, data)] return G, all_data
def populate_graph(G, speeds = False): G = ox.add_edge_speeds(G) if speeds else G G = ox.add_edge_travel_times(G) G = ox.add_node_elevations(G, api_key= api_key) G = ox.add_edge_grades(G, None, None)
def get_map(self, start_lat, start_long, end_lat, end_long, chosen_weight): print( "Coordinates", start_lat, start_long, end_lat, end_long, ) print("weight", chosen_weight) place = 'Amherst' place_query = { 'city': 'Amherst', 'state': 'Massachusetts', 'country': 'USA' } G = ox.graph_from_place(place_query, network_type='drive') G = ox.add_node_elevations( G, api_key='AIzaSyB9DBYn2sdIznFbmBg4DHOTl54soDBkx2E') G = ox.add_edge_grades(G) edge_grades = [ data['grade_abs'] for u, v, k, data in ox.get_undirected(G).edges(keys=True, data=True) ] avg_grade = np.mean(edge_grades) #print('Average street grade in {} is {:.1f}%'.format(place, avg_grade*100)) if chosen_weight == 0: choice = 'length' elif chosen_weight == 1: choice = 'minimum' elif chosen_weight == 2: choice = 'impedence' med_grade = np.median(edge_grades) #print('Median street grade in {} is {:.1f}%'.format(place, med_grade*100)) # project the street network to UTM G_proj = ox.project_graph(G) # get one color for each node, by elevation, then plot the network #nc = ox.get_node_colors_by_attr(G_proj, 'elevation', cmap='plasma', num_bins=20) #fig, ax = ox.plot_graph(G_proj, fig_height=6, node_color=nc, node_size=12, node_zorder=2, edge_color='#dddddd') # get a color for each edge, by grade, then plot the network #ec = ox.get_edge_colors_by_attr(G_proj, 'grade_abs', cmap='plasma', num_bins=10) #fig, ax = ox.plot_graph(G_proj, fig_height=6, edge_color=ec, edge_linewidth=0.8, node_size=0) # select an origin and destination node and a bounding box around them origin = ox.get_nearest_node(G, (start_lat, start_long)) destination = ox.get_nearest_node(G, (end_lat, end_long)) bbox = ox.bbox_from_point( ((start_lat + end_lat) / 2, (start_long + end_long) / 2), distance=5000, project_utm=True) for u, v, k, data in G_proj.edges(keys=True, data=True): data['impedance'] = self.impedance(data['length'], data['grade_abs']) data['rise'] = data['length'] * data['grade'] #weight_choice = {'easy' : 'length', 'median' : 'minimum', 'hard' : 'impedance'} routef = nx.shortest_path(G_proj, source=origin, target=destination, weight=choice) route_map = ox.plot_route_folium(G, routef) p1 = [start_lat, start_long] p2 = [end_lat, end_long] folium.Marker(location=p1, icon=folium.Icon(color='green')).add_to(route_map) folium.Marker(location=p2, icon=folium.Icon(color='red')).add_to(route_map) print("------------------4321") result = self.print_route_stats(routef, G_proj) filepath = 'routeff.html' route_map.save(filepath) IFrame(filepath, width=600, height=500) return result
# from keys import AIzaSyCm0ZhqcDDR75EnTs4EfSxFIdFIdowigCs #replace this with your own API key! import networkx as nx import numpy as np import osmnx as ox from flask import Flask, render_template, redirect, url_for,request from flask import make_response,jsonify app = Flask(__name__) #https://github.com/gboeing/osmnx-examples/blob/master/notebooks/12-node-elevations-edge-grades.ipynb api_key = "AIzaSyCm0ZhqcDDR75EnTs4EfSxFIdFIdowigCs" place = 'Amherst' place_query = {'city':'Amherst', 'state':'Massachusetts', 'country':'USA'} G = ox.graph_from_place(place_query, network_type='drive') G = ox.add_node_elevations(G, api_key=api_key) G = ox.add_edge_grades(G) G = ox.add_edge_lengths(G) G_proj = ox.project_graph(G) @app.route('/') def webprint(): return render_template('map_test.html') @app.route('/route', methods=['GET', 'POST']) def route(): route = getRoute((float(request.form['start']), float(request.form['start2'])),(float(request.form['end']),float(request.form['end2'])),float(request.form['distance']),float(request.form['elevation'])) gdf = ox.graph_to_gdfs(G, edges=False) gdf = gdf[['osmid','x','y']] l = []
def populate_graph(self, G, speeds = False): G = ox.add_edge_speeds(G) if speeds else G G = ox.add_node_elevations(G, api_key= api_key) G = ox.add_edge_grades(G) return G
(trips['Start Longitude'].isnull()) | (trips['End Latitude'].isnull()) | (trips['End Longitude'].isnull())].index) # '''Delete the rows with GPS out of range''' # trips = trips.drop(trips[(trips['Start Latitude'] > 46) | (trips['Start Latitude'] < 36)].index) '''Save the modified data''' trips.to_csv(cityname + '_modified.csv') # In[4]: ''' Extract the biking road network from OpenStreetMap ''' graph = ox.graph_from_place(place_query, network_type='all') ox.plot_graph(graph) # add elevation to each of the nodes, using the google elevation API, then calculate edge grades graph = ox.add_node_elevations(graph, api_key=apikey) graph = ox.add_edge_grades(graph) ''' Convert the network into nodes geodataframe and edges geodataframe respectively ''' nodes, edges = ox.graph_to_gdfs(graph, nodes=True, edges=True) # In[5]: ''' Convert into UTM format ''' graph_proj = ox.project_graph(graph) # get one color for each node, by elevation, then plot the network nc = ox.get_node_colors_by_attr(graph_proj, 'elevation', cmap='viridis', num_bins=20) fig, ax = ox.plot_graph(graph_proj, node_color=nc, node_zorder=2) plt.tight_layout()