In this post we are simply going to retrieve the restaurants from the city of Lyon-France from Open Street Map, and then plot them with Bokeh.
Downloading the restaurants name and coordinates is done using a fork of the great OSMnx library. The OSM-POI feature of this fork will probably soon be added to OSMnx from what I understand (issue).
First we create a fresh conda env, install jupyterlab, bokeh (the following lines show the Linux way to do it but a similar thing could be done with Windows):
$ conda create -n restaurants python=3.6
$ source activate restaurants
$ conda install jupyterlab
$ conda install -c bokeh bokeh
$ jupyter labextension install jupyterlab_bokeh
$ jupyter lab osm_restaurants.ipynb
The jupyterlab extension allows the rendering of JS Bokeh content.
Then we need to install the POI fork of OSMnx:
$ git clone Cette adresse e-mail est protégée contre les robots spammeurs. Vous devez activer le JavaScript pour la visualiser.:HTenkanen/osmnx.git
$ cd osmnx/
osmnx $ git checkout 1-osm-poi-dev
osmnx $ pip install .
osmnx $ cd ..
And we are ready to run the notebook:
jupyter lab osm_restaurants.ipynb
Download OSM restaurants¶
import osmnx as ox
place = "Lyon, France"
restaurant_amenities = ['restaurant', 'cafe', 'fast_food']
restaurants = ox.pois_from_place(place=place,
amenities=restaurant_amenities)[['geometry',
'name',
'amenity',
'cuisine',
'element_type']]
We are looking for 3 kinds of amenity related to food: restaurants, cafés and fast-foods. The collected data is returned as a geodataframe, which is basically a Pandas dataframe associated with a geoserie of Shapely geometries. Along with the geometry, we are only keeping 4 columns:
- restaurant name,
- amenity type (restaurant, café or fast_food),
- cuisine type and
- element_type (OSM types: node, way relation).
restaurants.head()
ax = restaurants.plot()
Update POI's geometry type¶
A large majority of the restaurants corresponds to OSM nodes that are translated to shapely Points:
restaurants['element_type'].value_counts()
restaurants[restaurants.element_type=='node']['geometry'].map(type).head()
However, OSM ways appear as shapely polygons:
restaurants[restaurants.element_type=='way']['geometry'].iat[0]
restaurants[restaurants.element_type=='way']['geometry'].map(type).head()
While OSM relation appears as shapely multipolygons:
restaurants[restaurants.element_type=='relation']['geometry'].iat[0]
restaurants[restaurants.element_type=='relation']['geometry'].map(type).head()
Using the shapely centroid method, we are going to get a point geometry for all polygon or multipolygon entries:
from shapely.geometry import Polygon, MultiPolygon
for item in ['way', 'relation']:
restaurants.loc[restaurants.element_type==item, 'geometry'] = \
restaurants[restaurants.element_type==item]['geometry'].map(lambda x: x.centroid)
restaurants['geometry_type'] = restaurants.geometry.map(type)
restaurants['geometry_type'].value_counts()
Plot the POIs with Bokeh¶
All restaurants may not have a name. Since we are going to plot all restaurants along with their name, we replace missing names by empty strings (we actually do the same for the cuisine type):
restaurants.name.fillna('', inplace=True)
restaurants.cuisine.fillna('?', inplace=True)
Also, we reproject the geometric data to WGS 84 / Pseudo-Mercator in order to be compatible with tile providers:
restaurants = restaurants.to_crs(epsg=3857)
And we are now ready to plot the restaurants on a map.
from bokeh.io import output_notebook
from bokeh.plotting import figure, show
from bokeh.models import (
ColumnDataSource,
HoverTool,
WheelZoomTool)
from bokeh.tile_providers import CARTODBPOSITRON_RETINA
source = ColumnDataSource(data=dict(
x=restaurants.geometry.x,
y=restaurants.geometry.y,
name=restaurants.name.values,
cuisine=restaurants.cuisine.values))
TOOLS = "pan,wheel_zoom,hover,reset"
p = figure(title="Lyon OSM restaurants", tools=TOOLS,
match_aspect=True, x_axis_location=None, y_axis_location=None,
active_scroll='wheel_zoom')
p.grid.grid_line_color = None
p.add_tile(CARTODBPOSITRON_RETINA)
p.circle(x='x', y='y', source=source, fill_alpha=0.6, size=6)
hover = p.select_one(HoverTool)
hover.point_policy = "snap_to_data"
hover.tooltips = [
("Name", "@name"),
("Cuisine", "@cuisine"),
]
output_notebook()
show(p)