Wednesday, March 27, 2024

City Accessibility — Tips on how to Attain Defibrillators on Time | by Milan Janosov | Oct, 2023

Must read

Picture by the creator.

On this piece, I mix earlier work on city accessibility or walkability with open-source knowledge on the situation of public defibrillator gadgets. Moreover, I incorporate world inhabitants knowledge and Uber’s H3 grid system to estimate the share of the inhabitants inside cheap attain to any system inside Budapest and Vienna.

11 min learn

12 hours in the past

The basis of city accessibility, or walkability, lies in a graph-based computation measuring the Euclidean distance (remodeling it into strolling minutes, assuming fixed velocity and no site visitors jams and obstacles). The outcomes of such analyses can inform us how straightforward it’s to succeed in particular kinds of facilities from each single location throughout the metropolis. To be extra exact, from each single node throughout the metropolis’s street community, however as a consequence of numerous street crossings, this approximation is usually negligible.

On this present case research, I concentrate on one explicit kind of Level of Curiosity (POI): the situation of defibrillator gadgets. Whereas the Austrian Authorities’s Open Information Portal shares official information on this, in Hungary, I may solely get hold of a less-then-half protection crowd-sourced knowledge set — which, hopefully, will later develop each in absolute measurement and knowledge protection.

Within the first part of my article, I’ll create the accessibility map for every metropolis, visualizing the time wanted to succeed in the closest defibrillator models inside a variety of two.5km at a operating velocity of 15km/h. Then, I’ll break up the cities into hexagon grids utilizing Uber’s H3 library to compute the typical defibrillator-accessibility time for every grid cell. I additionally estimate the inhabitants stage at every hexagon cell following my earlier article. Lastly, I mix these and compute the fraction of the inhabitants reachable as a operate of reachability (operating) time.

As a disclaimer, I wish to emphasize that I’m not a educated medical knowledgeable by any means — and I don’t intend to take a stand on the significance of defibrillator gadgets in comparison with different technique of life assist. Nonetheless, constructing on frequent sense and concrete planning rules, I assume that the simpler it’s to succeed in such gadgets, the higher.

As at all times, I like to begin by exploring the info sorts I exploit. First, I’ll acquire the executive boundaries of the cities I research in — Budapest, Hungary, and Vienna, Austria.

Then, constructing on a earlier article of mine on learn how to course of rasterized inhabitants knowledge, I add city-level inhabitants data from the WorldPop hub. Lastly, I incorporate official governmental knowledge on defibrillator gadgets in Vienna and my very own web-scraped model of the identical, although crowded sources and intrinsically incomplete, for Budapest.

1.1. Administrative boundaries

First, I question the admin boundaries of Budapest and Vienna from OpenStreetMap utilizing the OSMNx library:

import osmnx as ox # model: 1.0.1
import matplotlib.pyplot as plt # model: 3.7.1

admin = {}
cities = ['Budapest', 'Vienna']
f, ax = plt.subplots(1,2, figsize = (15,5))

# visualize the admin boundaries
for idx, metropolis in enumerate(cities):
admin[city] = ox.geocode_to_gdf(metropolis)
admin[city].plot(ax=ax[idx],coloration='none',edgecolor= 'ok', linewidth = 2) ax[idx].set_title(metropolis, fontsize = 16)

The results of this code block:

Determine 1. The admin boundaries of Budapest and Vienna. Picture by the creator.

1.2. Inhabitants knowledge

Second, following the steps on this article, I created the inhabitants grid in vector knowledge format for each cities, constructing on the WorldPop on-line Demographic Database. With out repeating the steps, I simply learn within the output recordsdata of that course of containing inhabitants data for these cities.

Additionally, to make issues look good, I created a colormap from the colour of 2022, Very Peri, utilizing Matplotlib and a fast script from ChatGPT.

import matplotlib.pyplot as plt
from matplotlib.colours import LinearSegmentedColormap

very_peri = '#8C6BF3'
second_color = '#6BAB55'

colours = [second_color, very_peri ]
n_bins = 100
cmap_name = "VeryPeri"
colormap = LinearSegmentedColormap.from_list(cmap_name, colours, N=n_bins)

import geopandas as gpd # model: 0.9.0

demographics = {}
f, ax = plt.subplots(1,2, figsize = (15,5))

for idx, metropolis in enumerate(cities):
demographics[city] = gpd.read_file(metropolis.decrease() +
'_population_grid.geojson')[['population', 'geometry']]
admin[city].plot(ax=ax[idx], coloration = 'none', edgecolor = 'ok',
linewidth = 3)
demographics[city].plot(column = 'inhabitants', cmap = colormap,
ax=ax[idx], alpha = 0.9, markersize = 0.25)
ax[idx].set_title('Inhabitants densityn in ' + metropolis, fontsize = 16)

The results of this code block:

Determine 2. Inhabitants density maps based mostly on WordPop knowledge. Picture by the creator.

1.3. Defibrillator places

Third, I collected locational knowledge on the obtainable defibrillators in each cities.

For Vienna, I downloaded this knowledge set from the official open knowledge portal of the Austrian authorities containing the purpose location of 1044 models:

Whereas such an official open knowledge portal doesn’t exist in Budapest/Hungary, the Hungarian Nationwide Coronary heart Basis runs a crowd-sourced web site the place operators can replace the situation of their defibrillator models. Their country-wide database consists of 677 models; nevertheless, their disclaimer says they find out about a minimum of one thousand models working within the nation — and are ready for his or her house owners to add them. With a easy internet crawler, I downloaded the situation of every of the 677 registered models and filtered the info set all the way down to these in Budapest, leading to a set of 148 models.

# parse the info for every metropolis
gdf_units= {}

gdf_units['Vienna'] = gpd.read_file('DEFIBRILLATOROGD')
gdf_units['Budapest'] = gpd.read_file('budapest_defibrillator.geojson')

for metropolis in cities:
gdf_units[city] = gpd.overlay(gdf_units[city], admin[city])

# visualize the models
f, ax = plt.subplots(1,2, figsize = (15,5))

for idx, metropolis in enumerate(cities):
admin[city].plot(ax=ax[idx],coloration='none',edgecolor= 'ok', linewidth = 3)
gdf_units[city].plot( ax=ax[idx], alpha = 0.9, coloration = very_peri,
markersize = 6.0)
ax[idx].set_title('Places of defibrillatorndevices in ' + metropolis,
fontsize = 16)

The results of this code block:

Determine 3. Defibrillator places in Budapest and Vienna. Picture by the creator.

Subsequent, I wrapped up this nice article written by Nick Jones in 2018 on learn how to compute pedestrian accessibility:

import os
import pandana # model: 0.6
import pandas as pd # model: 1.4.2
import numpy as np # model: 1.22.4
from shapely.geometry import Level # model: 1.7.1
from pandana.loaders import osm

def get_city_accessibility(admin, POIs):

# walkability parameters
walkingspeed_kmh = 15
walkingspeed_mm = walkingspeed_kmh * 1000 / 60
distance = 2500

# bounding field as a listing of llcrnrlat, llcrnrlng, urcrnrlat, urcrnrlng
minx, miny, maxx, maxy = admin.bounds.T[0].to_list()
bbox = [miny, minx, maxy, maxx]

# setting the enter params, going for the closest POI
num_pois = 1
num_categories = 1
bbox_string = '_'.be part of([str(x) for x in bbox])
net_filename = 'knowledge/network_{}.h5'.format(bbox_string)
if not os.path.exists('knowledge'): os.makedirs('knowledge')

# precomputing nework distances

if os.path.isfile(net_filename):
# if a avenue community file already exists, simply load the dataset from that
community =
methodology = 'loaded from HDF5'
# in any other case, question the OSM API for the road community throughout the specified bounding field
community = osm.pdna_network_from_bbox(bbox[0], bbox[1], bbox[2], bbox[3])
methodology = 'downloaded from OSM'

# determine nodes which can be linked to fewer than some threshold of different nodes inside a given distance
lcn = community.low_connectivity_nodes(impedance=1000, depend=10, imp_name='distance')
community.save_hdf5(net_filename, rm_nodes=lcn) #take away low-connectivity nodes and save to h5

# precomputes the vary queries (the reachable nodes inside this most distance)
# so, so long as you utilize a smaller distance, cached outcomes might be used
community.precompute(distance + 1)

# compute accessibilities on POIs
pois = POIs.copy()
pois['lon'] = pois.geometry.apply(lambda g: g.x)
pois['lat'] = pois.geometry.apply(lambda g: g.y)
pois = pois.drop(columns = ['geometry'])
community.init_pois(num_categories=num_categories, max_dist=distance, max_pois=num_pois)

community.set_pois(class='all', x_col=pois['lon'], y_col=pois['lat'])

# searches for the n nearest facilities (of every type) to every node within the community
all_access = community.nearest_pois(distance=distance, class='all', num_pois=num_pois)

# remodel the outcomes right into a geodataframe
nodes = community.nodes_df
nodes_acc = nodes.merge(all_access[[1]], left_index = True, right_index = True).rename(columns = {1 : 'distance'})
nodes_acc['time'] = nodes_acc.distance / walkingspeed_mm
xs = record(nodes_acc.x)
ys = record(nodes_acc.y)
nodes_acc['geometry'] = [Point(xs[i], ys[i]) for i in vary(len(xs))]
nodes_acc = gpd.GeoDataFrame(nodes_acc)
nodes_acc = gpd.overlay(nodes_acc, admin)

nodes_acc[['time', 'geometry']].to_file(metropolis + '_accessibility.geojson', driver = 'GeoJSON')

return nodes_acc[['time', 'geometry']]

accessibilities = {}
for metropolis in cities:
accessibilities[city] = get_city_accessibility(admin[city], gdf_units[city])

for metropolis in cities:
print('Variety of street community nodes in ' +
metropolis + ': ' + str(len(accessibilities[city])))

This code block outputs the variety of street community nodes in Budapest (116,056) and in Vienna (148,212).

Now visualize the accessibility maps:

for metropolis in cities:
f, ax = plt.subplots(1,1,figsize=(15,8))
admin[city].plot(ax=ax, coloration = 'ok', edgecolor = 'ok', linewidth = 3)
accessibilities[city].plot(column = 'time', cmap = 'RdYlGn_r',
legend = True, ax = ax, markersize = 2, alpha = 0.5)
ax.set_title('Defibrillator accessibility in minutesn' + metropolis,
pad = 40, fontsize = 24)

This code block outputs the next figures:

Determine 4. Defibrillator accessibility in minutes in Budapest. Picture by the creator.
Determine 5. Defibrillator accessibility in minutes in Vienna. Picture by the creator.

At this level, I’ve each the inhabitants and the accessibility knowledge; I simply should convey them collectively. The one trick is that their spatial models differ:

  • Accessibility is measured and connected to every node throughout the street community of every metropolis
  • Inhabitants knowledge is derived from a raster grid, now described by the POI of every raster grid’s centroid

Whereas rehabilitating the unique raster grid could also be an possibility, within the hope of a extra pronounced universality (and including a little bit of my private style), I now map these two kinds of level knowledge units into the H3 grid system of Uber for individuals who haven’t used it earlier than, for now, its sufficient to know that it’s a chic, environment friendly spacial indexing system utilizing hexagon tiles. And for extra studying, hit this hyperlink!

3.1. Creating H3 cells

First, put collectively a operate that splits a metropolis into hexagons at any given decision:

import geopandas as gpd
import h3 # model: 3.7.3
from shapely.geometry import Polygon # model: 1.7.1
import numpy as np

def split_admin_boundary_to_hexagons(admin_gdf, decision):
coords = record(admin_gdf.geometry.to_list()[0].exterior.coords)
admin_geojson = {"kind": "Polygon", "coordinates": [coords]}
hexagons = h3.polyfill(admin_geojson, decision,
hexagon_geometries = {hex_id : Polygon(h3.h3_to_geo_boundary(hex_id,
geo_json=True)) for hex_id in hexagons}
return gpd.GeoDataFrame(hexagon_geometries.gadgets(), columns = ['hex_id', 'geometry'])

decision = 8
hexagons_gdf = split_admin_boundary_to_hexagons(admin[city], decision)

The results of this code block:

Determine 6. The H3 hexagon break up of Vienna on the decision of 8. Picture by the creator.

Now, see a number of completely different resolutions:

for decision in [7,8,9]:

admin_h3 = {}
for metropolis in cities:
admin_h3[city] = split_admin_boundary_to_hexagons(admin[city], decision)

f, ax = plt.subplots(1,2, figsize = (15,5))

for idx, metropolis in enumerate(cities):
admin[city].plot(ax=ax[idx], coloration = 'none', edgecolor = 'ok',
linewidth = 3)
admin_h3[city].plot( ax=ax[idx], alpha = 0.8, edgecolor = 'ok',
coloration = 'none')
ax[idx].set_title(metropolis + ' (decision = '+str(decision)+')',
fontsize = 14)

The results of this code block:

Determine 7. H3 hexagon splitting of Budapest and Vienna at completely different resolutions. Picture by the creator.

Let’s maintain decision 9!

3.2. Map values into h3 cells

Now, I’ve each our cities in a hexagon grid format. Subsequent, I shall map the inhabitants and accessibility knowledge into the hexagon cells based mostly on which grid cells every level geometry falls into. For this, the sjoin operate of GeoPandasa, doing a pleasant spatial joint, is an efficient alternative.

Moreover, as we’ve got greater than 100k street community nodes in every metropolis and hundreds of inhabitants grid centroids, almost definitely, there might be a number of POIs mapped into every hexagon grid cell. Subsequently, aggregation might be wanted. Because the inhabitants is an additive amount, I’ll mixture inhabitants ranges throughout the identical hexagon by summing them up. Nonetheless, accessibility isn’t in depth, so I might as an alternative compute the typical defibrillator accessibility time for every tile.

demographics_h3 = {}
accessibility_h3 = {}

for metropolis in cities:

# do the spatial be part of, mixture on the inhabitants stage of every
# hexagon, after which map these inhabitants values to the grid ids
demographics_dict = gpd.sjoin(admin_h3[city], demographics[city]).groupby(by = 'hex_id').sum('inhabitants').to_dict()['population']
demographics_h3[city] = admin_h3[city].copy()
demographics_h3[city]['population'] = demographics_h3[city]

# do the spatial be part of, mixture on the inhabitants stage by averaging
# accessiblity occasions inside every hexagon, after which map these time rating # to the grid ids
accessibility_dict = gpd.sjoin(admin_h3[city], accessibilities[city]).groupby(by = 'hex_id').imply('time').to_dict()['time']
accessibility_h3[city] = admin_h3[city].copy()
accessibility_h3[city]['time'] =

# now present the outcomes
f, ax = plt.subplots(2,1,figsize = (15,15))

demographics_h3[city].plot(column = 'inhabitants', legend = True,
cmap = colormap, ax=ax[0], alpha = 0.9, markersize = 0.25)
accessibility_h3[city].plot(column = 'time', cmap = 'RdYlGn_r',
legend = True, ax = ax[1])

ax[0].set_title('Inhabitants leveln in ' + metropolis, fontsize = 16)
ax[1].set_title('Defibrillator reachability timen in ' + metropolis,
fontsize = 16)

for ax_i in ax: ax_i.axis('off')

The outcomes of this code block are the next figures:

Determine 8. City options in Budapest. Picture by the creator.
Determine 9. City options in Vienna. Picture by the creator.

On this closing step, I’ll estimate the fraction of the reachable inhabitants from the closest defibrillator unit inside a sure period of time. Right here, I nonetheless construct on the comparatively quick 15km/h operating tempo and the two.5km distance restrict.

From the technical perspective, I merge the H3-level inhabitants and accessibility time knowledge frames after which do a easy thresholding on the time dimension and a sum on the inhabitants dimension.

f, ax = plt.subplots(1,2, figsize = (15,5))

for idx, metropolis in enumerate(cities):

total_pop = demographics_h3[city].inhabitants.sum()
merged = demographics_h3[city].merge(accessibility_h3[city].drop(columns =
['geometry']), left_on = 'hex_id', right_on = 'hex_id')

time_thresholds = vary(10)
population_reached = [100*merged[merged.time<limit].inhabitants.sum()/total_pop for restrict in time_thresholds]

ax[idx].plot(time_thresholds, population_reached, linewidth = 3,
coloration = very_peri)
ax[idx].set_xlabel('Reachability time (min)', fontsize = 14,
labelpad = 12)
ax[idx].set_ylabel('Fraction of inhabitants reached (%)', fontsize = 14, labelpad = 12)
ax[idx].set_title('Fraction of inhabitants vs defibrillatornaccessibility in ' + metropolis, pad = 20, fontsize = 16)

The results of this code block are the next figures:

Determine 10. The fraction of the inhabitants reachable from the closest defibrillator unit at a operating tempo. Picture by the creator.

When deciphering these outcomes, I wish to emphasize that, on the one hand, defibrillator accessibility will not be immediately linked to heart-attack survival fee; judging that impact is past each my experience and this undertaking’s scope. Additionally, the info used for Budapest is knowingly incomplete and crowded sources, versus the official Austrian knowledge supply.

After the disclaimers, what will we see? On the one hand, we see that in Budapest, about 75–80% of the inhabitants can get to a tool inside 10 minutes, whereas in Vienna, we attain almost full protection in round 6–7 minutes already. Moreover, we have to learn these time values fastidiously: if we occur to be at an unlucky incident, we have to get to the system, decide it up, return (making the journey time double of the reachability time), set up it, and so forth. in a state of affairs the place each minute could also be a matter of life and dying.

So the takeaways, from a growth perspective, the takeaways are to make sure we’ve got full knowledge after which use the accessibility and inhabitants maps, mix them, analyze them, and construct on them when deploying new gadgets and new places to maximise the efficient inhabitants reached.

Supply hyperlink

More articles


Please enter your comment!
Please enter your name here

Latest article