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

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

Oct 27, 2020

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 [4]:
import osmnx as ox

Make sure the version >= 0.16 for this notebook to work!

In [5]:
print(ox.__version__)
0.16.1

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 [6]:
philly = ox.geocode_to_gdf('Philadelphia, PA, USA')
philly.head() 
Out[6]:
geometry place_name bbox_north bbox_south bbox_east bbox_west
0 POLYGON ((-75.28030 39.97500, -75.28022 39.974... Philadelphia, Philadelphia County, Pennsylvani... 40.137959 39.867005 -74.955831 -75.280298

We can plot it just like any other GeoDataFrame

In [7]:
# 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 [8]:
ax = ox.project_gdf(philly).plot(fc="lightblue", ec="gray")
ax.set_axis_off()

Some more examples:

In [9]:
# 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 [10]:
# Manhattan
ax = ox.project_gdf(place1).plot(fc="lightblue", ec="gray");
ax.set_axis_off()
In [11]:
# Miami-Dade County
ax = ox.project_gdf(place2).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
In [12]:
# Florida
ax = ox.project_gdf(place3).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
In [13]:
# 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 [14]:
# Get all amenities in Philadelphia
amenities = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": True})

len(amenities)
Out[14]:
8323
In [15]:
amenities.head()
Out[15]:
unique_id osmid element_type amenity geometry highway 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 wikipedia building gnis:edited craft microbrewery restaurant name:en fixme comment social_facility 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 fax smoking bar bus public_transport tourism source:pkey access fee sport name:zh addr:full twitter fuel:biodiesel fuel:biogas fuel:cng fuel:diesel fuel:e10 fuel:e85 fuel:electricity wifi drive_in collection_times food payment:bitcoin diet:halal note leisure drink:club-mate car_wash toilets:disposal unisex entrance diet:pescetarian lgbtq payment:credit_cards payment:debit_cards reservation park_ride supervised covered landuse contact:facebook source:cuisine addr:streetnumber social_facility:for diet:kosher contact:email wheelchair:description wheelchair:description:en contact:website layer service:bicycle:chain_tool service:bicycle:pump facebook network amenity_1 recycling:computers recycling:tv_monitor recycling_type ferry kitchen_hours 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 ref theatre:genre service_times addr:unit service:bicycle:tools beds level toilets currency:USD payment:visa cash_in currency:BCH currency:LTC currency:XBT opening_hours:kitchen date display faces office studio name:ca bench lit shelter_type addr:city:ar name:ar operator:wikidata start_date colour material music_venue addr:block_number seats indoor historic type payment:american_express payment:discover_card payment:mastercard self_service telephone surface internet_access:description name:vi payment:cheque min_age tram contact:instagram opening_hours:covid19 waste payment:credit_card seating street_vendor nodes building:levels capacity:disabled contact:twitter theatre:type ref:nrhp building:height vehicle natural water building:use ship:type automated building:material name:he source:name height roof:levels roof:material roof:shape 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 gnis:fcode nonsquare name:ja name:zn roof:orientation building:part operator:type elevation historic:name history maxstay monastery:type fuel:octane_95 library school owner loc_name drink police bin grades school:type old_name1 old_name2 ways
0 node/109811385 109811385 node bench POINT (-75.19487 40.05846) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 node/109993125 109993125 node pub POINT (-75.19107 40.06019) traffic_signals NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 node/274215467 274215467 node fast_food POINT (-75.19492 39.95935) NaN 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
3 node/274216093 274216093 node atm POINT (-75.19126 39.95765) NaN 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
4 node/274216582 274216582 node pub POINT (-75.20081 39.95476) NaN 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
In [16]:
# Get all bars in philadelphia
bars = ox.geometries_from_place("Philadelphia, PA", tags={"amenity": "bar"})

len(bars)
Out[16]:
145
In [17]:
bars.head()
Out[17]:
unique_id osmid element_type 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 music_venue payment:cash min_age alt_name nodes atm building building:levels addr:country brand nonsquare
0 node/357303425 357303425 node 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 NaN
1 node/655425272 655425272 node 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 NaN
2 node/1167079387 1167079387 node NaN NaN NaN NaN NaN bar NaN NaN NaN NaN Lickety Split Lounge 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 NaN
3 node/1204761128 1204761128 node 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 NaN
4 node/1424271155 1424271155 node 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 NaN
In [18]:
# 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[18]:
930
In [19]:
food_and_drink.head()
Out[19]:
unique_id osmid element_type 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 addr:streetnumber diet:kosher wheelchair:description facebook amenity_1 contact:facebook kitchen_hours fixme name:en name:es addr:unit 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 building:material roof:levels name:ja name:zn nonsquare
0 node/109993125 109993125 node 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
1 node/274216582 274216582 node 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
2 node/274216619 274216619 node 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
3 node/274216804 274216804 node 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
4 node/333786044 333786044 node 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
In [20]:
# Get higway bus stop features
bus_stops = ox.geometries_from_place("Philadelphia, PA", tags={"highway": "bus_stop"})

len(bus_stops)
Out[20]:
223
In [21]:
bus_stops.head()
Out[21]:
unique_id osmid element_type bus highway public_transport geometry bench covered name network operator shelter wheelchair route_ref local_ref designation source internet_access lit ref addr:street route_ref_1 departures_board note operator:wikidata brand brand:wikidata railway tram tactile_paving
0 node/109812837 109812837 node yes bus_stop platform POINT (-75.19362 40.05963) NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 node/361011456 361011456 node 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
2 node/750281693 750281693 node 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
3 node/768271130 768271130 node 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
4 node/1015342921 1015342921 node 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
In [22]:
# Get commercial and retail landuse features
landuse = ox.geometries_from_place("Philadelphia, PA", tags={"landuse": ["commercial", "retail"]})

len(landuse)
Out[22]:
165
In [23]:
landuse.head()
Out[23]:
unique_id osmid element_type wikidata geometry addr:city addr:housenumber addr:postcode addr:street landuse name phone shop nodes website historic historic:name old_name wikipedia farm_boxes addr:state building building:levels building:levels:underground building:material nonsquare office outdoor_seating smoking alt_name operator source wheelchair name:zh comment brewery craft microbrewery brand brand:wikidata brand:wikipedia ways type
0 node/2698888902 2698888902 node NaN POINT (-75.17547 39.92755) Philadelphia 1901 19145 South 18th Street retail Geneudy Mini Market +1 215 462 3152 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
1 way/46698901 46698901 way NaN POLYGON ((-75.17488 39.89827, -75.17494 39.898... NaN NaN NaN NaN commercial The Navy Yard NaN NaN [4319837164, 4319837648, 4319837616, 431983716... https://navyyard.org/ NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 way/49990198 49990198 way NaN POLYGON ((-75.16515 40.08216, -75.16588 40.082... NaN NaN NaN NaN retail Cedarbrook Plaza NaN NaN [634995223, 634995224, 634995225, 7196781088, ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 way/73038639 73038639 way NaN POLYGON ((-75.18471 40.01069, -75.18570 40.010... Philadelphia 3300 19129 Henry Avenue commercial Falls Center +1 215 438 3796 NaN [866844016, 5362254559, 5362254558, 5362254557... http://www.falls-center.com/ yes Women's Medical College Medical College of Pennsylvania Hospital NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 way/191635614 191635614 way NaN POLYGON ((-75.20920 39.95306, -75.20975 39.953... NaN NaN NaN NaN retail NaN NaN NaN [6960495452, 2022174514, 2022174519, 202217453... NaN NaN NaN NaN NaN 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 [24]:
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 [25]:
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=500)

Project and plot it:

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

Remove the nodes:

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

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

In [28]:
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", dist=2000)
G_projected = ox.project_graph(G)
In [29]:
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 [30]:
# the "drive" network
G = ox.graph_from_address("Northern Liberties, Philadelphia, PA", 
                          network_type='drive')
ox.plot_graph(G);
In [31]:
# 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 [32]:
berkeley = ox.graph_from_place("Berkeley, California", network_type='drive')
In [33]:
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 [34]:
url = "https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson"
hoods = gpd.read_file(url).rename(columns={"mapname": "neighborhood"})
In [35]:
hoods.head()
Out[35]:
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 2013-03-19T17:41:50 MULTIPOLYGON (((-75.05645 40.08743, -75.05667 ...
1 OVERBROOK Overbrook Overbrook 57004.924607 7.692499e+07 138 2013-03-19T17:41:50 2013-03-19T17:41:50 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 2013-03-19T17:41:50 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 2013-03-19T17:41:50 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 2013-03-19T17:41:50 MULTIPOLYGON (((-75.22722 40.03523, -75.22865 ...

Trim to Fishtown and Northern Liberties

In [36]:
sel = hoods['neighborhood'].isin(['Fishtown - Lower Kensington', 'Northern Liberties'])
nolibs_fishtown = hoods.loc[sel]
In [37]:
nolibs_fishtown.head()
Out[37]:
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 2013-03-19T17:41:50 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 2013-03-19T17:41:50 MULTIPOLYGON (((-75.12718 39.96086, -75.12959 ...
In [38]:
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 [39]:
nolibs_fishtown_outline = nolibs_fishtown.geometry.unary_union
nolibs_fishtown_outline
Out[39]:
In [40]:
type(nolibs_fishtown_outline)
Out[40]:
shapely.geometry.polygon.Polygon
In [41]:
# get the graph
G_nolibs_fishtown = ox.graph_from_polygon(nolibs_fishtown_outline, network_type='drive')
In [42]:
# 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 [43]:
nolibs_fishtown.geometry.unary_union
Out[43]:
In [44]:
nolibs_fishtown.geometry.unary_union.convex_hull
Out[44]:

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

Two functions:

  • ox.basic_stats()
  • ox.extended_stats()
In [48]:
ox.basic_stats(G_nolibs_fishtown)
Out[48]:
{'n': 623,
 'm': 1218,
 'k_avg': 3.9101123595505616,
 'intersection_count': 605,
 'streets_per_node_avg': 3.380417335473515,
 'streets_per_node_counts': {0: 0, 1: 18, 2: 1, 3: 340, 4: 256, 5: 6, 6: 2},
 'streets_per_node_proportion': {0: 0.0,
  1: 0.028892455858747994,
  2: 0.0016051364365971107,
  3: 0.5457463884430177,
  4: 0.41091492776886035,
  5: 0.009630818619582664,
  6: 0.0032102728731942215},
 'edge_length_total': 103742.862,
 'edge_length_avg': 85.17476354679802,
 'street_length_total': 86457.59700000001,
 'street_length_avg': 86.54414114114115,
 'street_segments_count': 999,
 'node_density_km': None,
 'intersection_density_km': None,
 'edge_density_km': None,
 'street_density_km': None,
 'circuity_avg': 1.0207469349821878,
 'self_loop_proportion': 0.0,
 'clean_intersection_count': None,
 'clean_intersection_density_km': None}
In [49]:
sorted(ox.extended_stats(G_nolibs_fishtown).keys())
Out[49]:
['avg_neighbor_degree',
 'avg_neighbor_degree_avg',
 'avg_weighted_neighbor_degree',
 'avg_weighted_neighbor_degree_avg',
 'clustering_coefficient',
 'clustering_coefficient_avg',
 'clustering_coefficient_weighted',
 'clustering_coefficient_weighted_avg',
 'degree_centrality',
 'degree_centrality_avg',
 'pagerank',
 'pagerank_max',
 'pagerank_max_node',
 'pagerank_min',
 'pagerank_min_node']

Finding the shortest route

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

In [50]:
import networkx as nx

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

In [51]:
# 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 [52]:
fishtown_food_drink.head()
Out[52]:
unique_id osmid element_type 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 payment:bitcoin wheelchair leisure sport addr:country source capacity addr:unit description diet:vegan nodes building kitchen_hours
0 node/357303425 357303425 node 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
1 node/1475230378 1475230378 node 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
2 node/1712028161 1712028161 node 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
3 node/1712044411 1712044411 node 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
4 node/1712050828 1712050828 node 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
In [54]:
# 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)
In [55]:
# Get all subway stations within our Fishtown/No Libs polygon
fishtown_subway_stops = ox.geometries_from_polygon(nolibs_fishtown_outline, 
                                                   tags={"railway": "station"})
In [56]:
# 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:

In [57]:
# 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) 

Important

get_nearest_node() needs a point in (lat, lng) or (y, x) order!!

Use networkx to find the shortest path between these graph nodes

In [58]:
# 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[58]:
[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 [59]:
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 [60]:
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 [61]:
osm.node_query?

Get the bounding box for Northern Liberties / Fishtown:

In [62]:
boundary = nolibs_fishtown_outline.bounds
boundary
Out[62]:
(-75.14891, 39.955458, -75.113226, 39.984757)
In [63]:
[lng_min, lat_min, lng_max, lat_max] = boundary
In [64]:
# 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 [65]:
poi_df.head()
Out[65]:
lat lon highway traffic_signals railway old_ref ref noref crossing 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 comment disused:amenity historic:amenity addr:state brewery craft microbrewery operator restaurant religion leisure 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 denomination 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 sport is_in:state_code population collection_times compost farm_boxes addr:place note addr:country atm barrier foot motor_vehicle emergency access fee toilets:disposal unisex fax fixme healthcare animal delivery tourism addr:unit direction crossing_ref payment:cash payment:cheque payment:credit_cards diet:vegan beds advertising lit artwork_type entrance office tower:type facebook drive_through historic disused:leisure construction:amenity natural books
id
357274893 39.972335 -75.130176 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
357280489 39.969835 -75.126565 NaN NaN NaN NaN NaN NaN NaN 5 NaN NaN NaN NaN NaN NaN NaN NaN Chandler School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN school 101 08/02/1979 1171555 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
357288962 39.981243 -75.126783 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
357297150 39.983168 -75.141566 NaN NaN NaN NaN NaN NaN NaN 16 NaN NaN NaN NaN NaN NaN NaN NaN McKinley School NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 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
357303425 39.960668 -75.147121 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 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
In [66]:
len(poi_df)
Out[66]:
171
In [67]:
poi_df[['lat', 'lon', 'amenity']].head(10)
Out[67]:
lat lon amenity
id
357274893 39.972335 -75.130176 school
357280489 39.969835 -75.126565 school
357288962 39.981243 -75.126783 school
357297150 39.983168 -75.141566 school
357303425 39.960668 -75.147121 bar
357307010 39.963827 -75.144182 place_of_worship
357307221 39.981509 -75.118768 school
357373853 39.972874 -75.127243 school
367962776 39.978845 -75.118371 fire_station
1475159482 39.969869 -75.144829 place_of_worship

Explore the amenities in this region

For the full list of amenities, see the OSM Wikipedia

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

chart
Out[68]:

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 [69]:
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-2020/lib/python3.7/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-2020/lib/python3.7/site-packages/pyproj/crs/crs.py:53: 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))
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-2020/lib/python3.7/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,185.2KB from www.overpass-api.de in 1.63 seconds
Downloaded OSM network data within bounding box from Overpass API in 1 request(s) and 1.65 seconds
Returning OSM data with 6,313 nodes and 2,004 ways...
Edge node pairs completed. Took 3.46 seconds
Returning processed graph with 3,180 nodes and 4,838 edges...
Completed OSM data download and Pandana node and edge table creation in 5.45 seconds

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 [70]:
# 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-2020/lib/python3.7/site-packages/pandana/network.py:627: 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-2020/lib/python3.7/site-packages/pandana/network.py:635: 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-2020/lib/python3.7/site-packages/pandana/network.py:627: 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-2020/lib/python3.7/site-packages/pandana/network.py:635: 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-2020/lib/python3.7/site-packages/pandana/network.py:627: 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-2020/lib/python3.7/site-packages/pandana/network.py:635: 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-2020/lib/python3.7/site-packages/pandana/network.py:627: 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-2020/lib/python3.7/site-packages/pandana/network.py:635: 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 [71]:
# 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 [72]:
num_pois
Out[72]:
10
In [73]:
amenity = 'bar'
access = net.nearest_pois(distance=1000, 
                          category=amenity, 
                          num_pois=num_pois)
In [74]:
access.head(n=20)
Out[74]:
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.000000
103357134 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103357139 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103407531 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103407534 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103417453 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103426172 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103426218 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103439886 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103449512 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103455424 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
103455428 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729330 550.145020 869.825012 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729372 420.851990 740.531982 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729430 456.153992 513.140991 608.002991 610.737000 636.344971 636.344971 704.914001 713.379028 745.841003 932.004028
109729453 420.438995 553.943970 615.588989 639.916992 648.051025 705.793030 708.526978 734.135010 734.135010 839.302002
109729661 480.747009 800.427002 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729673 412.723999 732.403992 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729699 304.622009 624.302002 912.182007 912.182007 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000
109729709 216.554993 536.234985 824.114990 824.114990 987.038025 1000.000000 1000.000000 1000.000000 1000.000000 1000.000000

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

In [75]:
net.nodes_df.head()
Out[75]:
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 [76]:
access.head()
Out[76]:
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 [77]:
# 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 [78]:
nodes.head()
Out[78]:
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 [79]:
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 [80]:
ax = plot_walking_distance(net, "bar", n=1)
ax.set_title("Walking distance to the nearest bar", fontsize=18);
In [81]:
ax = plot_walking_distance(net, "bar", n=3)
ax.set_title("Walking distance to the 3rd nearest bar", fontsize=18);

Example: schools

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

Example: restaurants

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

Example: car sharing

In [90]:
ax = plot_walking_distance(net, "car_sharing", n=1)
ax.set_title("Walking distance to the nearest car sharing", fontsize=18);
In [91]:
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 [ ]:
 
In [ ]: