Creating interactive maps with Bokeh


In [13]:
from bokeh.io import show, push_notebook, output_notebook
from bokeh.palettes import YlOrRd8 as palette  #Spectral6 as palette
from bokeh.palettes import brewer
from bokeh.plotting import figure, save
from bokeh.models import (
    ColumnDataSource,
    HoverTool,
    LogColorMapper
)
import gdal 
import geopandas as gpd
import pysal as ps
import numpy as np

# Plot the data using Jupyter Notebook - Initialize
output_notebook()

# Read the data with Geopandas
fp = r"D:\KOODIT\Opetus\Automating-GIS-processes\AutoGIS-Sphinx\data\TravelTimes_to_5975375_RailwayStation.shp"
fp = r"C:\HY-Data\HENTENKA\KOODIT\Opetus\Automating-GIS-processes\AutoGIS-Sphinx\data\TravelTimes_to_5975375_RailwayStation.shp"
data = gpd.read_file(fp)

# Get crs
CRS = data.crs

# Read Roads 
roads_fp = r"D:\KOODIT\Opetus\Automating-GIS-processes\AutoGIS-Sphinx\data\Helsinki_map_layers\pääväylät.shp"
roads_fp = r"C:\HY-Data\HENTENKA\KOODIT\Opetus\Automating-GIS-processes\AutoGIS-Sphinx\data\Helsinki_map_layers\pääväylät.shp"

roads = gpd.read_file(roads_fp)

# Ensure that geometries are in same CRS
roads['geometry'] = roads['geometry'].to_crs(crs=CRS)


Loading BokehJS ...

In [14]:
data.head()


Out[14]:
car_m_d car_m_t car_r_d car_r_t from_id geometry pt_m_d pt_m_t pt_m_tt pt_r_d pt_r_t pt_r_tt to_id walk_d walk_t
0 32297 43 32260 48 5785640 POLYGON ((382000.0001358641 6697750.000038058,... 32616 116 147 32616 108 139 5975375 32164 459
1 32508 43 32471 49 5785641 POLYGON ((382250.0001358146 6697750.000038053,... 32822 119 145 32822 111 133 5975375 29547 422
2 30133 50 31872 56 5785642 POLYGON ((382500.0001357661 6697750.000038046,... 32940 121 146 32940 113 133 5975375 29626 423
3 32690 54 34429 60 5785643 POLYGON ((382750.0001357181 6697750.000038039,... 33233 125 150 33233 117 144 5975375 29919 427
4 31872 42 31834 48 5787544 POLYGON ((381250.0001360176 6697500.000038121,... 32127 109 126 32127 101 121 5975375 31674 452

In [15]:
def getCoords(geom, coord_type):
    """Calculates coordinates ('x' or 'y') of a LinearRing geometry"""
    if coord_type == 'x':
        return geom.coords.xy[0]
    elif coord_type == 'y':
        return geom.coords.xy[1]
    
def getCoords(geom, coord_type):
    if geom.geom_type.startswith("Multi"):
        for i, part in enumerate(geom):
            if coord_type == 'x':
                if i == 0:
                    coord_arrays = part.coords.xy[0]
                else:
                    coord_arrays = np.concatenate([coord_arrays, part.coords.xy[0]])
            elif coord_type == 'y':
                if i == 0:
                    coord_arrays = part.coords.xy[1]
                else:
                    coord_arrays = np.concatenate([coord_arrays, part.coords.xy[1]])
        return coord_arrays
    else:
        if coord_type == 'x':
            return geom.coords.xy[0]
        elif coord_type == 'y':
            return geom.coords.xy[1]

def getLineCoords(row, geom_col, x_col, y_col):
    row[x_col] = list(getCoords(row[geom_col], coord_type='x'))
    row[y_col] = list(getCoords(row[geom_col], coord_type='y'))
    return row
    
def getPolyCoords(row, geom_col, x_col, y_col):
    """Calculates coordinates ('x' or 'y') of a Polygon and updates the values into 'x_col' and 'y_col' that user defines."""
    # Parse the exterior of the coordinate
    exterior = row[geom_col].exterior
    # Get the coordinates of the exterior
    x = getCoords(exterior, coord_type='x')
    y = getCoords(exterior, coord_type='y')
    # Update the row with a list of Polygon coordinate list (remember Polygon can have holes, thus coords are within nested list) 
    row[x_col] = list(x)
    row[y_col] = list(y)
    # Return the result
    return row

# Calculate the x and y coordinates
data = data.apply(getPolyCoords, geom_col='geometry', x_col='x', y_col='y', axis=1)

# Calculate the x and y values of road geometries
roads = roads.apply(getLineCoords, geom_col='geometry', x_col='x', y_col='y', axis=1)

# Replace No Data values (-1) with large number (999)
data = data.replace(-1, 999)

# Classify our travel times into 5 minute classes until 200 minutes
# Create a list of values where minumum value is 5, maximum value is 200 and step is 5.
breaks = [x for x in range(5, 200, 5)]

# Create names for the legend (until 60 minutes)
names = ["%s-%s " % (x-5, x) for x in range(5, 60, 5)]
# Add legend for over 60
names.append("60 <")

classifier = ps.User_Defined.make(bins=breaks)
pt_classif = data[['pt_r_tt']].apply(classifier)
car_classif = data[['car_r_t']].apply(classifier)

# Rename columns
pt_classif.columns = ['pt_r_tt_ud']
car_classif.columns = ['car_r_t_ud']

# Join back to main data
data = data.join(pt_classif)
data = data.join(car_classif)

# Assign legend names for the classes
data['label_pt'] = None
data['label_car'] = None

for i in range(len(names)):
    # Update rows where class is i
    data.loc[data['pt_r_tt_ud'] == i, 'label_pt'] = names[i]
    data.loc[data['car_r_t_ud'] == i, 'label_car'] = names[i]

# Update all cells that didn't get any value with "60 <"
data['label_pt'] = data['label_pt'].fillna("60 <")
data['label_car'] = data['label_car'].fillna("60 <")

In [16]:
data.head()


Out[16]:
car_m_d car_m_t car_r_d car_r_t from_id geometry pt_m_d pt_m_t pt_m_tt pt_r_d ... pt_r_tt to_id walk_d walk_t x y pt_r_tt_ud car_r_t_ud label_pt label_car
0 32297 43 32260 48 5785640 POLYGON ((382000.0001358641 6697750.000038058,... 32616 116 147 32616 ... 139 5975375 32164 459 [382000.00013586413, 381750.0001359122, 381750... [6697750.000038058, 6697750.000038066, 6698000... 27 9 60 < 45-50
1 32508 43 32471 49 5785641 POLYGON ((382250.0001358146 6697750.000038053,... 32822 119 145 32822 ... 133 5975375 29547 422 [382250.0001358146, 382000.00013586413, 382000... [6697750.000038053, 6697750.000038058, 6698000... 26 9 60 < 45-50
2 30133 50 31872 56 5785642 POLYGON ((382500.0001357661 6697750.000038046,... 32940 121 146 32940 ... 133 5975375 29626 423 [382500.0001357661, 382250.0001358146, 382250.... [6697750.000038046, 6697750.000038053, 6698000... 26 11 60 < 60 <
3 32690 54 34429 60 5785643 POLYGON ((382750.0001357181 6697750.000038039,... 33233 125 150 33233 ... 144 5975375 29919 427 [382750.0001357181, 382500.0001357661, 382500.... [6697750.000038039, 6697750.000038046, 6698000... 28 11 60 < 60 <
4 31872 42 31834 48 5787544 POLYGON ((381250.0001360176 6697500.000038121,... 32127 109 126 32127 ... 121 5975375 31674 452 [381250.0001360176, 381000.00013606605, 381000... [6697500.000038121, 6697500.0000381265, 669775... 24 9 60 < 45-50

5 rows × 21 columns


In [19]:
from bokeh.palettes import RdYlBu11 as palette  #Spectral6 as palette
# See reference http://bokeh.pydata.org/en/latest/docs/reference/palettes.html 

# Exclude Geometry column because it cannot be inserted as a bokeh datasource
df = data.drop('geometry', axis=1)

# Exclude geometry from roads as well
rdf = roads.drop('geometry', axis=1)

# Get Data sources
dfsource = ColumnDataSource(data=df)
rdfsource = ColumnDataSource(data=rdf)

TOOLS = "pan,wheel_zoom,box_zoom,reset,save"

# Reverse the colors in color palette
#palette.reverse()

color_mapper = LogColorMapper(palette=palette)

p = figure(title="Travel times with Public transportation to Central Railway station", tools=TOOLS, plot_width=850) # plot_height=650, 

# Do not add grid line 
p.grid.grid_line_color = None

# Plot grid
grid = p.patches('x', 'y', source=dfsource, name="grid",
         fill_color={'field': 'pt_r_tt_ud', 'transform': color_mapper},
         fill_alpha=0.7, line_color="black", line_width=0.05, legend="label_pt")

# Add roads
r = p.multi_line('x', 'y', source=rdfsource, color="grey")

# Modify legend location
p.legend.location = "top_left"
p.legend.orientation = "vertical"

# Insert a circle on top of the Central Railway Station (coords in EurefFIN-TM35FIN)
station_x = 385752.214
station_y =  6672143.803
circle = p.circle(x=[station_x], y=[station_y], name="point", size=6, color="yellow")

# Configure hover tooltips
#hover_columns = ["pt_r_tt", "car_r_t"]
#hover_tootips = [(c, '@' + c) for c in hover_columns] 

phover = HoverTool(names=["point"], renderers=[circle])
phover.tooltips=[("Destination", "Railway Station")]

ghover = HoverTool(renderers=[grid])
ghover.tooltips=[("YKR-ID", "@from_id"),
                ("PT time", "@pt_r_tt"),
                ("Car time", "@car_r_t"),
               ]

p.add_tools(ghover)
p.add_tools(phover)

show(p)



In [18]:
import bokeh
bokeh.__version__


Out[18]:
'0.12.3'