London Road Safety


(Link to open example in new tab):

Source Code (Link to Github):

# -*- coding: utf-8 -*-

import csv

from visigoth import Diagram
from visigoth.containers import Map, Box
from visigoth.map_layers import WMS, KDE, Geoplot
from visigoth.map_layers.geoplot import Multipoint
from visigoth.charts import Area
from visigoth.common import Text, LayerController, DiscreteHueLegend
from visigoth.utils import DiscreteHueManager, ContinuousHueManager
from visigoth.utils.mapping import Geocoder, Mapping, Projections

# get the boundaries of a region approximately 20 km radius around Central London

gc = Geocoder()
center = gc.fetchCenter("London")
bounds = Mapping.computeBoundaries(center,20000)

lon_min = bounds[0][0]
lat_min = bounds[0][1]
lon_max = bounds[1][0]
lat_max = bounds[1][1]

data = []
freqs_by_hour = {h:0 for h in range(24)}

headers = {}

# load the data from CSV
rdr = csv.reader(open("dftRoadSafetyData_Accidents_2018.csv","r"))

for row in rdr:
    if not headers:
        for idx in range(len(row)):
            headers[row[idx]] = idx
            lat = float(row[headers["Latitude"]])
            lon = float(row[headers["Longitude"]])
            hour = int(row[headers["Time"]][:2])
            continue # some records do not have an hour, lat and lon
        severity = int(row[headers["Accident_Severity"]])

        # ignore accidents that occur outside the london area or are not serious
        if lon >= lon_min and lon <= lon_max \
                and lat >= lat_min and lat <= lat_max \
                and (severity == 1 or severity == 2):
            category = "fatal"
            if severity == 2:
                category = "serious"
            freqs_by_hour[hour] = 1+freqs_by_hour[hour]

# compute some summary stats - totals for each type of accident
total_fatal = len([1 for (_,_,cat) in data if cat == "fatal" ])
total_serious = len([1 for (_,_,cat) in data if cat == "serious" ])

d = Diagram(fill="white")

# create the map and and an open street map base layer
m = Map(768,bounds,projection=Projections.EPSG_3857,zoom_to=2)
wms = WMS("osm")

# define a hue_manager for the heatmap ranging from white to blue to red
hue_manager = ContinuousHueManager(hueMap=[(1,1,1),(0,0,1),(1,0,0)])

# define the heatmap
heatmap = KDE([(lon,lat) for (lon,lat,_) in data],bandwidth=300,nr_samples_across=100,hue_manager=hue_manager,label_fn=None)

# define a layer plotting a point at the location of each accident
mp1 = Multipoint([(lon,lat) for (lon,lat,cat) in data if cat == "fatal"],fill="red",marker=False,radius=8)
mp2 = Multipoint([(lon,lat) for (lon,lat,cat) in data if cat == "serious"],fill="grey",marker=False,radius=3)
gp = Geoplot(multipoints=[mp1,mp2])
gp.setInfo("UK Department for Transport","","UK Department for Transport","")
gp.setVisible(False) # hide this layer by default

# add area chart showing the distribution of all accidents
area_hue_manager = DiscreteHueManager()
area_data = []
for hour in freqs_by_hour:
area_chart = Area(area_data, x=0, y=1, hue=None, width=512, height=512, hue_manager=area_hue_manager)
(ax,ay) = area_chart.getAxes()
ax.setLabel("Time Of Day (Hour)")
ay.setLabel("Accident Frequency/Hour")

# lay out the diagram, starting with a title
d.add(Text("London Area Serious and Fatal Road Accidents 2018"))

# define a hue_manager for the accident site plot with totals
discrete_hue_manager = DiscreteHueManager()
discrete_hue_manager.addHue("%d Serious Non-Fatal Accidents"%(total_serious), "grey").addHue("%d Fatal Accidents"%(total_fatal), "red")

# add the legend and map
d.add(DiscreteHueLegend(discrete_hue_manager,width=700, legend_columns=2, font_height=18))

# add a layer manager to allow layers to be turned on and off
mlm = LayerController([{"layer":gp,"label":"Accident Locations"},{"layer":heatmap,"label":"Accident Heatmap"}],title="Select Layer(s)",height=150)
d.add(Text("Serious and Fatal Accidents By Time Of Day"))

html = d.draw(format="html")

f = open("example.html", "w")