In [1]:
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import geopandas as gpd
import altair as alt
In [2]:
pd.options.display.max_columns = 999

Week 9B: OpenStreetMap, Urban Networks, and Interactive Web Maps¶

Nov 2, 2022

The final project¶

https://github.com/MUSA-550-Fall-2022/final-project

This week: OpenStreetMap (OSM)¶

Three parts:

  • OSMnx: downloading and manipulating streets as networks
  • Pandana: networks focused on accessibility of amenities, e.g., walking distances to the nearest amenities
  • Related: interactive web maps in Python
In [3]:
import osmnx as ox

Part 3: Interactive maps in Python¶

Haven't we already done this?

Yes!¶

We've used hvplot, holoviews, datashader, etc. to create interactive map-based visualizations

Why do we need something more?¶

The benefits of Leaflet¶

  • The leading open-source mapping library
  • Simple and powerful
  • Leverage the open-source community and lots of powerful plugins

Folium: Leaflet in Python¶

Pros

  • Create Leaflet.js maps directly from Python
  • Combine power of Leaflet.js with the data wrangling ease of Python

Cons

  • A wrapper for most, but not all of Leaflet's functionality
  • Can be difficult to debug and find errors

Revisiting routing with OSMnx¶

OSMnx leverages Folium under the hood to make interactive graphs of street networks!

Key function: ox.plot_graph_folium will make an interactive map of the graph object

Load the street network around City Hall

In [4]:
G = ox.graph_from_address('City Hall, Philadelphia, USA', 
                          dist=1500, 
                          network_type='drive')
In [5]:
# plot the street network with folium
graph_map = ox.plot_graph_folium(G, 
                                 popup_attribute='name', 
                                 edge_width=2)
In [6]:
graph_map
Out[6]:
Make this Notebook Trusted to load map: File -> Trust Notebook

And now save the map object and load it into the Jupyter notebook

In [7]:
from IPython.display import IFrame # loads HTML files
In [8]:
filepath = 'graph.html'
graph_map.save(filepath)
IFrame(filepath, width=600, height=500)
Out[8]:

Note¶

Folium map objects are supposed to render automatically in the Jupyter notebook, so if you output a Folium map from a notebook cell, it will render.

However, there a lot of times when the map won't show up properly, especially if the map has a large amount of data. Saving the file locally and loading it to the notebook via an IFrame will always work.

In [9]:
type(graph_map)
Out[9]:
folium.folium.Map
In [10]:
graph_map
Out[10]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Exercise: shortest route between the Liberty Bell and Art Museum¶

Let's calculate the shortest route between the Art Museum and the Liberty Bell.

See last lecture (Lecture 9A) for a guide!

Step 1: Download amenity info from OSM using OSMnx¶

Use OSMnx to download all amenities in Philadelphia of type "tourism".

  • The ox.geometries_from_place() can download OSM features with a specific tag
  • Consult the OSM pages (the Art Museum and Liberty Bell) for each feature for additional info
  • Both features are categorized as "tourism" in the OSM data — use the "tags" keyword to select this category
In [11]:
philly_tourism = ox.geometries_from_place("Philadelphia, PA", tags={"tourism": True})
In [12]:
len(philly_tourism)
Out[12]:
505
In [13]:
philly_tourism.head()
Out[13]:
ref geometry ele gnis:county_id gnis:created gnis:feature_id gnis:state_id name tourism brand brand:wikidata brand:wikipedia operator artwork_type wikidata information addr:state gnis:county_name gnis:import_uuid gnis:reviewed source fee opening_hours artist_name wheelchair wikipedia addr:city historic name:de designation amenity description museum operator:type alt_name website source_1 addr:housenumber addr:postcode addr:street opening_hours:covid19 layer phone contact:email check_date:opening_hours name:en barrier board_type toilets:wheelchair inscription material start_date subject:wikipedia memorial level natural artwork_subject attraction note comment historic:amenity garden:type leisure postal_code internet_access official_name short_name parking name:ru guest_house covered drinking_water openfire addr:unit artist:wikidata image mimics source:url direction nodes building building:material ref:nrhp name:hi building:levels height roof:shape highway incline step_count old_name smoking building:colour place air_conditioning fax rooms stars roof:colour heritage heritage:operator addr:country contact:fax contact:phone ship:type addr:housename roof:material email abandoned:amenity internet_access:fee internet_access:ssid not:brand:wikidata shop roof:levels area name:zh artist:website bicycle bridge foot horse lit sac_scale surface trail_visibility width bridge:structure man_made boundary heritage:website nrhp:criteria nrhp:inscription_date ownership protected protection_title building:part subject subject:wikidata ways type
element_type osmid
node 357371322 NaN POINT (-75.19580 39.96970) 17 101 08/23/2007 2347097 42 Bird Lake Picnic Area picnic_site NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
360500844 NaN POINT (-75.19582 39.95352) NaN NaN NaN NaN NaN Hilton Inn at Penn hotel Hilton Q598884 en:Hilton Hotels & Resorts Hilton NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
360542779 NaN POINT (-75.18932 39.95540) NaN NaN NaN NaN NaN Mario the Magnificent artwork NaN NaN NaN NaN statue Q98563440 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
360777728 NaN POINT (-75.19021 39.95230) NaN NaN NaN NaN NaN Pennsylvania Historical Marker: ENIAC, first a... information NaN NaN NaN NaN NaN NaN board NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
360777735 NaN POINT (-75.19166 39.95123) NaN NaN NaN NaN NaN John Harrison, Chemist artwork NaN NaN NaN NaN statue NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

Step 2: Identify the Art Museum and Liberty Bell geometries¶

You should notice we have the building footprint for the Art Museum (a polygon geometry) and the point location for the Liberty Bell.

  • The names of the features are "Philadelphia Museum of Art" and "Liberty Bell" — you can identify these names using the OSM website
  • The .squeeze() function can be useful for converting to a Series object from a DataFrame of length 1
In [14]:
# How to find the name of the POI: search for keywords
philly_tourism.loc[philly_tourism['name'].str.contains("Art", na=False)]
Out[14]:
ref geometry ele gnis:county_id gnis:created gnis:feature_id gnis:state_id name tourism brand brand:wikidata brand:wikipedia operator artwork_type wikidata information addr:state gnis:county_name gnis:import_uuid gnis:reviewed source fee opening_hours artist_name wheelchair wikipedia addr:city historic name:de designation amenity description museum operator:type alt_name website source_1 addr:housenumber addr:postcode addr:street opening_hours:covid19 layer phone contact:email check_date:opening_hours name:en barrier board_type toilets:wheelchair inscription material start_date subject:wikipedia memorial level natural artwork_subject attraction note comment historic:amenity garden:type leisure postal_code internet_access official_name short_name parking name:ru guest_house covered drinking_water openfire addr:unit artist:wikidata image mimics source:url direction nodes building building:material ref:nrhp name:hi building:levels height roof:shape highway incline step_count old_name smoking building:colour place air_conditioning fax rooms stars roof:colour heritage heritage:operator addr:country contact:fax contact:phone ship:type addr:housename roof:material email abandoned:amenity internet_access:fee internet_access:ssid not:brand:wikidata shop roof:levels area name:zh artist:website bicycle bridge foot horse lit sac_scale surface trail_visibility width bridge:structure man_made boundary heritage:website nrhp:criteria nrhp:inscription_date ownership protected protection_title building:part subject subject:wikidata ways type
element_type osmid
node 367974278 NaN POINT (-75.15240 40.03748) 61 NaN NaN 2349240 NaN La Salle University Art Museum museum NaN NaN NaN NaN NaN Q16893816 NaN PA Philadelphia 57871b70-0100-4405-bb30-88b2e001a944 no USGS Geonames NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4064343335 NaN POINT (-75.22112 40.02427) NaN NaN NaN NaN NaN Soft Illusions Art Gallery gallery NaN NaN NaN NaN NaN NaN NaN PA NaN NaN NaN NaN NaN NaN NaN NaN NaN Philadelphia NaN NaN NaN NaN NaN NaN NaN NaN http://www.softillusions.net/ NaN 4226 19127 Main Street NaN NaN (215) 840-0832 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
5368454121 NaN POINT (-75.19477 39.95406) NaN NaN NaN NaN NaN Institute of Contemporary Art museum NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN yes NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 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
5718906477 NaN POINT (-75.13058 39.99573) NaN NaN NaN NaN NaN Art Making Machine Studios gallery NaN NaN NaN NaN NaN NaN NaN PA NaN NaN NaN NaN NaN NaN NaN NaN NaN Philadelphia NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3000 19133 North Hope Street NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6639391809 NaN POINT (-75.16088 39.96383) NaN NaN NaN NaN NaN Philadelphia Museum of Jewish Art museum NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN https://rodephshalom.org/community/philadelphi... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
6680708848 NaN POINT (-75.16833 39.94733) NaN NaN NaN NaN NaN Romanian Folk Art Museum museum NaN NaN NaN NaN NaN Q113484178 NaN PA NaN NaN NaN NaN NaN NaN NaN NaN NaN Philadelphia NaN NaN NaN NaN NaN NaN NaN NaN http://www.romanianculture.us/ NaN 1606 19103 Spruce Street NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
way 28533972 NaN POLYGON ((-75.18116 39.96467, -75.18138 39.964... 32 NaN NaN NaN NaN Philadelphia Museum of Art museum NaN NaN NaN NaN NaN Q510324 NaN PA NaN NaN NaN NaN NaN Su,Mo,Th,Sa 10:00-17:00; Fr 10:00-20:45 NaN yes en:Philadelphia Museum of Art Philadelphia NaN NaN NaN NaN NaN art NaN NaN https://www.philamuseum.org/ NaN 2600 19130 Benjamin Franklin Parkway NaN NaN +1-215-763-8100 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN [313440467, 313440468, 313440469, 313440470, 3... 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
333931830 NaN POLYGON ((-75.16410 39.95543, -75.16416 39.955... 13 NaN NaN 1196718 NaN Pennsylvania Academy of the Fine Arts museum NaN NaN NaN NaN NaN Q1952033 NaN PA NaN NaN NaN NaN NaN Tu-Fr 10:00-17:00; Sa-Su 11:00-17:00 NaN NaN en:Pennsylvania Academy of the Fine Arts Philadelphia building NaN NaN NaN NaN art NaN NaN https://www.pafa.org/ NaN 118 19102 North Broad Street NaN NaN +1-215-972-7600 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN [3409804705, 3409804706, 3453181052, 340980470... yes NaN 71000731 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN US NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
988345364 NaN POLYGON ((-75.21924 40.08337, -75.21934 40.083... 84 NaN NaN 2350852 NaN Woodmere Art Museum museum NaN NaN NaN NaN NaN Q2424929 NaN PA Philadelphia 57871b70-0100-4405-bb30-88b2e001a944 no USGS Geonames NaN NaN NaN NaN NaN Philadelphia NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 9201 19118 Germantown Avenue NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN [367974360, 9135569422, 9135569423, 9135569424... 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
In [15]:
art_museum = philly_tourism.query("name == 'Philadelphia Museum of Art'").squeeze()

art_museum.geometry
Out[15]:
b''
In [16]:
liberty_bell = philly_tourism.query("name == 'Liberty Bell'").squeeze()

liberty_bell.geometry
Out[16]:
b''

Step 3: Extract the lat and lng coordinates¶

For the Art Museum geometry, we can use the .geometry.centroid attribute to calculate the centroid of the building footprint.

Remember: we need the coordinates in the order of (latitude, longitude)

In [17]:
liberty_bell_x = liberty_bell.geometry.x
liberty_bell_y = liberty_bell.geometry.y
In [18]:
art_museum_x = art_museum.geometry.centroid.x
art_museum_y = art_museum.geometry.centroid.y

Step 4: Find the nearest nodes on our OSMnx graph¶

Use the street network graph around City Hall and the ox.get_nearest_node() function to find the starting and ending nodes for the trip.

In [19]:
# Get the origin node
orig_node = ox.distance.nearest_nodes(G, liberty_bell_x, liberty_bell_y) 

# Get the destination node
dest_node = ox.distance.nearest_nodes(G, art_museum_x, art_museum_y) 

Step 5: Use networkx to find the shortest path between nodes¶

The nx.shortest_path() will do the calculation for you!

In [20]:
import networkx as nx
In [21]:
# Calculate the shortest path between these nodes
route = nx.shortest_path(G, orig_node, dest_node)
In [22]:
route
Out[22]:
[3408446156,
 110217312,
 109814427,
 109825548,
 109825559,
 109809258,
 109825575,
 109825606,
 109789819,
 775433860,
 110232279,
 646149049,
 110232451,
 109755755,
 109744095,
 109792682,
 775896555,
 775896556,
 110329757,
 110329738,
 110329851,
 110125895,
 534969088,
 109745905,
 109740423,
 110330569,
 550175455,
 550175399]

Example: interactive maps of network routes¶

Now, we can overlay the shortest route between two nodes on the folium map.

Key function: use ox.plot_route_folium to plot the route.

In [23]:
# plot the route with folium
route_map = ox.plot_route_folium(G, route)
In [24]:
filepath = 'route.html'
route_map.save(filepath)
IFrame(filepath, width=600, height=500)
Out[24]:

We can also add the underlying street network graph

In [25]:
# plot the route with folium on top of the previously created graph_map
route_graph_map = ox.plot_route_folium(G, route, route_map=graph_map, color="red", weight=5,)
In [28]:
# save as html file then display map as an iframe
filepath = 'route_graph.html'
route_graph_map.save(filepath)
IFrame(filepath, width=600, height=500)
Out[28]:

Note the Leaflet annotation in the bottom right corner of the maps...

Getting started with Folium¶

Things we'll cover:

  1. Creating a base map with tiles
  2. Overlaying GeoJSON polygons
  3. Plotting an interactive choropleth
In [29]:
import folium

3.1 Creating a Folium map¶

Key function: folium.Map

Lots of configuration options¶

Some key ones:

  • location: the center location of the map
  • zoom_start: the initial zoom level of the map
  • tiles: the name of the tile provider

Let's take a look at the help message:

In [30]:
folium.Map?
Init signature:
folium.Map(
    location=None,
    width='100%',
    height='100%',
    left='0%',
    top='0%',
    position='relative',
    tiles='OpenStreetMap',
    attr=None,
    min_zoom=0,
    max_zoom=18,
    zoom_start=10,
    min_lat=-90,
    max_lat=90,
    min_lon=-180,
    max_lon=180,
    max_bounds=False,
    crs='EPSG3857',
    control_scale=False,
    prefer_canvas=False,
    no_touch=False,
    disable_3d=False,
    png_enabled=False,
    zoom_control=True,
    **kwargs,
)
Docstring:     
Create a Map with Folium and Leaflet.js

Generate a base map of given width and height with either default
tilesets or a custom tileset URL. The following tilesets are built-in
to Folium. Pass any of the following to the "tiles" keyword:

    - "OpenStreetMap"
    - "Mapbox Bright" (Limited levels of zoom for free tiles)
    - "Mapbox Control Room" (Limited levels of zoom for free tiles)
    - "Stamen" (Terrain, Toner, and Watercolor)
    - "Cloudmade" (Must pass API key)
    - "Mapbox" (Must pass API key)
    - "CartoDB" (positron and dark_matter)

You can pass a custom tileset to Folium by passing a Leaflet-style
URL to the tiles parameter: ``http://{s}.yourtiles.com/{z}/{x}/{y}.png``.

You can find a list of free tile providers here:
``http://leaflet-extras.github.io/leaflet-providers/preview/``.
Be sure to check their terms and conditions and to provide attribution
with the `attr` keyword.

Parameters
----------
location: tuple or list, default None
    Latitude and Longitude of Map (Northing, Easting).
width: pixel int or percentage string (default: '100%')
    Width of the map.
height: pixel int or percentage string (default: '100%')
    Height of the map.
tiles: str, default 'OpenStreetMap'
    Map tileset to use. Can choose from a list of built-in tiles,
    pass a custom URL or pass `None` to create a map without tiles.
    For more advanced tile layer options, use the `TileLayer` class.
min_zoom: int, default 0
    Minimum allowed zoom level for the tile layer that is created.
max_zoom: int, default 18
    Maximum allowed zoom level for the tile layer that is created.
zoom_start: int, default 10
    Initial zoom level for the map.
attr: string, default None
    Map tile attribution; only required if passing custom tile URL.
crs : str, default 'EPSG3857'
    Defines coordinate reference systems for projecting geographical points
    into pixel (screen) coordinates and back.
    You can use Leaflet's values :
    * EPSG3857 : The most common CRS for online maps, used by almost all
    free and commercial tile providers. Uses Spherical Mercator projection.
    Set in by default in Map's crs option.
    * EPSG4326 : A common CRS among GIS enthusiasts.
    Uses simple Equirectangular projection.
    * EPSG3395 : Rarely used by some commercial tile providers.
    Uses Elliptical Mercator projection.
    * Simple : A simple CRS that maps longitude and latitude into
    x and y directly. May be used for maps of flat surfaces
    (e.g. game maps). Note that the y axis should still be inverted
    (going from bottom to top).
control_scale : bool, default False
    Whether to add a control scale on the map.
prefer_canvas : bool, default False
    Forces Leaflet to use the Canvas back-end (if available) for
    vector layers instead of SVG. This can increase performance
    considerably in some cases (e.g. many thousands of circle
    markers on the map).
no_touch : bool, default False
    Forces Leaflet to not use touch events even if it detects them.
disable_3d : bool, default False
    Forces Leaflet to not use hardware-accelerated CSS 3D
    transforms for positioning (which may cause glitches in some
    rare environments) even if they're supported.
zoom_control : bool, default True
    Display zoom controls on the map.
**kwargs
    Additional keyword arguments are passed to Leaflets Map class:
    https://leafletjs.com/reference-1.6.0.html#map

Returns
-------
Folium Map Object

Examples
--------
>>> m = folium.Map(location=[45.523, -122.675], width=750, height=500)
>>> m = folium.Map(location=[45.523, -122.675], tiles='cartodb positron')
>>> m = folium.Map(
...    location=[45.523, -122.675],
...    zoom_start=2,
...    tiles='https://api.mapbox.com/v4/mapbox.streets/{z}/{x}/{y}.png?access_token=mytoken',
...    attr='Mapbox attribution'
...)
File:           ~/mambaforge/envs/musa-550-fall-2022/lib/python3.9/site-packages/folium/folium.py
Type:           type
Subclasses:     

The default tiles: OpenStreetMap¶

In [31]:
# let's center the map on Philadelphia
m = folium.Map(
    location=[39.99, -75.13],
    zoom_start=11
)

m
Out[31]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [32]:
m = folium.Map(
    location=[39.99, -75.13],
    zoom_start=11,
     tiles='stamenwatercolor'
)

m
Out[32]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Using custom tile sets¶

  • Many sites provide free tile sets for download
  • Just need the URL of the tile server
  • Very useful demo of tile providers: https://leaflet-extras.github.io/leaflet-providers/preview

Let's try out the ESRI World Map:

Important: for custom tile providers, you need to specify the attribution too!

In [33]:
tile_url = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png'
attr = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
In [34]:
m = folium.Map(
    location=[39.99, -75.13],
    zoom_start=11,
    tiles=tile_url,
    attr=attr
)

m
Out[34]:
Make this Notebook Trusted to load map: File -> Trust Notebook

3.2 Overlaying GeoJSON on a folium map¶

Key function: folium.GeoJson

Key parameters:

  • style_function: set the default style of the features
  • highlight_function: set the style when the mouse hovers over the features
  • tooltip: add a tooltip when hovering over a feature
In [35]:
folium.GeoJson?
Init signature:
folium.GeoJson(
    data,
    style_function=None,
    highlight_function=None,
    name=None,
    overlay=True,
    control=True,
    show=True,
    smooth_factor=None,
    tooltip=None,
    embed=True,
    popup=None,
    zoom_on_click=False,
    marker=None,
)
Docstring:     
Creates a GeoJson object for plotting into a Map.

Parameters
----------
data: file, dict or str.
    The GeoJSON data you want to plot.
    * If file, then data will be read in the file and fully
    embedded in Leaflet's JavaScript.
    * If dict, then data will be converted to JSON and embedded
    in the JavaScript.
    * If str, then data will be passed to the JavaScript as-is.
    * If `__geo_interface__` is available, the `__geo_interface__`
    dictionary will be serialized to JSON and
    reprojected if `to_crs` is available.
style_function: function, default None
    Function mapping a GeoJson Feature to a style dict.
highlight_function: function, default None
    Function mapping a GeoJson Feature to a style dict for mouse events.
name : string, default None
    The name of the Layer, as it will appear in LayerControls
overlay : bool, default True
    Adds the layer as an optional overlay (True) or the base layer (False).
control : bool, default True
    Whether the Layer will be included in LayerControls
show: bool, default True
    Whether the layer will be shown on opening (only for overlays).
smooth_factor: float, default None
    How much to simplify the polyline on each zoom level. More means
    better performance and smoother look, and less means more accurate
    representation. Leaflet defaults to 1.0.
tooltip: GeoJsonTooltip, Tooltip or str, default None
    Display a text when hovering over the object. Can utilize the data,
    see folium.GeoJsonTooltip for info on how to do that.
popup: GeoJsonPopup, optional
    Show a different popup for each feature by passing a GeoJsonPopup object.
marker: Circle, CircleMarker or Marker, optional
    If your data contains Point geometry, you can format the markers by passing a Circle,
    CircleMarker or Marker object with your wanted options. The `style_function` and
    `highlight_function` will also target the marker object you passed.
embed: bool, default True
    Whether to embed the data in the html file or not. Note that disabling
    embedding is only supported if you provide a file link or URL.
zoom_on_click: bool, default False
    Set to True to enable zooming in on a geometry when clicking on it.

Examples
--------
>>> # Providing filename that shall be embedded.
>>> GeoJson('foo.json')
>>> # Providing filename that shall not be embedded.
>>> GeoJson('foo.json', embed=False)
>>> # Providing dict.
>>> GeoJson(json.load(open('foo.json')))
>>> # Providing string.
>>> GeoJson(open('foo.json').read())

>>> # Provide a style_function that color all states green but Alabama.
>>> style_function = lambda x: {'fillColor': '#0000ff' if
...                             x['properties']['name']=='Alabama' else
...                             '#00ff00'}
>>> GeoJson(geojson, style_function=style_function)
File:           ~/mambaforge/envs/musa-550-fall-2022/lib/python3.9/site-packages/folium/features.py
Type:           type
Subclasses:     
In [36]:
folium.GeoJsonTooltip?
Init signature:
folium.GeoJsonTooltip(
    fields,
    aliases=None,
    labels=True,
    localize=False,
    style=None,
    class_name='foliumtooltip',
    sticky=True,
    **kwargs,
)
Docstring:     
Create a tooltip that uses data from either geojson or topojson.

Parameters
----------
fields: list or tuple.
    Labels of GeoJson/TopoJson 'properties' or GeoPandas GeoDataFrame
    columns you'd like to display.
aliases: list/tuple of strings, same length/order as fields, default None.
    Optional aliases you'd like to display in the tooltip as field name
    instead of the keys of `fields`.
labels: bool, default True.
    Set to False to disable displaying the field names or aliases.
localize: bool, default False.
    This will use JavaScript's .toLocaleString() to format 'clean' values
    as strings for the user's location; i.e. 1,000,000.00 comma separators,
    float truncation, etc.
    Available for most of JavaScript's primitive types (any data you'll
    serve into the template).
style: str, default None.
    HTML inline style properties like font and colors. Will be applied to
    a div with the text in it.
sticky: bool, default True
    Whether the tooltip should follow the mouse.
**kwargs: Assorted.
    These values will map directly to the Leaflet Options. More info
    available here: https://leafletjs.com/reference-1.6.0#tooltip

Examples
--------
# Provide fields and aliases, with Style.
>>> GeoJsonTooltip(
>>>     fields=['CNTY_NM', 'census-pop-2015', 'census-md-income-2015'],
>>>     aliases=['County', '2015 Census Population', '2015 Median Income'],
>>>     localize=True,
>>>     style=('background-color: grey; color: white; font-family:'
>>>            'courier new; font-size: 24px; padding: 10px;')
>>> )
# Provide fields, with labels off and fixed tooltip positions.
>>> GeoJsonTooltip(fields=('CNTY_NM',), labels=False, sticky=False)
File:           ~/mambaforge/envs/musa-550-fall-2022/lib/python3.9/site-packages/folium/features.py
Type:           type
Subclasses:     

Example: Philadelphia ZIP codes & Neighborhoods¶

In [37]:
# Load neighborhoods from GitHub
url = "https://github.com/azavea/geo-data/raw/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson"
hoods = gpd.read_file(url).rename(columns={"mapname": "neighborhood"})
In [38]:
# Load ZIP codes from Open Data Philly
zip_url = "http://data.phl.opendata.arcgis.com/datasets/b54ec5210cee41c3a884c9086f7af1be_0.geojson"
zip_codes = gpd.read_file(zip_url).rename(columns={"CODE":"ZIP Code"})
In [39]:
ax = ox.project_gdf(hoods).plot(fc="lightblue", ec="gray")
ax.set_axis_off()
In [40]:
ax = ox.project_gdf(zip_codes).plot(fc="lightblue", ec="gray")
ax.set_axis_off()

Define functions to set the styles:

In [41]:
def get_zip_code_style(feature):
    """Return a style dict."""
    return {"weight": 2, "color": "white"}


def get_neighborhood_style(feature):
    """Return a style dict."""
    return {"weight": 2, "color": "lightblue", "fillOpacity": 0.1}


def get_highlighted_style(feature):
    """Return a style dict when highlighting a feature."""
    return {"weight": 2, "color": "red"}

Usual Leaflet/Folium syntax¶

  1. Create the map
  2. Create your overlay layer
  3. Add your overlay layer to your map
In [42]:
# Create the map
m = folium.Map(
    location=[39.99, -75.13],
    tiles='Cartodb dark_matter',
    zoom_start=11
)

# Add the ZIP Codes GeoJson to the map
folium.GeoJson(
    zip_codes.to_crs(epsg=4326), # IMPORTANT: make sure CRS is lat/lng (EPSG=4326)
    name='Philadelphia ZIP_codes',
    style_function=get_zip_code_style,
    highlight_function=get_highlighted_style,
    tooltip=folium.GeoJsonTooltip(['ZIP Code'])
).add_to(m)


# Add a SECOND layer for neighborhoods
folium.GeoJson(
    hoods.to_crs(epsg=4326), # IMPORTANT: make sure CRS is lat/lng (EPSG=4326)
    name='Neighborhoods',
    style_function=get_neighborhood_style,
    highlight_function=get_highlighted_style,
    tooltip=folium.GeoJsonTooltip(['neighborhood'])
).add_to(m)


# Also add option to toggle layers
folium.LayerControl().add_to(m)

m
Out[42]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Important notes:¶

  • The data should be passed as GeoJSON rather than a GeoDataFrame — you need to call .to_json()
  • I've added a LayerControl to toggle different layers on the map
  • I've specified a tooltip using folium.GeoJsonTooltip

3.3 Plotting a choropleth map¶

Overlay GeoJSON features on an interactive map, colored by a specific data variable

At-home exercise: load data for internet availability in US counties¶

In the interest of time, I've used cenpy to download data for internet availability from the 2019 5-year ACS, but a good at-home exercise is to try to replicate my work.

  • The relevant data table is B28002: PRESENCE AND TYPES OF INTERNET SUBSCRIPTIONS IN HOUSEHOLD
  • Columns:
    • B28002_001E: the total universe of households
    • B28002_013E: households without internet
In [43]:
census_data = pd.read_csv("./data/internet_avail_census.csv", dtype={"geoid": str})
In [44]:
census_data.head()
Out[44]:
NAME universe no_internet state county geoid
0 Washington County, Mississippi 18299.0 6166.0 28 151 28151
1 Perry County, Mississippi 4563.0 1415.0 28 111 28111
2 Choctaw County, Mississippi 3164.0 1167.0 28 19 28019
3 Itawamba County, Mississippi 8706.0 1970.0 28 57 28057
4 Carroll County, Mississippi 3658.0 1218.0 28 15 28015
In [45]:
# Remove counties with no households
valid = census_data['universe'] > 0
census_data = census_data.loc[valid]

# Calculate the percent without internet
census_data['percent_no_internet'] = census_data['no_internet'] / census_data['universe']
In [46]:
census_data.head()
Out[46]:
NAME universe no_internet state county geoid percent_no_internet
0 Washington County, Mississippi 18299.0 6166.0 28 151 28151 0.336958
1 Perry County, Mississippi 4563.0 1415.0 28 111 28111 0.310103
2 Choctaw County, Mississippi 3164.0 1167.0 28 19 28019 0.368837
3 Itawamba County, Mississippi 8706.0 1970.0 28 57 28057 0.226281
4 Carroll County, Mississippi 3658.0 1218.0 28 15 28015 0.332969

Load counties from the data folder as well:

In [47]:
counties = gpd.read_file("./data/us-counties-10m.geojson")
In [48]:
counties.head()
Out[48]:
id geometry
0 53073 MULTIPOLYGON (((-120.85361 49.00011, -120.7674...
1 30105 POLYGON ((-106.11238 48.99904, -106.15187 48.8...
2 30029 POLYGON ((-114.06985 48.99904, -114.05908 48.8...
3 16021 POLYGON ((-116.04755 49.00065, -116.04755 48.5...
4 30071 POLYGON ((-107.17840 49.00011, -107.20712 48.9...

Now let's make the choropleth...¶

The easy way: use folium.Choropleth¶

  • The good:
    • Automatically generate a choropleth from a set of features and corresponding pandas DataFrame
    • Automatic creation of a legend
  • The bad:
    • no tooltip and little highlight interactivity (currently being worked on)
In [49]:
folium.Choropleth?
Init signature:
folium.Choropleth(
    geo_data,
    data=None,
    columns=None,
    key_on=None,
    bins=6,
    fill_color=None,
    nan_fill_color='black',
    fill_opacity=0.6,
    nan_fill_opacity=None,
    line_color='black',
    line_weight=1,
    line_opacity=1,
    name=None,
    legend_name='',
    overlay=True,
    control=True,
    show=True,
    topojson=None,
    smooth_factor=None,
    highlight=None,
    **kwargs,
)
Docstring:     
Apply a GeoJSON overlay to the map.

Plot a GeoJSON overlay on the base map. There is no requirement
to bind data (passing just a GeoJSON plots a single-color overlay),
but there is a data binding option to map your columnar data to
different feature objects with a color scale.

If data is passed as a Pandas DataFrame, the "columns" and "key-on"
keywords must be included, the first to indicate which DataFrame
columns to use, the second to indicate the layer in the GeoJSON
on which to key the data. The 'columns' keyword does not need to be
passed for a Pandas series.

Colors are generated from color brewer (http://colorbrewer2.org/)
sequential palettes. By default, linear binning is used between
the min and the max of the values. Custom binning can be achieved
with the `bins` parameter.

TopoJSONs can be passed as "geo_data", but the "topojson" keyword must
also be passed with the reference to the topojson objects to convert.
See the topojson.feature method in the TopoJSON API reference:
https://github.com/topojson/topojson/wiki/API-Reference


Parameters
----------
geo_data: string/object
    URL, file path, or data (json, dict, geopandas, etc) to your GeoJSON
    geometries
data: Pandas DataFrame or Series, default None
    Data to bind to the GeoJSON.
columns: dict or tuple, default None
    If the data is a Pandas DataFrame, the columns of data to be bound.
    Must pass column 1 as the key, and column 2 the values.
key_on: string, default None
    Variable in the `geo_data` GeoJSON file to bind the data to. Must
    start with 'feature' and be in JavaScript objection notation.
    Ex: 'feature.id' or 'feature.properties.statename'.
bins: int or sequence of scalars or str, default 6
    If `bins` is an int, it defines the number of equal-width
    bins between the min and the max of the values.
    If `bins` is a sequence, it directly defines the bin edges.
    For more information on this parameter, have a look at
    numpy.histogram function.
fill_color: string, optional
    Area fill color, defaults to blue. Can pass a hex code, color name,
    or if you are binding data, one of the following color brewer palettes:
    'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
    'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
nan_fill_color: string, default 'black'
    Area fill color for nan or missing values.
    Can pass a hex code, color name.
fill_opacity: float, default 0.6
    Area fill opacity, range 0-1.
nan_fill_opacity: float, default fill_opacity
    Area fill opacity for nan or missing values, range 0-1.
line_color: string, default 'black'
    GeoJSON geopath line color.
line_weight: int, default 1
    GeoJSON geopath line weight.
line_opacity: float, default 1
    GeoJSON geopath line opacity, range 0-1.
legend_name: string, default empty string
    Title for data legend.
topojson: string, default None
    If using a TopoJSON, passing "objects.yourfeature" to the topojson
    keyword argument will enable conversion to GeoJSON.
smooth_factor: float, default None
    How much to simplify the polyline on each zoom level. More means
    better performance and smoother look, and less means more accurate
    representation. Leaflet defaults to 1.0.
highlight: boolean, default False
    Enable highlight functionality when hovering over a GeoJSON area.
name : string, optional
    The name of the layer, as it will appear in LayerControls
overlay : bool, default True
    Adds the layer as an optional overlay (True) or the base layer (False).
control : bool, default True
    Whether the Layer will be included in LayerControls.
show: bool, default True
    Whether the layer will be shown on opening (only for overlays).

Returns
-------
GeoJSON data layer in obj.template_vars

Examples
--------
>>> Choropleth(geo_data='us-states.json', line_color='blue',
...            line_weight=3)
>>> Choropleth(geo_data='geo.json', data=df,
...            columns=['Data 1', 'Data 2'],
...            key_on='feature.properties.myvalue',
...            fill_color='PuBu',
...            bins=[0, 20, 30, 40, 50, 60])
>>> Choropleth(geo_data='countries.json',
...            topojson='objects.countries')
>>> Choropleth(geo_data='geo.json', data=df,
...            columns=['Data 1', 'Data 2'],
...            key_on='feature.properties.myvalue',
...            fill_color='PuBu',
...            bins=[0, 20, 30, 40, 50, 60],
...            highlight=True)
File:           ~/mambaforge/envs/musa-550-fall-2022/lib/python3.9/site-packages/folium/features.py
Type:           type
Subclasses:     

Steps:

  1. Pass the geometry data (counties) as GeoJSON in lat/lng CRS
  2. Pass in the census data separately
  3. Pass in the column that we match the geometries on (key_on=), using the GeoJSON "features.properties." syntax
  4. Pass the data key (column to match the data on) and the data value (the column to color the geometries by) via the columns= keyword
In [50]:
m = folium.Map(location=[40, -98], zoom_start=4)

# Convert the counties geometries into GeoJSON
counties_geojson = counties.to_crs(epsg=4326)

folium.Choropleth(
    geo_data=counties_geojson, # Pass in GeoJSON data for counties
    data=census_data, # the census data
    columns=["geoid", 'percent_no_internet'], # First column must be the key, second the values
    key_on="feature.properties.id", # Key to match on in the geometries --> Remember to prepend "feature.properties"
    fill_color='RdPu', # any ColorBrewer name will work here
    fill_opacity=0.7,
    line_opacity=1,
    line_weight=0.5,
    legend_name='Households without Internet (%)',
    name='choropleth',
).add_to(m)


m
Out[50]:
Make this Notebook Trusted to load map: File -> Trust Notebook

The hard way: use folium.GeoJson¶

  • The good:
    • More customizable, and can add user interaction
  • The bad:
    • Requires more work
    • No way to add a legend, see this open issue on GitHub

The steps involved¶

  1. Join data and geometry features into a single GeoDataFrame
  2. Define a function to style features based on data values
  3. Create GeoJSON layer and add it to the map

Step 1: Join the census data and features¶

Note: this is different than using folium.Choropleth, where data and features are stored in two separate data frames.

In [51]:
# Merge the county geometries with census data
# Left column: "id"
# Right column: "geoid"
census_joined = counties.merge(census_data, left_on="id", right_on="geoid")
In [52]:
census_joined.head()
Out[52]:
id geometry NAME universe no_internet state county geoid percent_no_internet
0 53073 MULTIPOLYGON (((-120.85361 49.00011, -120.7674... Whatcom County, Washington 85008.0 9189.0 53 73 53073 0.108096
1 30105 POLYGON ((-106.11238 48.99904, -106.15187 48.8... Valley County, Montana 3436.0 672.0 30 105 30105 0.195576
2 30029 POLYGON ((-114.06985 48.99904, -114.05908 48.8... Flathead County, Montana 38252.0 5662.0 30 29 30029 0.148018
3 16021 POLYGON ((-116.04755 49.00065, -116.04755 48.5... Boundary County, Idaho 4605.0 1004.0 16 21 16021 0.218024
4 30071 POLYGON ((-107.17840 49.00011, -107.20712 48.9... Phillips County, Montana 1770.0 484.0 30 71 30071 0.273446

Step 2: Normalize the data column to 0 to 1¶

  • We will use a matplotlib color map that requires data to be between 0 and 1
  • Normalize our "percent_no_internet" column to be between 0 and 1
In [53]:
# Minimum
min_val = census_joined['percent_no_internet'].min()

# Maximum
max_val = census_joined['percent_no_internet'].max()

# Calculate a normalized column
normalized = (census_joined['percent_no_internet'] - min_val) / (max_val - min_val)

# Add to the dataframe
census_joined['percent_no_internet_normalized'] = normalized

Step 3: Define our style functions¶

  • Create a matplotlib colormap object using plt.get_cmap()
  • Color map objects are functions: give the function a number between 0 and 1 and it will return a corresponding color from the color map
  • Based on the feature data, evaluate the color map and convert to a hex string
In [54]:
import matplotlib.colors as mcolors
In [55]:
# Use a red-purple colorbrewer color scheme
cmap = plt.get_cmap('RdPu')
In [56]:
# The minimum value of the color map as an RGB tuple
cmap(0)
Out[56]:
(1.0, 0.9686274509803922, 0.9529411764705882, 1.0)
In [57]:
# The minimum value of the color map as a hex string
mcolors.rgb2hex(cmap(0.0))
Out[57]:
'#fff7f3'
In [58]:
# The maximum value of the color map as a hex string
mcolors.rgb2hex(cmap(1.0))
Out[58]:
'#49006a'
In [59]:
def get_style(feature):
    """
    Given an input GeoJSON feature, return a style dict.
    
    Notes
    -----
    The color in the style dict is determined by the 
    "percent_no_internet_normalized" column in the 
    input "feature".
    """
    # Get the data value from the feature
    value = feature['properties']['percent_no_internet_normalized']
    
    # Evaluate the color map
    # NOTE: value must between 0 and 1
    rgb_color = cmap(value) # this is an RGB tuple
    
    # Convert to hex string
    color = mcolors.rgb2hex(rgb_color)
    
    # Return the style dictionary
    return {'weight': 0.5, 'color': color, 'fillColor': color, "fillOpacity": 0.75}
In [60]:
def get_highlighted_style(feature):
    """
    Return a style dict to use when the user highlights a 
    feature with the mouse.
    """
    
    return {"weight": 3, "color": "black"}

Step 4: Convert our data to GeoJSON¶

  • Tip: To limit the amount of data Folium has to process, it's best to trim our GeoDataFrame to only the columns we'll need before converting to GeoJSON
  • You can use the .to_json() function to convert to a GeoJSON string
In [61]:
needed_cols = ['NAME', 'percent_no_internet', 'percent_no_internet_normalized', 'geometry']
census_json = census_joined[needed_cols]
In [62]:
# STEP 1: Initialize the map
m = folium.Map(location=[40, -98], zoom_start=4)

# STEP 2: Add the GeoJson to the map
folium.GeoJson(
    census_json, # The geometry + data columns in GeoJSON format
    style_function=get_style, # The style function to color counties differently
    highlight_function=get_highlighted_style, 
    tooltip=folium.GeoJsonTooltip(['NAME', 'percent_no_internet'])
).add_to(m)



# avoid a rendering bug by saving as HTML and re-loading
m.save('percent_no_internet.html')

And viola!¶

The hard way is harder, but we have a tooltip and highlight interactivity!

In [63]:
from IPython.display import IFrame
In [64]:
IFrame('percent_no_internet.html', width=800, height=500)
Out[64]:

That's it!¶

See you on Monday for clustering!

In [ ]: