from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import geopandas as gpd
import altair as alt
%matplotlib inline
pd.options.display.max_columns = 999
plt.rcParams['figure.figsize'] = (10,6)
Oct 27, 2020
Great source of data: street networks and a wealth of amenity information
Related: interactive web maps in Python
Several key features:
import osmnx as ox
Make sure the version >= 0.16 for this notebook to work!
print(ox.__version__)
philly = ox.geocode_to_gdf('Philadelphia, PA, USA')
philly.head()
We can plot it just like any other GeoDataFrame
# Project it to Web Mercator first and plot
ax = philly.to_crs(epsg=3857).plot(facecolor='none', edgecolor='black')
ax.set_axis_off()
Key function: project_gdf()
(docs)
Automatically projects to the Universal Transverse Mercator (UTM) CRS for the UTM zone that the centroid of the polygon lies in
A good, general projection that works for most latitudes except very northern locations.
ax = ox.project_gdf(philly).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
Some more examples:
# Some examples
place1 = ox.geocode_to_gdf('Manhattan, New York City, New York, USA')
place2 = ox.geocode_to_gdf('Miami-Dade County, Florida')
place3 = ox.geocode_to_gdf('Florida, USA')
place4 = ox.geocode_to_gdf('Spain')
# Manhattan
ax = ox.project_gdf(place1).plot(fc="lightblue", ec="gray");
ax.set_axis_off()
# Miami-Dade County
ax = ox.project_gdf(place2).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
# Florida
ax = ox.project_gdf(place3).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
# Spain
ax = ox.project_gdf(place4).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
Key functions: geometries_from_*
geometries_from_place()
(docs)geometries_from_address()
(docs)geometries_from_bbox()
(docs)geometries_from_point()
(docs) geometries_from_polygon()
(docs)Important reference: https://wiki.openstreetmap.org/wiki/Map_Features
In the language of OSM, the "key" is the main feature category (e.g., amenity) and the "value" is the sub-category type (e.g., "bar")
Use a dict to specify the features you want:
# Get all amenities in Philadelphia
amenities = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": True})
len(amenities)
amenities.head()
# Get all bars in philadelphia
bars = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": "bar"})
len(bars)
bars.head()
# Get bar, pub, and restaurant features in Philadelphia
food_and_drink = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": ["pub", "bar", "restaurant"]})
len(food_and_drink)
food_and_drink.head()
# Get higway bus stop features
bus_stops = ox.geometries_from_place("Philadelphia, PA", tags={"highway": "bus_stop"})
len(bus_stops)
bus_stops.head()
# Get commercial and retail landuse features
landuse = ox.geometries_from_place("Philadelphia, PA", tags={"landuse": ["commercial", "retail"]})
len(landuse)
landuse.head()
fig, ax = plt.subplots(figsize=(10, 6))
ax = landuse.plot(ax=ax)
ax.set_axis_off()
Key functions: graph_from_*
graph_from_place()
(docs)graph_from_address()
(docs)graph_from_bbox()
(docs)graph_from_point()
(docs) graph_from_polygon()
(docs)Get streets within 500 meters of the center of Northern Liberties
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=500)
Project and plot it:
G_projected = ox.project_graph(G)
ox.plot_graph(G_projected);
Remove the nodes:
ox.plot_graph(G_projected, node_size=0);
Let's zoom out to 2,000 meters. This will take a little longer.
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=2000)
G_projected = ox.project_graph(G)
ox.plot_graph(G_projected, node_size=0);
drive
- get drivable public streets (but not service roads)drive_service
- get drivable streets, including service roadswalk
- get all streets and paths that pedestrians can use (this network type ignores one-way directionality)bike
- get all streets and paths that cyclists can useall
- download all non-private OSM streets and pathsall_private
- download all OSM streets and paths, including private-access ones (default)# the "drive" network
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA",
network_type='drive')
ox.plot_graph(G);
# the "walk" network
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA",
network_type='walk')
ox.plot_graph(ox.project_graph(G));
Use graph_from_place()
to get the streets within a specific OSM place.
Note: the place query has to be resolved by OSM.
berkeley = ox.graph_from_place("Berkeley, California", network_type='drive')
ox.plot_graph(ox.project_graph(berkeley), node_size=0);
Example: all streets within Northern Liberties and Fishtown
url = "https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson"
hoods = gpd.read_file(url).rename(columns={"mapname": "neighborhood"})
hoods.head()
Trim to Fishtown and Northern Liberties
sel = hoods['neighborhood'].isin(['Fishtown - Lower Kensington', 'Northern Liberties'])
nolibs_fishtown = hoods.loc[sel]
nolibs_fishtown.head()
ax = ox.project_gdf(nolibs_fishtown).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
unary_union
ox.graph_from_polygon()
nolibs_fishtown_outline = nolibs_fishtown.geometry.unary_union
nolibs_fishtown_outline
type(nolibs_fishtown_outline)
# get the graph
G_nolibs_fishtown = ox.graph_from_polygon(nolibs_fishtown_outline, network_type='drive')
# viola!
ox.plot_graph(ox.project_graph(G_nolibs_fishtown), node_size=0);
We could also use unary_union.convex_hull
. This will be an encompassing polygon around any set of geometries.
nolibs_fishtown.geometry.unary_union
nolibs_fishtown.geometry.unary_union.convex_hull
# only get the edges
nolibs_edges = ox.graph_to_gdfs(G_nolibs_fishtown,
edges=True,
nodes=False)
# we have lots of data associated with each edge!
nolibs_edges.head()
# plot it like any old GeoDataFrame
ax = nolibs_edges.to_crs(epsg=3857).plot(color='gray')
# add the neighborhood boundaries
boundary = gpd.GeoSeries([nolibs_fishtown_outline], crs='EPSG:4326')
boundary.to_crs(epsg=3857).plot(ax=ax, facecolor='none', edgecolor='red', linewidth=3, zorder=2)
ax.set_axis_off()
And much more: see the OSMnx repository of Jupyter notebook examples
ox.basic_stats(G_nolibs_fishtown)
sorted(ox.extended_stats(G_nolibs_fishtown).keys())
import networkx as nx
Let's calculate the shortest route between Frankford Hall (a bar in Fishtown) and the Spring Garden subway station:
# Get all food and drink places within our Fishtown/No Libs polygon
fishtown_food_drink = ox.geometries_from_polygon(nolibs_fishtown_outline,
tags={"amenity": ["restaurant", "bar", "pub"]})
fishtown_food_drink.head()
# Select Frankford Hall based on the name column
frankford_hall = fishtown_food_drink.query("name == 'Frankford Hall'").squeeze()
# Get the x/y coordinates
# NOTE: Ordering is (lat, lng)
frankford_hall_coords = (frankford_hall.geometry.y, frankford_hall.geometry.x)
# Get all subway stations within our Fishtown/No Libs polygon
fishtown_subway_stops = ox.geometries_from_polygon(nolibs_fishtown_outline,
tags={"railway": "station"})
# Select Spring Garden based on the name column
spring_garden_station = fishtown_subway_stops.query("name == 'Spring Garden'").squeeze()
# Get the x/y coordinates
# NOTE: Ordering is (lat, lng)
spring_garden_coords = (spring_garden_station.geometry.y, spring_garden_station.geometry.x)
Find the nearest nodes on our OSMnx graph:
# Get the origin node
orig_node = ox.get_nearest_node(G_nolibs_fishtown, spring_garden_coords)
# Get the destination node
dest_node = ox.get_nearest_node(G_nolibs_fishtown, frankford_hall_coords)
get_nearest_node()
needs a point in (lat, lng) or (y, x) order!!
Use networkx
to find the shortest path between these graph nodes
# get the shortest path --> just a list of node IDs
route = nx.shortest_path(G_nolibs_fishtown,
orig_node,
dest_node,
weight='length')
route
Use ox.plot_graph_route()
to plot a graph and highlight a specific route
ox.plot_graph_route(G_nolibs_fishtown, route, node_size=0);
"Pandas Network Analysis - dataframes of network queries, quickly"
A complementary set of OSM-related features:
import pandana as pnda
from pandana.loaders import osm
Key function: osm.node_query()
ox.geometries_from_bbox()
function in OSMnx, but we slightly different syntax.osm.node_query?
Get the bounding box for Northern Liberties / Fishtown:
boundary = nolibs_fishtown_outline.bounds
boundary
[lng_min, lat_min, lng_max, lat_max] = boundary
# query OSM
poi_df = osm.node_query(lat_min, lng_min, lat_max, lng_max)
# remove missing data
poi_df = poi_df.dropna(subset=['amenity'])
poi_df.head()
len(poi_df)
poi_df[['lat', 'lon', 'amenity']].head(10)
For the full list of amenities, see the OSM Wikipedia
chart = (
alt.Chart(poi_df)
.mark_bar()
.encode(y=alt.Y("amenity", sort="-x"), x="count()", tooltip=["amenity", "count()"])
)
chart
pdna_network_from_bbox()
net = osm.pdna_network_from_bbox(
lat_min, lng_min, lat_max, lng_max, network_type="walk"
)
Key function: network.set_pois()
# sensible defaults
max_distance = 2000 # in meters
num_pois = 10 # only need the 10 nearest POI to each point in the network
AMENITIES = ["restaurant", "bar", "school", "car_sharing"]
for amenity in AMENITIES:
# get the subset of amenities for this type
pois_subset = poi_df[poi_df["amenity"] == amenity]
# set the POI, using the longitude and latitude of POI
net.set_pois(
amenity, max_distance, num_pois, pois_subset["lon"], pois_subset["lat"]
)
# keyword arguments to pass for the matplotlib figure
bbox_aspect_ratio = (lat_max - lat_min) / (lng_max - lng_min)
fig_kwargs = {"facecolor": "w", "figsize": (10, 10 * bbox_aspect_ratio)}
# keyword arguments to pass for scatter plots
plot_kwargs = {"s": 20, "alpha": 0.9, "cmap": "viridis_r", "edgecolor": "none"}
For every point on the network, find the nth nearest POI, calculate the distance, and color that point according to the distance.
network.nearest_poi()
to get distances from nodes to nearest POIsnetwork.nearest_poi()
to get distances from nodes to nearest POIs¶num_pois
amenity = 'bar'
access = net.nearest_pois(distance=1000,
category=amenity,
num_pois=num_pois)
access.head(n=20)
net.nodes_df.head()
access.head()
# Merge the nodes and the distance to POIs
nodes = pd.merge(net.nodes_df, access, left_index=True, right_index=True)
# Make into a geodataframe
nodes = gpd.GeoDataFrame(
nodes, geometry=gpd.points_from_xy(nodes["x"], nodes["y"]), crs="EPSG:4326"
)
nodes.head()
Let's define a function to do this for us, since we'll repeat this plot multiple times
def plot_walking_distance(net, amenity, distance=1000, n=1):
"""
Plot the walking distance to the specified amenity
"""
from mpl_toolkits.axes_grid1 import make_axes_locatable
# subset of POI
poi_subset = poi_df[poi_df["amenity"] == amenity]
# get the distances to nearest num_pois POI
access = net.nearest_pois(distance=1000, category=amenity, num_pois=num_pois)
# merge node positions and distances to nearest PO
nodes = pd.merge(net.nodes_df, access, left_index=True, right_index=True)
nodes = gpd.GeoDataFrame(
nodes, geometry=gpd.points_from_xy(nodes["x"], nodes["y"]), crs="EPSG:4326"
)
# Create the figure
fig, ax = plt.subplots(figsize=(10, 10))
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)
# plot the distance to the nth nearest amenity
ax = nodes.plot(ax=ax, cax=cax, column=nodes[n], legend=True, **plot_kwargs)
# add the amenities as stars
for i, row in poi_subset.iterrows():
ax.scatter(row["lon"], row["lat"], color="red", s=100, marker="*")
# format
ax.set_facecolor("black")
ax.figure.set_size_inches(fig_kwargs["figsize"])
# set extent
[xmin, ymin, xmax, ymax] = nodes.geometry.total_bounds
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
return ax
The difference between maps to the nearest amenity and for example, the 5th nearest amenity tells us about the options consumers have
ax = plot_walking_distance(net, "bar", n=1)
ax.set_title("Walking distance to the nearest bar", fontsize=18);
ax = plot_walking_distance(net, "bar", n=3)
ax.set_title("Walking distance to the 3rd nearest bar", fontsize=18);
ax = plot_walking_distance(net, "school", n=1)
ax.set_title("Walking distance to the nearest school", fontsize=18);
ax = plot_walking_distance(net, "school", n=3)
ax.set_title("Walking distance to the 3rd nearest school", fontsize=18);
ax = plot_walking_distance(net, "restaurant", n=1)
ax.set_title("Walking distance to the nearest restaurant", fontsize=18);
ax = plot_walking_distance(net, "restaurant", n=5)
ax.set_title("Walking distance to the 5th nearest restaurant", fontsize=18);
ax = plot_walking_distance(net, "car_sharing", n=1)
ax.set_title("Walking distance to the nearest car sharing", fontsize=18);
ax = plot_walking_distance(net, "car_sharing", n=5)
ax.set_title("Walking distance to the 5th nearest car sharing", fontsize=18);
Many, many more amenities are logged throughout the city. Pick your favorite neighborhood and explore.
See this page for the full list of amenities.
Final project idea: With this kind of analysis, you can look at amenity-based influence in housing, neighborhood selection, etc. or something similar to the Walk Score.