In [29]:
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import geopandas as gpd
import altair as alt
In [30]:
pd.options.display.max_columns = 999
In [31]:
plt.rcParams['figure.figsize'] = (10,6)

Week 9A: OpenStreetMap, Urban Networks, and Interactive Web Maps

Nov 1, 2021

Happy day after Halloween

Housekeeping

Important: Update your local environment

  • Small update to course's Python environment relevant to today's lecture
  • Update the environment on your laptop using these instructions on course website

Today: OpenStreetMap (OSM)

  • Two tools that make working with OSM data very easy
  • What kind of questions can we answer?
    • Street orientations
    • Mapping event points to streets: car crashes
    • Mapping amenities
    • Network-constrained distances: accessibility

OSM: what is it?

  • Collaborative mapping
  • A free editable map of the World
  • Sort of like Wikipedia for maps

Great source of data: street networks and a wealth of amenity information

Working with OSM data

  • Raw data is very messy
  • Two relatively new, amazing Python packages greatly simply the process
  • Related, but complementary features
    • OSMnx: downloading and manipulating streets as networks
    • Pandana: networks focused on accessibility of amenities

Related: interactive web maps in Python

Part 1: OSMnx

Several key features:

  • Downloading political boundaries for cities, states, countries, etc
  • Downloading street networks
  • Analyzing networks: routing, visualization, statistics
In [32]:
import osmnx as ox

1.1. Getting boundary shapefiles from OSM

Key function: geocode_to_gdf() (docs)

We can get the boundary for anything identified as a "place" by OSM.

Important: Be careful to pass the right place name that OSM needs

In [33]:
philly = ox.geocode_to_gdf('Philadelphia, PA, USA')
philly.head() 
Out[33]:
geometry bbox_north bbox_south bbox_east bbox_west place_id osm_type osm_id lat lon display_name class type importance
0 POLYGON ((-75.28030 39.97500, -75.28022 39.974... 40.137959 39.867005 -74.955831 -75.280298 282365563 relation 188022 39.952724 -75.163526 Philadelphia, Philadelphia County, Pennsylvani... boundary administrative 0.823797

We can plot it just like any other GeoDataFrame

In [34]:
# Project it to Web Mercator first and plot
ax = philly.to_crs(epsg=3857).plot(facecolor='none', edgecolor='black') 
ax.set_axis_off()

1.2 Projecting with OSMnx

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.

In [35]:
ax = ox.project_gdf(philly).plot(fc="lightblue", ec="gray")
ax.set_axis_off()

Some more examples:

In [36]:
# 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')
In [37]:
# Manhattan
ax = ox.project_gdf(place1).plot(fc="lightblue", ec="gray");
ax.set_axis_off()
In [38]:
# Miami-Dade County
ax = ox.project_gdf(place2).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
In [39]:
# Florida
ax = ox.project_gdf(place3).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
In [40]:
# Spain
ax = ox.project_gdf(place4).plot(fc="lightblue", ec="gray")
ax.set_axis_off()

1.3 Downloading OSM features

Key functions: geometries_from_*

  • geometries_from_place() (docs)
    • Download geometries within an OSM place boundary
  • geometries_from_address() (docs)
    • Download geometries within a certain distance of an address
  • geometries_from_bbox() (docs)
    • Download geometries within a N, S, E, W bounding box
  • geometries_from_point() (docs)
    • Download geometries within a certain distance of a specified point
  • geometries_from_polygon() (docs)
    • Download geometries within a polygon object

About OSM features

Important reference: https://wiki.openstreetmap.org/wiki/Map_Features

  • OSM uses a tagging system to categorize different map features
  • The main feature categories are available on the OSM Wikipedia
    • Examples: 'amenity', 'building', 'landuse', 'highway'
  • There are specific sub-categories for each feature type too:
    • Amenity examples: 'bar', 'college', 'library'

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")

OSMnx mirrors the key/value syntax

Use a dict to specify the features you want:

In [41]:
# Get all amenities in Philadelphia
amenities = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": True})

len(amenities)
Out[41]:
9157
In [42]:
amenities.head()
Out[42]:
highway geometry amenity created_by cuisine name shop addr:city addr:postcode wheelchair addr:housenumber addr:state addr:street brand brand:wikidata brand:wikipedia official_name opening_hours phone takeaway source healthcare short_name internet_access url contact:phone description email website wikidata internet_access:ssid brewery alt_name ele gnis:county_id gnis:created gnis:feature_id gnis:state_id religion historic:amenity old_name operator denomination building comment operator:type school wikipedia gnis:edited craft microbrewery restaurant name:en fixme social_facility operator:wikidata operator:wikipedia emergency healthcare:speciality name:bg bicycle_parking capacity atm attraction gnis:county_name gnis:import_uuid gnis:reviewed addr:county addr:country outdoor_seating air_conditioning internet_access:fee diet:vegetarian toilets:wheelchair addr:housename bottle parking designation dispensing drive_through barrier screen delivery diet:vegan payment:cash payment:coins male female check_date fee fax collection_times smoking bar bus public_transport tourism source:pkey access name:zh addr:full twitter ref fuel:biodiesel fuel:biogas fuel:cng fuel:diesel fuel:e10 fuel:e85 fuel:electricity covered wifi drive_in food payment:bitcoin diet:halal self_service note leisure sport drink:club-mate car_wash toilets:disposal unisex entrance diet:pescetarian lgbtq payment:credit_cards payment:debit_cards reservation park_ride supervised landuse check_date:opening_hours flickr contact:facebook contact:email facebook instagram service_times youtube source:cuisine social_facility:for diet:kosher maxheight wheelchair:description wheelchair:description:en contact:website layer service:bicycle:chain_tool service:bicycle:pump network amenity_1 recycling:computers recycling:tv_monitor recycling_type ferry kitchen_hours branch animal was:amenity was:cuisine was:delivery was:drive_through was:name was:outdoor_seating was:takeaway direction backrest service:bicycle:repair payment:cards vending name:es theatre:genre addr:unit service:bicycle:tools beds level toilets currency:USD payment:visa cash_in currency:BCH currency:LTC currency:XBT opening_hours:kitchen office date display faces studio name:ca bench lit shelter_type addr:city:ar name:ar start_date colour material music_venue addr:block_number seats indoor historic type payment:american_express payment:discover_card payment:mastercard recycling:cans recycling:glass_bottles recycling:paper surface internet_access:description name:vi payment:cheque min_age tram contact:instagram opening_hours:covid19 waste payment:cash_app payment:venmo seating street_vendor theatre:type smoothness nodes building:levels capacity:disabled contact:twitter ref:nrhp height vehicle natural water building:use ship:type roof:shape automated building:material name:he source:name roof:levels roof:material access:conditional rite heritage heritage:operator architect:wikidata building:levels:underground heritage:website name:etymology:wikidata nrhp:criteria nrhp:inscription_date wikimedia_commons urgent_care building:colour area image gnis:fcode nonsquare name:ja name:zn roof:orientation building:part elevation historic:name grades location maxstay monastery:type fuel:octane_95 library owner loc_name drink bin police school:type old_name1 old_name2 ways building:height
element_type osmid
node 109811385 NaN POINT (-75.19487 40.05846) bench NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
109993125 traffic_signals POINT (-75.19107 40.06019) pub NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274215467 NaN POINT (-75.19492 39.95935) fast_food Potlatch 0.9c pizza Powelton Pizza NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274216093 NaN POINT (-75.19126 39.95765) atm NaN NaN Citi Bank convenience NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274216582 NaN POINT (-75.20081 39.95476) pub Potlatch 0.9c NaN The Blarney Stone NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [43]:
# Get all bars in philadelphia
bars = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": "bar"})

len(bars)
Out[43]:
151
In [44]:
bars.head()
Out[44]:
addr:city addr:housenumber addr:postcode addr:state addr:street amenity brewery craft gnis:county_id microbrewery name operator restaurant website wikidata wikipedia geometry opening_hours phone smoking toilets:wheelchair wheelchair addr:housename designation source wifi leisure sport cuisine diet:vegan diet:vegetarian food outdoor_seating internet_access wheelchair:description contact:facebook fixme description name:en name:es addr:unit check_date:opening_hours music_venue payment:cash min_age alt_name nodes atm building building:levels nonsquare
element_type osmid
node 357303425 Philadelphia 500 19123 PA Spring Garden Street bar yes brewery 101 yes Yards Brewing Company Yards Brewing Company yes https://yardsbrewing.com/ Q16903914 en:Yards Brewing Company POINT (-75.14712 39.96067) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
655425272 NaN NaN NaN NaN NaN bar NaN NaN NaN NaN Drinkers West NaN NaN NaN NaN NaN POINT (-75.20020 39.95521) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1167079387 NaN NaN NaN NaN NaN bar NaN NaN NaN NaN Milkboy NaN NaN NaN NaN NaN POINT (-75.14925 39.94170) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1204761128 NaN NaN NaN NaN NaN bar NaN NaN NaN NaN Vikings High School Club NaN NaN NaN NaN NaN POINT (-75.16363 39.92685) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1424271155 NaN 344 19147 NaN South Street bar NaN NaN NaN NaN Copabanana NaN NaN http://copabanana.com/ NaN NaN POINT (-75.14909 39.94152) Mo-Su 11:30-02:00 +1 215 9236180 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [45]:
# 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)
Out[45]:
982
In [46]:
food_and_drink.head()
Out[46]:
amenity highway geometry created_by name addr:city addr:postcode wheelchair source addr:housenumber addr:state addr:street brewery opening_hours craft gnis:county_id microbrewery operator restaurant website wikidata wikipedia cuisine outdoor_seating phone diet:vegetarian description addr:housename designation diet:vegan email fax smoking bar takeaway toilets:wheelchair addr:country alt_name short_name name:zh brand brand:wikidata brand:wikipedia official_name twitter wifi drive_in internet_access food payment:cash delivery payment:bitcoin diet:halal air_conditioning capacity note leisure sport contact:phone diet:pescetarian lgbtq payment:credit_cards payment:debit_cards reservation source:cuisine diet:kosher wheelchair:description facebook amenity_1 contact:facebook kitchen_hours fixme name:en name:es addr:unit check_date:opening_hours level opening_hours:kitchen name:ca internet_access:fee toilets start_date music_venue payment:american_express payment:discover_card payment:mastercard payment:visa internet_access:description internet_access:ssid min_age opening_hours:covid19 nodes building historic ship:type tourism atm building:levels area image building:material roof:levels name:ja name:zn nonsquare
element_type osmid
node 109993125 pub traffic_signals POINT (-75.19107 40.06019) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274216582 pub NaN POINT (-75.20081 39.95476) Potlatch 0.9c The Blarney Stone NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274216619 pub NaN POINT (-75.19994 39.95446) NaN Cavanaugh's Philadelphia 19104 limited NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
274216804 pub NaN POINT (-75.19844 39.95571) Potlatch 0.9c Brownie's 38th St. NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
333786044 restaurant NaN POINT (-75.15893 39.94086) Potlatch 0.10f Sam's Morning Glory NaN NaN limited knowledge NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [47]:
# Get higway bus stop features
bus_stops = ox.geometries_from_place("Philadelphia, PA", tags={"highway": "bus_stop"})

len(bus_stops)
Out[47]:
249
In [48]:
bus_stops.head()
Out[48]:
bus highway public_transport geometry bench covered name network operator shelter ref tactile_paving wheelchair route_ref local_ref designation source departures_board bin lit internet_access addr:street route_ref_1 note operator:wikidata brand brand:wikidata railway tram trolleybus
element_type osmid
node 109812837 yes bus_stop platform POINT (-75.19363 40.05964) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
361011456 yes bus_stop platform POINT (-75.16166 39.95223) yes yes 13th St & Market St SEPTA SEPTA yes NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
750281693 yes bus_stop platform POINT (-75.07732 40.01797) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
768271130 yes bus_stop platform POINT (-75.20726 40.01487) NaN NaN Wissahickon Transportation Center NaN SEPTA yes NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1015342921 yes bus_stop platform POINT (-75.18187 39.96640) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [49]:
# Get commercial and retail landuse features
landuse = ox.geometries_from_place("Philadelphia, PA", tags={"landuse": ["commercial", "retail"]})

len(landuse)
Out[49]:
172
In [50]:
fig, ax = plt.subplots(figsize=(10, 6))

ax = landuse.plot(ax=ax)
ax.set_axis_off()

1.4 Downloading street networks

Key functions: graph_from_*

  • graph_from_place() (docs)
    • Download street network within an OSM place boundary
  • graph_from_address() (docs)
    • Download street network within a certain distance of an address
  • graph_from_bbox() (docs)
    • Download street network within a N, S, E, W bounding box
  • graph_from_point() (docs)
    • Download street network within a certain distance of a specified point
  • graph_from_polygon() (docs)
    • Download street network within a polygon object

Street network around an address

Get streets within 500 meters of the center of Northern Liberties

In [51]:
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=500)

Project and plot it:

In [52]:
G_projected = ox.project_graph(G)
ox.plot_graph(G_projected);

Remove the nodes:

In [53]:
ox.plot_graph(G_projected, node_size=0);

Let's zoom out to 2,000 meters. This will take a little longer.

In [54]:
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=2000)
G_projected = ox.project_graph(G)
In [55]:
ox.plot_graph(G_projected, node_size=0);

Getting different network types

  • drive - get drivable public streets (but not service roads)
  • drive_service - get drivable streets, including service roads
  • walk - 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 use
  • all - download all non-private OSM streets and paths
  • all_private - download all OSM streets and paths, including private-access ones (default)
In [56]:
# the "drive" network
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", 
                          network_type='drive')
ox.plot_graph(G);
In [57]:
# the "walk" network
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", 
                           network_type='walk')
ox.plot_graph(ox.project_graph(G));

Street network within a place boundary

Use graph_from_place() to get the streets within a specific OSM place.

Note: the place query has to be resolved by OSM.

In [58]:
berkeley = ox.graph_from_place("Berkeley, California, USA",)
In [59]:
ox.plot_graph(ox.project_graph(berkeley), node_size=0);

Streets within a specific polygon

Example: all streets within Northern Liberties and Fishtown

First, let's wrangle some Zillow neighborhood boundaries

In [60]:
url = "https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson"
hoods = gpd.read_file(url).rename(columns={"mapname": "neighborhood"})
In [61]:
hoods.head()
Out[61]:
name listname neighborhood shape_leng shape_area cartodb_id created_at updated_at geometry
0 PENNYPACK_PARK Pennypack Park Pennypack Park 87084.285589 6.014076e+07 9 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.05645 40.08743, -75.05667 ...
1 OVERBROOK Overbrook Overbrook 57004.924607 7.692499e+07 138 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.22719 39.97740, -75.22984 ...
2 GERMANTOWN_SOUTHWEST Germantown, Southwest Southwest Germantown 14880.743608 1.441867e+07 59 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.16208 40.02829, -75.16145 ...
3 EAST_PARKSIDE East Parkside East Parkside 10885.781535 4.231000e+06 129 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.19931 39.97462, -75.19869 ...
4 GERMANY_HILL Germany Hill Germany Hill 13041.939087 6.949968e+06 49 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.22722 40.03523, -75.22865 ...

Trim to Fishtown and Northern Liberties

In [62]:
sel = hoods['neighborhood'].isin(['Fishtown - Lower Kensington', 'Northern Liberties'])
nolibs_fishtown = hoods.loc[sel]
In [63]:
nolibs_fishtown.head()
Out[63]:
name listname neighborhood shape_leng shape_area cartodb_id created_at updated_at geometry
105 FISHTOWN Fishtown - Lower Kensington Fishtown - Lower Kensington 25973.395800 3.360583e+07 87 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.12718 39.96086, -75.13048 ...
106 NORTHERN_LIBERTIES Northern Liberties Northern Liberties 20685.015399 2.031376e+07 89 2013-03-19T17:41:50.507999+00:00 2013-03-19T17:41:50.743000+00:00 MULTIPOLYGON (((-75.12718 39.96086, -75.12959 ...
In [64]:
ax = ox.project_gdf(nolibs_fishtown).plot(fc="lightblue", ec="gray")
ax.set_axis_off()

Extract streets within these polygons

  • Take the union of the polygons: unary_union
  • Use ox.graph_from_polygon()
In [65]:
nolibs_fishtown_outline = nolibs_fishtown.geometry.unary_union
nolibs_fishtown_outline
Out[65]:
In [66]:
type(nolibs_fishtown_outline)
Out[66]:
shapely.geometry.polygon.Polygon
In [67]:
# get the graph
G_nolibs_fishtown = ox.graph_from_polygon(nolibs_fishtown_outline, network_type='drive')
In [68]:
# 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.

In [69]:
nolibs_fishtown.geometry.unary_union
Out[69]:
In [70]:
nolibs_fishtown.geometry.unary_union.convex_hull
Out[70]:

1.5 Converting from a graph to a GeoDataFrame

Key function: ox.graph_to_gdfs() (docs)

You can get a GeoDataFrame for both the nodes (points) and edges (lines)

In [71]:
# only get the edges
nolibs_edges = ox.graph_to_gdfs(G_nolibs_fishtown, 
                                edges=True, 
                                nodes=False)
In [72]:
# we have lots of data associated with each edge!
nolibs_edges.head()
Out[72]:
osmid oneway name highway length geometry lanes maxspeed bridge ref
u v key
109729430 109729453 0 12109175 True North 5th Street residential 117.685 LINESTRING (-75.14637 39.96458, -75.14655 39.9... NaN NaN NaN NaN
1 789845372 True North 5th Street tertiary 97.779 LINESTRING (-75.14637 39.96458, -75.14627 39.9... NaN NaN NaN NaN
109729453 109729801 0 789845372 True North 5th Street tertiary 32.185 LINESTRING (-75.14617 39.96544, -75.14609 39.9... NaN NaN NaN NaN
109729699 109811674 0 [424804073, 121643778] True Callowhill Street trunk 135.768 LINESTRING (-75.14724 39.95779, -75.14739 39.9... 5 35 mph NaN NaN
109729709 0 [633770802, 41959235] True North 5th Street secondary 90.451 LINESTRING (-75.14724 39.95779, -75.14722 39.9... NaN NaN NaN NaN
In [73]:
# 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()

1.6 What can we do with the graph?

  • Network-based statistics
  • Routing
  • Street orientations
  • Visualizing crashes

And much more: see the OSMnx repository of Jupyter notebook examples

Network statistics

We can use the ox.basic_stats() to get some basic network statistics

In [74]:
ox.basic_stats(G_nolibs_fishtown)
Out[74]:
{'n': 629,
 'm': 1229,
 'k_avg': 3.9077901430842608,
 'edge_length_total': 103561.27499999995,
 'edge_length_avg': 84.26466639544341,
 'streets_per_node_avg': 3.379968203497615,
 'streets_per_node_counts': {0: 0, 1: 18, 2: 2, 3: 342, 4: 259, 5: 6, 6: 2},
 'streets_per_node_proportions': {0: 0.0,
  1: 0.028616852146263912,
  2: 0.003179650238473768,
  3: 0.5437201907790143,
  4: 0.4117647058823529,
  5: 0.009538950715421303,
  6: 0.003179650238473768},
 'intersection_count': 611,
 'street_length_total': 86326.12299999999,
 'street_segment_count': 1009,
 'street_length_avg': 85.55611793855302,
 'circuity_avg': 1.0165046392413242,
 'self_loop_proportion': 0.0}

Finding the shortest route

We can use the networkx package to do network-based calculations.

In [75]:
import networkx as nx

Let's calculate the shortest route between Frankford Hall (a bar in Fishtown) and the Spring Garden subway station:

In [76]:
# 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"]})
In [77]:
fishtown_food_drink.head()
Out[77]:
addr:city addr:housenumber addr:postcode addr:state addr:street amenity brewery craft gnis:county_id microbrewery name operator restaurant website wikidata wikipedia geometry opening_hours phone addr:housename designation cuisine brand brand:wikidata brand:wikipedia official_name drive_in outdoor_seating smoking takeaway wheelchair payment:bitcoin leisure sport addr:country source capacity addr:unit description diet:vegan min_age nodes building kitchen_hours
element_type osmid
node 357303425 Philadelphia 500 19123 PA Spring Garden Street bar yes brewery 101 yes Yards Brewing Company Yards Brewing Company yes https://yardsbrewing.com/ Q16903914 en:Yards Brewing Company POINT (-75.14712 39.96067) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1475230378 Philadelphia 1001 19123 NaN North 2nd Street bar NaN NaN NaN NaN Gunner's Run NaN NaN http://gunnersrun.com/ NaN NaN POINT (-75.14002 39.96627) Mo-Su 11:00-02:00 215-923-4600 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1712028161 Philadelphia 901 19123 NaN North 2nd Street restaurant NaN NaN NaN NaN Standard Tap NaN NaN standardtap.com NaN NaN POINT (-75.14057 39.96416) NaN 215 -238-0630 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1712044411 NaN NaN NaN NaN thompson and susquahana pub NaN NaN NaN NaN Les n Doreen's Happy Tap NaN NaN NaN NaN NaN POINT (-75.12558 39.97373) NaN NaN the happy tap local dive NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1712050828 NaN 931 19123 NaN North 2nd Street restaurant NaN NaN NaN NaN Cantina Dos Segundos NaN NaN https://www.cantinadossegundos.com/ NaN NaN POINT (-75.14042 39.96483) NaN 215-629-0500 NaN NaN mexican NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [78]:
# Select Frankford Hall based on the name column
frankford_hall = fishtown_food_drink.query("name == 'Frankford Hall'").squeeze()

# Get the x/y coordinates
frankford_hall_x = frankford_hall.geometry.x
frankford_hall_y = frankford_hall.geometry.y
In [79]:
# Get all subway stations within our Fishtown/No Libs polygon
fishtown_subway_stops = ox.geometries_from_polygon(nolibs_fishtown_outline, 
                                                   tags={"railway": "station"})
In [80]:
# Select Spring Garden based on the name column
spring_garden_station = fishtown_subway_stops.query("name == 'Spring Garden'").squeeze()

# Get the x/y coordinates
spring_garden_x = spring_garden_station.geometry.x
spring_garden_y = spring_garden_station.geometry.y

Find the nearest nodes on our OSMnx graph:

In [81]:
# Get the origin node
orig_node = ox.distance.nearest_nodes(G_nolibs_fishtown,  spring_garden_x, spring_garden_y) 

# Get the destination node
dest_node = ox.distance.nearest_nodes(G_nolibs_fishtown, frankford_hall_x, frankford_hall_y) 

Use networkx to find the shortest path between these graph nodes

In [82]:
# get the shortest path --> just a list of node IDs
route = nx.shortest_path(G_nolibs_fishtown, 
                         orig_node, 
                         dest_node, 
                         weight='length')
route
Out[82]:
[110156961,
 110156976,
 110240102,
 110240110,
 110156990,
 110408354,
 110150984,
 110151026,
 109812663,
 3405862196,
 110274404,
 786190278,
 786189685,
 786189751,
 786190146,
 786189922,
 110227372,
 110549183,
 110207010,
 7738710013,
 7738710015,
 1479201402,
 1479201356,
 1479201380,
 1479201400,
 1479201370,
 7741504690,
 1479201401,
 110447508,
 109990294,
 109834418,
 109921057,
 109801805,
 109801799,
 109998370]

Use ox.plot_graph_route() to plot a graph and highlight a specific route

In [83]:
ox.plot_graph_route(G_nolibs_fishtown, route, node_size=0);

Part 2: Pandana

"Pandas Network Analysis - dataframes of network queries, quickly"

A complementary set of OSM-related features:

  • Downloading OSM-based networks
  • Extracting amenity data (so-called "Points of Interest")
  • Calculating network-constrained distances
In [84]:
import pandana as pnda
from pandana.loaders import osm

Step 1: Get amenity data

Key function: osm.node_query()

  • This will extract amenities within a given bounding box.
  • Similar to the ox.geometries_from_bbox() function in OSMnx, but we slightly different syntax.
In [85]:
osm.node_query?

Get the bounding box for Northern Liberties / Fishtown:

In [86]:
boundary = nolibs_fishtown_outline.bounds
boundary
Out[86]:
(-75.14891, 39.955458, -75.113226, 39.984757)
In [87]:
[lng_min, lat_min, lng_max, lat_max] = boundary
In [88]:
# 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'])
In [89]:
poi_df.head()
Out[89]:
lat lon highway traffic_signals railway old_ref ref noref stop crossing disused:railway ele gnis:Class gnis:County gnis:County_num gnis:ST_alpha gnis:ST_num gnis:id import_uuid is_in name place wikidata wikipedia addr:city addr:housenumber addr:postcode addr:street opening_hours phone shop website alt_name amenity gnis:county_id gnis:created gnis:feature_id gnis:state_id historic:amenity note comment old_name addr:state brewery craft microbrewery operator restaurant man_made gnis:county_name gnis:import_uuid gnis:reviewed traffic_signals:direction network public_transport subway brand brand:wikidata brand:wikipedia platforms station wheelchair cuisine disused:amenity denomination religion capacity source:pkey addr:housename building designation payment:bitcoin addr:full description diet:vegetarian internet_access official_name short_name tram bus drive_in outdoor_seating smoking takeaway drink:club-mate internet_access:fee contact:phone email social_facility leisure healthcare sport is_in:state_code population collection_times compost farm_boxes addr:place addr:country atm operator:type operator:wikidata operator:wikipedia tactile_paving crossing:island barrier foot motor_vehicle emergency access fee toilets:disposal unisex fax fixme branch animal delivery tourism addr:unit direction crossing_ref payment:cash payment:cheque payment:credit_cards diet:vegan beds advertising lit artwork_type entrance office diet:dairy organic payment:credit_card communication:mobile_phone tower:type facebook playground drive_through historic disused:leisure construction:amenity natural books demolished:leisure opening_hours:covid19 twitter min_age
id
357274893 39.972335 -75.130176 NaN NaN NaN NaN NaN NaN NaN NaN NaN 7 NaN NaN NaN NaN NaN NaN NaN NaN Adaire Alexander School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Adaire School school 101 08/02/1979 1168055 42 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
357283515 39.976497 -75.116743 NaN NaN NaN NaN NaN NaN NaN NaN NaN 4 NaN NaN NaN NaN NaN NaN NaN NaN Maritime Academy Charter High School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN school 101 08/02/1979 1173424 42 NaN NaN Stephen A. Douglas High School closed in 2013 Stephen A. Douglas High School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
357288962 39.981243 -75.126783 NaN NaN NaN NaN NaN NaN NaN NaN NaN 8 NaN NaN NaN NaN NaN NaN NaN NaN Horatio B. Hackett School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Hackett School school 101 08/02/1979 1176347 42 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
357297150 39.983189 -75.141190 NaN NaN NaN NaN NaN NaN NaN NaN NaN 16 NaN NaN NaN NaN NaN NaN NaN NaN McKinley William School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN McKinley School school 101 08/02/1979 1180755 42 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
357303425 39.960668 -75.147121 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Yards Brewing Company NaN Q16903914 en:Yards Brewing Company Philadelphia 500 19123 Spring Garden Street NaN NaN NaN https://yardsbrewing.com/ NaN bar 101 NaN NaN NaN NaN NaN NaN NaN PA yes brewery yes Yards Brewing Company yes NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
In [90]:
len(poi_df)
Out[90]:
171
In [91]:
poi_df[['lat', 'lon', 'amenity']].head(10)
Out[91]:
lat lon amenity
id
357274893 39.972335 -75.130176 school
357283515 39.976497 -75.116743 school
357288962 39.981243 -75.126783 school
357297150 39.983189 -75.141190 school
357303425 39.960668 -75.147121 bar
357373853 39.972874 -75.127243 school
367962776 39.978845 -75.118371 fire_station
1475159482 39.969869 -75.144829 place_of_worship
1475229905 39.967481 -75.139696 disused
1475230378 39.966270 -75.140024 bar

Explore the amenities in this region

For the full list of amenities, see the OSM Wikipedia

In [92]:
chart = (
    alt.Chart(poi_df)
    .mark_bar()
    .encode(y=alt.Y("amenity", sort="-x"), x="count()", tooltip=["amenity", "count()"])
)

chart
Out[92]:

Step 2: Create a Pandana network

  • Key function: pdna_network_from_bbox()
  • It takes a bounding box and returns the OSM network within that region.
  • Multiple network types: 'walk' and 'drive'
In [93]:
net = osm.pdna_network_from_bbox(
    lat_min, lng_min, lat_max, lng_max, network_type="walk"
)
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/osmnet/load.py:445: FutureWarning: Assigning CRS to a GeoDataFrame without a geometry column is now deprecated and will not be supported in the future.
  gdf.crs = crs
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pyproj/crs/crs.py:68: FutureWarning: '+init=<authority>:<code>' syntax is deprecated. '<authority>:<code>' is the preferred initialization method. When making the change, be mindful of axis order changes: https://pyproj4.github.io/pyproj/stable/gotchas.html#axis-order-changes-in-proj-6
  return _prepare_from_string(" ".join(pjargs))
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/osmnet/load.py:445: FutureWarning: Assigning CRS to a GeoDataFrame without a geometry column is now deprecated and will not be supported in the future.
  gdf.crs = crs
Requesting network data within bounding box from Overpass API in 1 request(s)
Posting to http://www.overpass-api.de/api/interpreter with timeout=180, "{'data': '[out:json][timeout:180];(way["highway"]["highway"!~"motor|proposed|construction|abandoned|platform|raceway"]["foot"!~"no"]["pedestrians"!~"no"](39.95545800,-75.14891000,39.98475700,-75.11322600);>;);out;'}"
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/osmnet/load.py:237: DeprecationWarning: Flags not at the start of the expression '//(?s)(.*?)/'
  domain = re.findall(r'//(?s)(.*?)/', url)[0]
Downloaded 1,268.7KB from www.overpass-api.de in 1.05 seconds
Downloaded OSM network data within bounding box from Overpass API in 1 request(s) and 1.09 seconds
Returning OSM data with 6,807 nodes and 2,119 ways...
Edge node pairs completed. Took 2.74 seconds
Returning processed graph with 3,327 nodes and 5,067 edges...
Completed OSM data download and Pandana node and edge table creation in 4.15 seconds
Generating contraction hierarchies with 4 threads.
Setting CH node vector of size 3327
Setting CH edge vector of size 5093
Range graph removed 138 edges of 10186
. 10% . 20% . 30% . 40% . 50% . 60% . 70% . 80% . 90% . 100%

Step 3: Tell the network the location of amenities

Key function: network.set_pois()

  • Today, we'll explore these four amenities: "restaurant", "bar", "school", "car_sharing"
  • IMPORTANT: if you want to explore other amenities, you'll need to run the code below for your amenities of interest
In [94]:
# 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"]
    )
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:660: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxitems, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:668: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxdist, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:660: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxitems, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:668: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxdist, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:660: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxitems, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:668: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxdist, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:660: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxitems, type(pd.Series())):
/Users/nhand/opt/miniconda3/envs/musa-550-fall-2021/lib/python3.8/site-packages/pandana/network.py:668: DeprecationWarning: The default dtype for empty Series will be 'object' instead of 'float64' in a future version. Specify a dtype explicitly to silence this warning.
  elif isinstance(maxdist, type(pd.Series())):
In [95]:
# 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"}

Step 4: Plot the walking distance to the nearest POI

For every point on the network, find the nth nearest POI, calculate the distance, and color that point according to the distance.

  1. Use network.nearest_poi() to get distances from nodes to nearest POIs
  2. Merge coordinates of network nodes with distances to nearest POIs
  3. Plot the node coordinates, colored by distance to nth nearest POI

1. Use network.nearest_poi() to get distances from nodes to nearest POIs

In [96]:
num_pois
Out[96]:
10
In [97]:
amenity = 'bar'
access = net.nearest_pois(distance=1000, 
                          category=amenity, 
                          num_pois=num_pois)
In [98]:
access.head(n=20)
Out[98]:
1 2 3 4 5 6 7 8 9 10
id
103353219 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103357134 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103357139 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103407531 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103407534 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103417453 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103426172 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103426218 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103439886 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103449512 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103455424 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
103455428 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729330 549.151001 872.901001 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729372 420.627014 744.377014 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729430 456.679993 608.028015 610.737000 635.078003 635.078003 702.736023 713.599976 745.841003 932.004028 1000.0
109729453 554.469971 615.809998 639.916992 648.051025 705.817993 708.526978 732.867981 732.867981 839.302002 1000.0
109729661 480.838989 804.588989 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729673 412.653015 736.403015 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729699 306.233002 629.982971 919.973022 919.973022 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0
109729709 215.768005 539.518005 829.507996 829.507996 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.0

2. Merge coordinates of network nodes with distances to nearest POIs

In [99]:
net.nodes_df.head()
Out[99]:
x y
id
103353219 -75.115434 39.957472
103357134 -75.114734 39.954193
103357139 -75.113843 39.957221
103407531 -75.120641 39.955889
103407534 -75.119286 39.955627
In [100]:
access.head()
Out[100]:
1 2 3 4 5 6 7 8 9 10
id
103353219 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0
103357134 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0
103357139 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0
103407531 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0
103407534 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0
In [101]:
# 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"
)
In [102]:
nodes.head()
Out[102]:
x y 1 2 3 4 5 6 7 8 9 10 geometry
id
103353219 -75.115434 39.957472 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 POINT (-75.11543 39.95747)
103357134 -75.114734 39.954193 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 POINT (-75.11473 39.95419)
103357139 -75.113843 39.957221 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 POINT (-75.11384 39.95722)
103407531 -75.120641 39.955889 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 POINT (-75.12064 39.95589)
103407534 -75.119286 39.955627 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 1000.0 POINT (-75.11929 39.95563)

And now plot it!

Let's define a function to do this for us, since we'll repeat this plot multiple times

In [103]:
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

Evaluating amenity choice

The difference between maps to the nearest amenity and for example, the 5th nearest amenity tells us about the options consumers have

Example: bars

In [104]:
ax = plot_walking_distance(net, "bar", n=1)
ax.set_title("Walking distance to the nearest bar", fontsize=18);
In [105]:
ax = plot_walking_distance(net, "bar", n=3)
ax.set_title("Walking distance to the 3rd nearest bar", fontsize=18);

Example: schools

In [106]:
ax = plot_walking_distance(net, "school", n=1)
ax.set_title("Walking distance to the nearest school", fontsize=18);
In [107]:
ax = plot_walking_distance(net, "school", n=3)
ax.set_title("Walking distance to the 3rd nearest school", fontsize=18);

Example: restaurants

In [108]:
ax = plot_walking_distance(net, "restaurant", n=1)
ax.set_title("Walking distance to the nearest restaurant", fontsize=18);
In [109]:
ax = plot_walking_distance(net, "restaurant", n=5)
ax.set_title("Walking distance to the 5th nearest restaurant", fontsize=18);

Example: car sharing

In [110]:
ax = plot_walking_distance(net, "car_sharing", n=1)
ax.set_title("Walking distance to the nearest car sharing", fontsize=18);
In [111]:
ax = plot_walking_distance(net, "car_sharing", n=5)
ax.set_title("Walking distance to the 5th nearest car sharing", fontsize=18);

At-home exercise: Explore amenities in the neighborhood of your choice

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.

In [ ]: