2 Methodology
2.1 Technical design
2.1.1 Research strategy
A quantitative analysis of the road connectivity and access to healthcare facilities before and after a flooding event identified the temporal patterns of changes on centrality metrics. This was based on the case study of Rio Grande do Sul floodings that took place in late April 2024. The spatial pattern of the flooding shown that some regions were more vulnerable than others. Lastly, an assessment of different pathways to the healthcare facilities contributed to take an informed decision on which areas are more critical to avoid the disruption of healthcare access facilitating urban resilience.
Therefore, this practical-orientated research aim to make a diagnosis of the occurred flooding in Rio Grande do Sul to identifying the potential critical infrastructures necessary for accessing healthcare facilities that could improve the urban resilience against future floodings.
2.1.2 Research planning
To address the diagnosis of the loss of centrality by the flooding, the methodological approach is segmented into three major lanes that constituted the workflow of this research.
- The first lane, selection of the Area Of Interested (AOI), defined the study area based on the flooding extent reported by the Universidade Federal do Rio Grande do Sul and imported the OSM network geometry using osmium.
- The second lane, routable network, transformed the OSM network geometry into a routable graph network using OpenRoutService. Additionally, the post-disaster network is derived through spatial overlay with the extent of flooding.
- The third lane,centrality analysis , connectivity and accessibility metrics identified critical infrastructure.
The method is based on PostgreSQL using the extensions PostGIS and Pgrouting. In the context of emergency management, the spatial database PostGIS and the library of functions Pgrouting proved to be useful developing emergency notifications (Hataitara, Piyathamrongchai, and Choosumrong 2024) or finding dynamic shortest simulating obstruction of the network by floods (Singh et al. 2015), (Choosumrong et al. 2019).
2.1.3 Research material
The before and after flooding networks are obtained using the OSM network from Geofabrik and the flooding extent from Universidad de Rio Grande do Sul. Regarding the location of the hospitals, the Geoportal de Rio Grande do Sul and OSM offered this data. From this material, the PostGIS and OpenRouteService Docker set up the environment required to conduct the centrality analysis. Moreover, the Global Settlement Layer-SMOD classified the different cities to select the AOI, while the Global Settlement -Bult-V added different weights used in the origin-destination sampling. This research material is shown in the table.
3 Workflow
3.1 Selecting the Area of Interest (AoI) and importing data
PostGIS queries obtained the Area of Interest calculating the bounding box from the flooding extent provided by the Universidade Federal do Rio Grande do Sul. Likewise, PostGIS queries prepared the flooding extent for the analysis subdividing the layer and removing slivers. Having obtained the area of interest, the C++/Javascript framework osmium (Barron, Neis, and Zipf 2014) processed the OSM data obtaining the geometry of the network.
3.1.1 Importing data into PostgreSQL
The tool ogr2ogr imported the data into a PostgreSQL database. The output file format parameter -f included the PostgreSQL connection components, while the parameters -nln and -lco assigned the name of the table and columns respectively. For the OSM network, the parameter -nlt adjusted the multilinestring geometry to linestring to process single features more efficiently as indicated in the bibliography (Obe and Hsu 2017).
PostGIS: Importing data to PostgreSQL using ogr2ogr
## OSM geometry: porto_alegre_net_pre.geojson
-f PostgreSQL PG:"host=localhost port= 25432 user=docker password=docker dbname=gis schemas=heigit" /home/ricardo/HeiGIT-Github/data_required_porto_alegre/porto_alegre_net_pre.geojson -nln porto_alegre_net_pre -lco GEOMETRY_NAME=the_geom -nlt LINESTRING -explodecollections
ogr2ogr
## Administrative units: nuts.geojson
-f PostgreSQL PG:"host=localhost port= 25432 user=docker password=docker dbname=gis schemas=heigit" /home/ricardo/HeiGIT-Github/data_required_porto_alegre/nuts.geojson -nln nuts -lco GEOMETRY_NAME=geom
ogr2ogr
## Flooding extent: flooding_rio_grande_do_sul.geojson
-f PostgreSQL PG:"host=localhost port= 25432 user=docker password=docker dbname=gis schemas=heigit" /home/ricardo/HeiGIT-Github/data_required_porto_alegre/flooding_rio_grande_do_sul.geojson -nln flooding_rio_grande_do_sul -lco GEOMETRY_NAME=the_geom
ogr2ogr
## Building density: urban_center_4326.geojson
-f PostgreSQL PG:"host=localhost port= 25432 user=docker password=docker dbname=gis schemas=heigit" /home/ricardo/HeiGIT-Github/data_required_porto_alegre/urban_center_4326.geojson -nln urban_center_4326 -lco GEOMETRY_NAME=geom
ogr2ogr
## Hospitals
### From Rio Grande do Sul Geoportal
-f PostgreSQL PG:"host=localhost port= 25432 user=docker password=docker dbname=gis schemas=heigit" /home/ricardo/HeiGIT-Github/data_required_porto_alegre/Hospitais_com_Leitos_de_UTIs_no_RS.geojson -nln hospitals_bed_rs -lco GEOMETRY_NAME=geom ogr2ogr
3.1.2 Preparing the flooding mask
The downloaded flooding extent covered a larger area in Rio Grande do Sul. However, the area of interest that contained urban dense cities such as Porto Alegre was smaller. Therefore, a series of operations reduced the area focusing on Porto Alegre and at the same time improved the query performance.
Subdividing the flooding mask to make the spatial indexes more efficient was the first step. The reason was the higher number of vertices of large objects and larges bounding boxes that hinder the spatial index performance (Link). After enabling the spatial indexes, selecting only the flooding subunits that intersected with the area of interest reduced the size of the region query. Previous studies reported that reducing the number of spatial polygons and points of the region saved processing time and computation costs (Zhao et al. 2017). Additionally, the following code dissolved the borders to break the multipolygon into simple polygons to improve the performance of functions such as st_difference() and removed slivers polygons. Based on size criteria, the calculated area of these undesirable polygons identified the slivers of the flooding extent (Grippa et al. 2018). (link).
PostGIS: Subdiving flooding extent and removing slivers
--- 1) Subdividing to make spatial indexes more efficent
--- Select the GHS area that intersects with Porto Alegre
CREATE TABLE flooding_subdivided_porto AS
WITH porto_alegre_ghs AS(
SELECT
*
ghs.FROM
AS ghs
urban_center_4326 JOIN
nuts ON
st_intersects(nuts.geom, ghs.geom)WHERE
= 'Porto Alegre'),
nuts.shapename --- Bounding Box that contained the GHS in Porto Alegre
AS(
porto_alegre_ghs_bbox SELECT
4326) as geom_bbox
st_setsrid(st_extent(geom),FROM
porto_alegre_ghs),---Subdivide the flood extent to increase spatial indexes performance
AS (
flooding_sul_subdivided SELECT
as the_geom
st_subdivide(geom) FROM
flooding_rio_grande_do_sul) ---- Select the flooding subunits that intersects with the previous bounding box
SELECT
*
flooding_sul_subdivided.FROM
flooding_sul_subdivided,
porto_alegre_ghs_bboxWHERE
st_intersects(flooding_sul_subdivided.the_geom, porto_alegre_ghs_bbox.geom_bbox);--- 2) Simplifying geometry
CREATE TABLE flooding_cleaned_porto AS
SELECT
:geometry(polygon, 4326) geom
(ST_Dump(the_geom)).geom:FROM
flooding_subdivided_porto;
CREATE TABLE flooding_cleaned_porto_union AS
SELECT
:geometry(multipolygon, 4326) geom
ST_Union(geom):FROM
flooding_cleaned_porto;
CREATE TABLE flooding_cleaned_porto_union_simple AS
SELECT
:geometry(polygon, 4326) geom
(ST_Dump(geom)).geom:FROM
flooding_cleaned_porto_union;--- This allowed to calculate the area of each polygon finding slivers that were removed using the following code.
SELECT COUNT(*)
FROM flooding_cleaned_porto_union_simple ; --- count= 54.
DELETE FROM
flooding_cleaned_porto_union_simpleWHERE
< 0.0001; ---count= 2.
ST_Area(geom)
--- Obtain Area
SELECT
sum(st_area(geom::geography)/10000)::integer
FROM
flooding_cleaned_porto_union_simple;--- Obtain size in memory
SELECT
SUM(ST_MemSize(geom)))
pg_size_pretty(FROM
flooding_cleaned_porto_union_simple;--- Obtain number of points
SELECT
sum(ST_Npoints(geom))
FROM
flooding_cleaned_porto_union_simple;
3.2 Obtaining pre and post disaster routable networks
The creation of the network after the disaster using the modified flooding mask and the creation of the origin-destination are considered in this section. Before generating these routable networks, the following code set configuration parameters, recommended to deliver faster results (Jhummarwala Abdul 2014).
PostGIS: Adjusting configuration settings
--- Set up the configuration
----- Increase performance by using more max_parallel_workes_per_gather (https://blog.cleverelephant.ca/2019/05/parallel-postgis-4.html)
SET max_parallel_workers_per_gather =4;
----- Cluster the index (https://gis-ops.com/pgrouting-speedups/)
CLUSTER porto_alegre_net_largest USING porto_alegre_net_largest;
3.2.1 Importing OpenStreetMap (OSM) network as geometry
The following SQL code created the command to import OpenStreetMap (OSM) network using osmium. After creating the spatial index on the network table, the GHS-SMOD dataset the Global Human Settlement that intersected with Porto Alegre is selected as AOI. Secondly, this GHS is used to dynamically generate the bounding box and command line required by osmium. The OSM network obtained with this osmium command was the input required for OpenRouteService (ORS).
PostGIS: Generating responsive code to import OSM
--- 0) Add spatial index everywhere:
CREATE INDEX idx_porto_alegre_net_largest_geom ON porto_alegre_net_largest USING gist(the_geom);
CREATE INDEX idx_porto_alegre_net_largest_source ON porto_alegre_net_largest USING btree(fromid);
CREATE INDEX idx_porto_alegre_net_largest_target ON porto_alegre_net_largest USING btree(toid);
CREATE INDEX idx_porto_alegre_net_largest_id ON porto_alegre_net_largest USING btree(ogc_fid);
CREATE INDEX idx_porto_alegre_net_largest_cost ON porto_alegre_net_largest USING btree(weight);
--- 1) GHS urban area that intersected with Porto Alegre city
WITH porto_alegre_ghs AS(
SELECT
*
ghs.FROM
AS ghs
urban_center_4326 JOIN
nuts ON
st_intersects(nuts.geom, ghs.geom)WHERE
= 'Porto Alegre'),
nuts.shapename --- Bounding Box that contained the GHS in Porto Alegre
AS(
porto_alegre_ghs_bbox SELECT
4326) as geom_bbox
st_setsrid(st_extent(geom),FROM
porto_alegre_ghs),--- 2) The command used the Porto Alegre GHS and its Bounding Box to import the OpenStreet Network.
AS (
porto_alegre_ghs_bbox_osmium_command SELECT
0.0001)) AS min_lon,
ST_XMin(ST_SnapToGrid(geom_bbox, 0.0001)) AS max_lon,
St_xmax(ST_SnapToGrid(geom_bbox,0.0001)) AS min_lat,
St_ymin(ST_SnapToGrid(geom_bbox,0.0001)) AS max_lat
St_ymax(ST_SnapToGrid(geom_bbox, FROM porto_alegre_ghs_bbox)
SELECT
'osmium extract -b '
|| min_lon || ',' || min_lat || ',' || max_lon || ',' || max_lat ||
' sul-240501.osm.pbf -o puerto_alegre_urban_center.osm.pbf' AS osmium_command
FROM porto_alegre_ghs_bbox_osmium_command;
3.2.2 Transforming geometric network into routable graph network using OpenRouteService (ORS).
Firstly, running the docker for OpenRouteService transformed the OSM network from the GHS AoI into a graph. In the “ors-config.yml” from OpenRotueService (ORS), the “source_file” parameter is set to find the directory that contained the OSM geometry network. OpenRouteService (ORS) created a a routable network assigining costs and adding information for each node such as the origin, “fromId”, and the destination, “toId”. The R script “get_graph” from Marcel Reinmuth exported the ORS network named as “porto_alegre_net”.
Secondly, the data type of the columns “fromid” and “toid” from the graph network generated by ORS is transform into bigint to run the algorithm pgr_dijkstra as the official documentation indicated.
PostGIS: Adjusting ORS configuration & preparation for pgrouting
--- 1) the source_file parameter in the ors.config.yml file is set to find the imported OSM network
/home/ors/files/puerto_alegre_urban_center.osm.pbf
source_file: ---- 2) The exported ORS graph is prepared for pgrouting casting the right data type for the pgrouting functions.
select * from porto_alegre_net_pre;
ALTER TABLE porto_alegre_net_pre
ALTER COLUMN "toid" type bigint,
ALTER COLUMN "fromid" type bigint,
ALTER COLUMN "ogc_fid" type bigint;
R: Visualizing OSM and ORS network with Mapview
## Load data
<- st_read(eisenberg_connection, layer="porto_alegre_net_pre")
ors_network <- st_read(eisenberg_connection, layer="puerto_alegre_ghs_osm")
osm_network <- ors_network |> head()
ors_network_subset ## Create subset using a bounding box
<- ors_network |> filter(id ==140210) |> st_bbox()
ors_subset <- ors_subset$xmax - ors_subset$xmin
xrange <- ors_subset$ymax - ors_subset$ymin
yrange ## Expand the bounding box
1] <- ors_subset[1] - (4 * xrange) # xmin - left
ors_subset[3] <- ors_subset[3] + (4 * xrange) # xmax - right
ors_subset[2] <- ors_subset[2] - (2 * yrange) # ymin - bottom
ors_subset[4] <- ors_subset[4] + (2 * yrange) # ymax - top
ors_subset[## Convert bounding box into polygon
<- ors_subset %>% #
ors_subset_bbox st_as_sfc()
## Use the polygon to subset the network
<- sf::st_intersection(ors_network, ors_subset_bbox)
intersection_ors <- sf::st_intersection(osm_network, ors_subset_bbox)
intersection_osm ## Mapview maps
<- mapview(intersection_osm,
m1 color="#6e93ff",
layer.name ="OpenStreetMap - Geometry",
popup=popupTable(intersection_osm,
zcol=c("id","osm_id","name","geom")))
<- mapview(intersection_ors,
m2 color ="#d50038",
layer.name="OpenRouteService -Graph",
popup=popupTable(intersection_ors))
sync(m1,m2)
3.2.3 Exploring ORS graph network and components
Before using the graph network, a quick inspection using the pgrouting function pgr_strongComponents() revealed the number of components or isolated self-connecting networks within the graph. The largest network component is selected using its length. The following code created a table with the different components and another table containing the largest component. The reason to discard the rest of components was its relatively small size and based on the observation that some of these components appeared next to OSM objects categorized as “barriers”, which meant that they were part of private properties, such as “Villa’s Home Resot”, being not relevant for an emergency response.
PostGIS: Obtaining components of the network and their length
--- 1) Quick inspection of the graph to determine components
---- Create a vertice table for pgr_dijkstra()
SELECT
'porto_alegre_net_pre', source:='fromid', target:='toid');
pgr_createVerticesTable(
---- Add data to the vertices created table
CREATE TABLE component_analysis_network_porto AS
WITH porto_alegre_net_component AS (
SELECT
*
FROM
'SELECT ogc_fid AS id,
pgr_strongComponents( fromid AS source,
toid AS target,
weight AS cost
FROM
porto_alegre_net_pre')),
AS (
porto_alegre_net_component_geom SELECT
*,
net.
net_geom.the_geomFROM
AS net
porto_alegre_net_component JOIN
AS net_geom
porto_alegre_net_pre ON
= net_geom.fromid)
net.node SELECT
component,AS the_geom,
st_union(the_geom) :geography)::int AS length
st_length(st_union(the_geom):FROM
porto_alegre_net_component_geomGROUP BY component
ORDER BY length DESC;
--- 2) A table with the component of the network with the highest length
CREATE TABLE porto_alegre_net_largest AS
---- Obtain again table classifying nodes in different components
WITH porto_alegre_net_component AS (
SELECT
*
FROM
'SELECT
pgr_strongComponents( ogc_fid AS id,
fromid AS source,
toid AS target,
weight AS cost
FROM
porto_alegre_net_pre')),
--- Calculate the largest component from the network
AS (
largest_component_net SELECT
componentFROM
component_analysis_network_porto LIMIT 1),
--- Using the largest component from the network to filter
AS (
largeset_component_network_porto SELECT
*
FROM
porto_alegre_net_component,
largest_component_netWHERE
= largest_component_net.component)
porto_alegre_net_component.component SELECT
*
net_multi_component.FROM
AS net_multi_component,
porto_alegre_net_pre AS net_largest_component
largeset_component_network_porto WHERE
IN (net_largest_component.node); net_multi_component.fromid
R: Visualizing the three longest component
<- st_read(eisenberg_connection, "component_analysis_network_porto")
component_analysis_network ## Select top 3
<- component_analysis_network |> arrange(desc(round(distance))) |> slice(1:3)
component_analysis_network_3 ### Tidy component to be used as categorical variable
$component <- component_analysis_network_3$component |> as.character() |> as_factor()
component_analysis_network_3## Visualization
<- component_analysis_network_3 |>
m1_net filter(component=="1") |>
mapview(layer.name = "1st Longest component",
lwd= 0.5,
color="#66c2a5")
<- component_analysis_network_3 |>
m2_net filter(component=="3728") |>
mapview(layer.name = "2nd Longest component",
color ="#8da0cb",
lwd=3,
popup= popupTable( component_analysis_network_3[2,],
zcol=(c("component","distance"))))
<- component_analysis_network_3 |>
m3_net filter(component=="40578") |>
mapview(layer.name = "3rd Longest component",
color="#fc8d62",
popup= popupTable(component_analysis_network_3[3,]),
lwd =3)
+ m2_net + m3_net m1_net
3.2.4 Generating a routable pre-disaster network
The original porto_alegre_net_pre graph network is classified by its components and the largest component representing the network before the flooding is filtered.In the first step, the components are calculated and the vertices from the largest network stored in the table largeset_component_network_porto. Then, selecting the vertices from the original table where they also appeared in the largest component network generated the pre-disaster network.
PostGIS: Creating the pre-disaster network filtering out components
CREATE TABLE porto_alegre_net_largest AS
---- 1) Classify the geometric OSM network in different components
WITH porto_alegre_net_component AS (
SELECT
*
FROM
'SELECT id,
pgr_strongComponents( source,
target,
cost
FROM
porto_alegre_net_pre')),
--- Obtaining the nodes from the largest component from the network
AS (
largest_component_net SELECT
componentFROM
component_analysis_network_porto LIMIT 1),
--- 2) Filtering the nodes from the original network to only those in the largest component
AS (
largeset_component_network_porto SELECT
*
FROM
porto_alegre_net_component,
largest_component_netWHERE
= largest_component_net.component)
porto_alegre_net_component.component SELECT
*
net_multi_component.FROM
AS net_multi_component,
porto_alegre_net_pre AS net_largest_component
largeset_component_network_porto WHERE
source IN (net_largest_component.node); net_multi_component.
3.2.5 Generating a routable post-disaster network
A naive approach overlaying the flooding mask with the road network by the function st_difference() crashed the session. The follwing multi-step methodology reduced the processing cost making the query feasible using less resources by incorporating boolean operators in some steps. In other studies, decomposing difference function queries using boolean operators reported an increase on the performance of 223% in PostgreSQL (Lupa and Piórkowski 2014). Firstly the network inside the flooding mask is selected. Secondly, a subset of the network outside the flooding mask avoided using spatial operations saving computing resources by using the ID’s from the network inside the flooding to filter the data.Thirdly, to obtain the boundaries between the inside and outside network, the geometry is converted into its exterior ring. From this network on the boundary, only the exterior part that intersected with the outside boundary was selected.
PostGIS: Multi-step methodology to create the post-disaster network
--- 1) Network inside the flooding mask
CREATE TABLE porto_alegre_street_in_v2 AS
SELECT net.id,
CASE
WHEN ST_Contains(flood.the_geom, net.the_geom)
THEN net.the_geom
ELSE st_intersection(net.the_geom, flood.the_geom)
END AS geom
FROM porto_alegre_net_largest net
JOIN flooding_subdivided_porto flood
ON st_intersects(net.the_geom, flood.the_geom);
--- 2) Network outside the flooding mask
CREATE TABLE porto_alegre_street_out_v2 AS
SELECT net.*
FROM porto_alegre_net_largest net
WHERE net.id NOT IN (
SELECT net.id
FROM porto_alegre_street_in_v2 net);
--- 3) Network on the boundaries
CREATE TABLE porto_alegre_net_outside_v2 AS
WITH porto_alegre_ghs AS(
SELECT
*
ghs.FROM
AS ghs
urban_center_4326 JOIN
nuts ON
st_intersects(nuts.geom, ghs.geom)WHERE
= 'Porto Alegre'),
nuts.shapename --- Bounding Box that contained the GHS in Porto Alegre
AS(
porto_alegre_ghs_bbox SELECT
4326) as geom_bbox
st_setsrid(st_extent(geom),FROM
porto_alegre_ghs),AS (
flooding_sul_subdivided SELECT
as the_geom
st_subdivide(geom) FROM
flooding_rio_grande_do_sul),AS (
exterior_ring_porto_alegre_v2 SELECT
as geom
ST_ExteriorRing((ST_Dump(union_geom)).geom) FROM (
SELECT
as union_geom
ST_Union(flood.the_geom) FROM
as bbox
porto_alegre_ghs_bbox JOIN
as flood
flooding_sul_subdivided ON
ST_Intersects(flood.the_geom, bbox.geom_bbox)AS subquery)
) SELECT net.id,
CASE
WHEN NOT ST_Contains(flood.geom, net.the_geom)
THEN net.the_geom
ELSE st_intersection(net.the_geom, flood.geom)
END AS geom,
net.target,source,
net.cost,
"unidirectid",
"bidirectid"
FROM
AS net
porto_alegre_net_largest JOIN exterior_ring_porto_alegre_v2 flood ON
st_intersects(net.the_geom, flood.geom);
---- For the network
CREATE INDEX idx_porto_alegre_net_outside_v2 ON porto_alegre_net_outside_v2 USING gist (geom);
CLUSTER porto_alegre_net_outside_v2 USING idx_porto_alegre_net_outside_v2;
--- For the flooding mask
CREATE INDEX flooding_sul_subdivided_idx ON flooding_sul_subdivided USING gist (the_geom);
CLUSTER flooding_sul_subdivided USING flooding_sul_subdivided_idx;
---- Before doing difference
FULL, ANALYZE) porto_alegre_net_outside_v2;
VACUUM(FULL, ANALYZE) flooding_sul_subdivided;
VACUUM(--- st_difference and uniting the external to the boundaries
--- Unite the subunits of the flooding
CREATE TABLE flooding_symple as
SELECT st_union(geom) as the_geom FROM flooding_cleaned_porto_union_simple;
---- Index the flooding
CREATE INDEX flooding_symple_idx ON flooding_symple USING gist (the_geom);
CLUSTER flooding_symple USING flooding_symple_idx;
--- 4) Obtain the boundary network that intersect with the outside network
CREATE TABLE difference_outside_flood_v3 AS
SELECT net.id,
target,source,
cost,
unidirectid,
bidirectid,AS the_geom
st_difference(net.geom, flood.the_geom) FROM porto_alegre_net_outside_v2 AS net,
AS flood;
flooding_symple
--- 5) Unite outside network with the external part of the boundary network
CREATE TABLE porto_alegre_net_post_v5 AS
SELECT *
FROM porto_alegre_street_out_v2
UNION
SELECT *
FROM difference_outside_flood_v3;
R: Visualizing the multi-step methodology
<- sf::st_read(eisenberg_connection, "porto_alegre_net_largest")
porto_alegre_net_pre <- sf::st_read(eisenberg_connection, "centrality_weighted_100_bidirect_cleaned")
centrality_pre <- sf::st_read(eisenberg_connection, "flooding_cleaned_porto")
flooding <- sf::st_read(eisenberg_connection, "porto_alegre_street_in_v2")
net_in ## subset
<- sf::st_read(eisenberg_connection, "porto_alegre_net_largest_subset")
subset_network_pre <- sf::st_read("/home/ricardo/heigit_bookdown/data/porto_alegre_street_in_v3_subset.geojson") |> select("geometry")
subset_network_in <- sf::st_read("/home/ricardo/heigit_bookdown/data/porto_alegre_street_out_subset.geojson")
subset_network_out <-sf::st_read("/home/ricardo/heigit_bookdown/data/porto_alegre_street_outside_subset.geojson")
subset_network_outside_flood <- sf::st_read(eisenberg_connection, "reet_united_v3")
subset_network_post <- sf::st_read(eisenberg_connection, "flooding_symple") |> st_as_sf()
flooding ## centrality
mapview(subset_network_pre,
color="#d4e7e7",
lwd= 1,
layer.name="0.Pre-flooding network",
popup=popupTable(subset_network_pre,
zcol=c("id","source","target","bidirectid"))) +
mapview(flooding,
color="darkblue",
alpha.regions= 0.5,
layer.name="0.Flooding layer") +
mapview(subset_network_in,
color="red",
lwd= 1,
hide = TRUE,
layer.name="1.Network inside the flooding") +
mapview(subset_network_out,
color="yellow",
lwd= 1.2,
hide = TRUE,
layer.name="2.Flooding outside the flooding mask",
popup=popupTable(subset_network_out)) +
mapview(subset_network_outside_flood,
color="lightgreen",
lwd= 1.4,
hide = TRUE,
layer.name="3 & 4. Network on the boundaries",
popup=popupTable(subset_network_outside_flood)) +
mapview(subset_network_post,
color="green",
lwd= 1,
hide =TRUE,
layer.name="5. Uniting network external to the boundires and network from outside",
popup=popupTable(subset_network_post))
After obtaining this post-disaster network, pgrouting created the vertices table required to conduct the centrality analysis. Similarly to generating the pre-disaster network, an analysis of the component identified those components more relevant due to its length. Since some of the post-disaster network components had similar length, the table “component_analysis_network_porto_post” stored this information, which after a qualitative inspection determined the components and their nodes that finally composed the post-disaster network.
PostGIS: Creating the post-disaster network
---- 1) Creating a vertices table required for centrality analysis
SELECT pgr_createverticesTable('porto_alegre_net_post_v5');
---- 2) Obtaining components and their nodes to be used later as a filter.
CREATE TABLE component_analysis_network_porto_post AS
WITH porto_alegre_net_component_post AS (
SELECT
*
FROM
'SELECT id,
pgr_strongComponents( source,
target,
cost
FROM
porto_alegre_net_post_v5')),
AS (
porto_alegre_net_component_geom_post SELECT
*,
net.
net_geom.the_geomFROM
AS net
porto_alegre_net_component_post JOIN
AS net_geom
porto_alegre_net_post_v5 ON
= net_geom.source)
net.node SELECT
component,AS the_geom,
st_union(the_geom) :geography)::int AS length
st_length(st_union(the_geom):FROM
porto_alegre_net_component_geom_postGROUP BY component
ORDER BY length DESC;
--- Summarising component
CREATE TABLE components_network_post AS
WITH porto_alegre_net_component_post AS (
SELECT
*
FROM
'SELECT
pgr_strongComponents( id,
source,
target,
cost
FROM
porto_alegre_net_post_v5')),
--- 3) Calculating the largest component from the network
AS (
largests_component_net_post SELECT
component,
the_geom,length::int
FROM
component_analysis_network_porto_post)SELECT * FROM largests_component_net_post;
---- Visualizing which are more important
CREATE TABLE main_components_post AS
SELECT
*
FROM
components_network_postWHERE
IN (21,14,5187);
component
-------------- Remember to justify why 5
CREATE TABLE prueba_largest_network_post AS
WITH porto_alegre_net_component_post AS (
SELECT
*
FROM
'SELECT
pgr_strongComponents( id,
source,
target,
cost
FROM
porto_alegre_net_post_v5')),
--- Calculate the largest component from the network
AS (
largest_component_net_post SELECT
componentFROM
component_analysis_network_porto_postLIMIT 5),
--- Using the largest component from the network to filter
AS (
largeset_component_network_porto_post SELECT
*
FROM
porto_alegre_net_component_post,
largest_component_net_postWHERE
= largest_component_net_post.component)
porto_alegre_net_component_post.component SELECT
*
net_multi_component.FROM
AS net_multi_component,
porto_alegre_net_post_v5 AS net_largest_component
largeset_component_network_porto_post WHERE
source IN (net_largest_component.node); net_multi_component.
3.3 Measuring centrality on networks
The centrality metrics used to determine the importance of each road segment or hospitals in the network were the edge betweenness centrality and closeness centrality respectively.
- The closenesss centrality defined as a measure of how central to other vertices is a vertex (Kolaczyk and Csárdi 2014). It is obtained calculating the average shortest path from a node v to all other nodes in the network (Eq 1) (Florath, Chanussot, and Keller 2024).
\[ \text{CN}(v) = \frac{n - 1}{\sum_{w = 1}^{n - 1} d(w, v)} \]
where d(w,v) is the shortest path distance and n-1 is | the number of nodes reachable from v. In this case, | instead of using the distance to determine how central | | each hospital in the AoI was,it was the cost provided by | ORS. A higher value indicated a faster access to the | hospital, while a large decrease after the flooding event | indicated high vulnerability.
- The betweenness centrality assess the significance of roads in a network by counting the number of shortest paths that pass through this node. The most common definition of the betweenness centrality was introduced by Freeman (Eq 2) (Freeman 1977).
\[ c_{B}(v) = \sum_{\substack{s \neq t \neq v \\ s,t \in V}} \frac{\sigma(s,t|v)}{\sigma(s,t)} \]
where σ(s,t|v) is the total number of shortest paths between s and t that pass through v, and σ(s,t) is the total number of shortest paths between s and t (regardless of whether or not they pass through v). In the event that shortest paths are unique, cB(v) just counts the number of shortest paths going through v (Kolaczyk and Csárdi 2014). In this case, edges are used instead of vertices. Those roads that are frequently part of shortest paths had higher values. This metric was used before to assess the accessibility of critical amenities before and during floodings (Phua et al. 2024 ; Petricola et al. 2022 ; Gangwal et al. 2023).
3.3.1 Generating Origin-Destination matrix
The volume of people is modeled using a traffic matrix or origin-destination (OD) matrix denoted by \(Z = [Z_{ij}]\) , where \(Z_{ij}\) represents the total volume of people flowing from a starting point \(i\) to a ending point \(j\) in a given period of time. For this study case, these generated points are then snapped into the ORS graph network where the variables “fromid” and “toid” are the origin and destination respectively. A naive approach sampled these OD using a regular distribution, while another approach used the built-up density as weight to take more samples where there were more buildings. The motivation of choice were previous studies that suggested using to consider origin-destination distributions to avoid being overly simplistic (Coles et al. 2017).
3.3.1.1 Sampling using regular distribution
The function “I_Grid_Point_Series” created a series of regular points that represented the OD matrix given an area geometry, and the distance for the X-axis and Y-axis in angle degrees. After this, a table stored 1258 sample points generated on the AoI with a distance of 0.01 º between each sample. Lastly, the application of two indexes improved further queries on this new table. This was recommended because these points were outside the network requiring snapping, which is a spatial operation with relatively high computational costs. Apart from indexing the geometry column and the id, the query is constrained to a buffer of 0.01º to reduce computational costs and the one multipoint geometry is converted into 1258 simple points.
PostGIS: Creating a function to sample regularly
--- 1) Creating the function that sample OD regularly:
CREATE OR REPLACE FUNCTION I_Grid_Point_Series(geom geometry, x_side decimal, y_side decimal, spheroid boolean default false)
AS $BODY$
RETURNS SETOF geometry DECLARE
decimal;
x_max decimal;
y_max decimal;
x_min decimal;
y_min integer := 4326;
srid integer;
input_srid DECIMAL;
x_series DECIMAL;
y_series BEGIN
CASE st_srid(geom) WHEN 0 THEN
:= ST_SetSRID(geom, srid);
geom 'SRID Not Found.';
RAISE NOTICE ELSE
'SRID Found.';
RAISE NOTICE END CASE;
CASE spheroid WHEN false THEN
'Spheroid False';
RAISE NOTICE else
:= 900913;
srid 'Spheroid True';
RAISE NOTICE END CASE;
:=st_srid(geom);
input_srid:= st_transform(geom, srid);
geom := ST_XMax(geom);
x_max := ST_YMax(geom);
y_max := ST_XMin(geom);
x_min := ST_YMin(geom);
y_min := CEIL ( @( x_max - x_min ) / x_side);
x_series := CEIL ( @( y_max - y_min ) / y_side );
y_series RETURN QUERY
SELECT st_collect(st_setsrid(ST_MakePoint(x * x_side + x_min, y*y_side + y_min), srid)) FROM
0, x_series) as x,
generate_series(0, y_series) as y
generate_series(WHERE st_intersects(st_setsrid(ST_MakePoint(x*x_side + x_min, y*y_side + y_min), srid), geom);
END;
$BODY$ LANGUAGE plpgsql IMMUTABLE STRICT;--- 2) Using this function to create the sampling table
CREATE TABLE regular_point_od AS (
WITH multipoint_regular AS(
select
0.037,0.037, false) AS geom
I_Grid_Point_Series(geom, from porto_alegre_bbox as geom),
AS(
point_regular SELECT
4326)::geometry(Point, 4326) AS geom
st_setsrid((st_dump(geom)).geom, FROM multipoint_regular)
SELECT
row_number() over() AS id,
geomFROM
point_regular);--- 3) Applying indexes to increase performance
CREATE INDEX idx_regular_point_od_geom ON regular_point_od USING GIST (geom);
CREATE INDEX idx_regular_point_od_id ON regular_point_od USING btree(id);
3.3.1.2 Sampling using weighted distribution
Currently sampling the OD matrix using a weighted distribution is carried out using R. An aliquot of 2728 points is taken from the total size of the sample population of 2.728.133, which represented a 1% of this total. The function spatSample from the terra library took this sample based on the GHS-Built-V raster and the results are stored directly in the PostgreSQL database using the DBI library.
R: Weighted sampling based on the built up density
# Import data
## raster
<- terra::rast('/home/ricardo/HeiGIT-Github/do_not_push_too_large/GHS_BUILT_V_E2020_GLOBE_R2023A_54009_100_V1_0/GHS_BUILT_V_E2020_GLOBE_R2023A_54009_100_V1_0.tif')
buildings <- 2728
pop_ghs ## AoI
<- sf::read_sf('/home/ricardo/HeiGIT-Github/data_required_porto_alegre/porto_alegre_bbox_derived.geojson')
aoi_bbox ## reproject for clipping
<- aoi_bbox |> st_transform(crs(buildings)) |> as_Spatial()
aoi_bbox_reproj <- crop(buildings, aoi_bbox_reproj)
build_cropped ## reproject for sampling
<- terra::project(build_cropped, crs(aoi_bbox))
build_4326 ## weighted sampling
<- spatSample(build_cropped, pop_ghs, "weights", as.points=TRUE, ) |> st_as_sf() |> st_transform(4326)
od names(od)[1] <- 'building'
::dbWriteTable(connection,
DBI::Id(schema = "public", table = "od_2728"),
DBI od)
After creating an ID for the samples, a neighbor nearest function snapped these points into the closest node of the network. Subsequently, two table subset stored a subset of 100 points for origin and destination due to the path computation costs, since the costs fom the Dijkstra algorithm used in pgrouting are reported as very high (Brandes 2001). Indexes applied on these created tables that contained the OD matrix speed up further queries. This sampling process repeated twice for the pre-flooding and post-flooding scenario generated the OD matrix required to assess the centrality of both networks.
PostGIS: Snapping samples to the closest node
--- 0) Creating an ID column
ALTER TABLE od_2728
ADD COLUMN id serial PRIMARY KEY;
----1: Pre flooding event
--- 1.1) Snapping all the points based on building density to the closest vertices within the network
CREATE TABLE od_2728_snapped AS
SELECT DISTINCT ON (net.id)
id AS pt_id,
pt.id AS net_id,
net.
net.the_geomFROM
select *
(FROM
as pt) as pt
od_2728 CROSS JOIN
SELECT
LATERAL (*
FROM porto_alegre_net_largest_vertices_pgr AS net
ORDER BY net.the_geom <-> pt.geometry
LIMIT 1) AS net;
--- 1.2) Limiting to 100 origin points in the pre flooding event
CREATE TABLE weight_sampling_100_origin AS
WITH porto_100_origin AS (
SELECT
*
FROM
od_2728_snapped ORDER BY random() LIMIT 100)
SELECT * FROM porto_100_origin;
--- 1.3) Limiting to 100 destination in the pre flooding event
CREATE TABLE weight_sampling_100_destination AS
WITH porto_100_destination AS (
SELECT
*
FROM
od_2728_snapped ORDER BY random() LIMIT 100)
SELECT * FROM porto_100_destination;
--- Indeces on origin
CREATE INDEX weight_sampling_100_origin_idx_pt_id ON weight_sampling_100_origin USING btree(pt_id);
CREATE INDEX weight_sampling_100_origin_idx_net_id ON weight_sampling_100_origin USING btree(net_id);
CREATE INDEX weight_sampling_100_origin_idx_the_geom ON weight_sampling_100_origin USING gist(the_geom);
----- A) Indexes, clustering and vacuum
--- Indexes on destination
CREATE INDEX weight_sampling_100_destination_idx_pt_id ON weight_sampling_100_destination USING btree(pt_id);
CREATE INDEX weight_sampling_100_destination_idx_net_id ON weight_sampling_100_destination USING btree(net_id);
CREATE INDEX weight_sampling_100_destination_idx_the_geom ON weight_sampling_100_destination USING gist(the_geom);
---- Cluster
CLUSTER porto_alegre_net_largest USING idx_porto_alegre_net_largest_geom;
---- Vacuum clean
full, ANALYZE) weight_sampling_100_origin;
VACUUM(full, ANALYZE) weight_sampling_100_destination;
VACUUM(full, ANALYZE) porto_alegre_net_largest;
VACUUM(----
---- 2) Post flooding scenario:
--- 2.1) Snapping 2728 points to post-scenario network
CREATE TABLE od_2728_snapped_post AS
SELECT DISTINCT ON (net.id)
id AS pt_id,
pt.id AS net_id,
net.
net.the_geomFROM
select *
(FROM
as pt) as pt
od_2728 CROSS JOIN
SELECT
LATERAL (*
FROM prueba_largest_network_post_vertices_pgr AS net
ORDER BY net.the_geom <-> pt.geometry
LIMIT 1) AS net;
--- 2.2) Creating 100 origin samples from the snapped points post-scenario
CREATE TABLE weight_sampling_100_origin_post AS
WITH porto_100_origin AS (
SELECT
*
FROM
od_2728_snapped_post ORDER BY random() LIMIT 100)
SELECT * FROM porto_100_origin;
--- 2.3) Creating 100 destination from the snapped points post-scenario
CREATE TABLE weight_sampling_100_destination_post AS
WITH porto_100_destination AS (
SELECT
*
FROM
od_2728_snapped_post ORDER BY random() LIMIT 100)
SELECT * FROM porto_100_destination;
--- B) Indeces on origin
CREATE INDEX weight_sampling_100_origin_idx_pt_id ON weight_sampling_100_origin USING btree(pt_id);
CREATE INDEX weight_sampling_100_origin_idx_pt_id ON weight_sampling_100_origin USING btree(net_id);
CREATE INDEX weight_sampling_100_origin_idx_the_geom ON weight_sampling_100_origin USING gist(the_geom);
--- Indeces on destination
CREATE INDEX weight_sampling_100_destination_post_idx_pt_id ON weight_sampling_100_destination_post USING btree(pt_id);
CREATE INDEX weight_sampling_100_destination_post_idx_net_id ON weight_sampling_100_destination_post USING btree(net_id);
CREATE INDEX weight_sampling_100_destination_post_idx_the_geom ON weight_sampling_100_destination_post USING gist(the_geom);
---- Indexes on origin
CREATE INDEX weight_sampling_100_origin_post_idx_pt_id ON weight_sampling_100_origin_post USING btree(pt_id);
CREATE INDEX weight_sampling_100_origin_post_idx_net_id ON weight_sampling_100_origin_post USING btree(net_id);
CREATE INDEX weight_sampling_100_origin_post_idx_the_geom ON weight_sampling_100_origin_post USING gist(the_geom);
---- Index on the new network
CREATE INDEX prueba_largest_network_post_idx_GEOM ON prueba_largest_network_post USING gist(the_geom);
CREATE INDEX prueba_largest_network_post_idx_id ON prueba_largest_network_post USING btree(id);
CREATE INDEX prueba_largest_network_post_idx_target ON prueba_largest_network_post USING btree(target);
CREATE INDEX prueba_largest_network_post_idx_source ON prueba_largest_network_post USING btree(source);
CREATE INDEX prueba_largest_network_post_idx_cost ON prueba_largest_network_post USING btree(cost);
---- Cluster
CLUSTER prueba_largest_network_post USING prueba_largest_network_post_idx_GEOM;
---- Vacuum clean
full, ANALYZE) weight_sampling_100_origin;
VACUUM(full, ANALYZE) weight_sampling_100_destination;
VACUUM(full, ANALYZE) porto_alegre_net_largest; VACUUM(
3.3.1.3 Selecting point of interest (Hospitals)
The bounding box containing the GHS settlement that intersected with Porto Alegre filtered the Hospitals from Rio Grande do Sul. Similarly to previous sampling, a closest neighbor query snapped the location of these hospitals to the closest vertices of the network. Hospitals and other healthcare facilities were the objects of previous studies focused on accessibility (Geldsetzer et al. 2020 ; Sushma and Reddy 2021). The following query provided the table of healthcare facilities affected by the flooding, which were the destination of the OD matrix and required to assess their accessibility.
PostGIS: Selecting hospistals in the AoI and snapping
--- Creating a table with the bounding box that contains Porto Alegre + GHS
CREATE TABLE porto_alegre_bbox AS
WITH porto_alegre_ghs AS(
SELECT
*
ghs.FROM
AS ghs
urban_center_4326 JOIN
nuts ON
st_intersects(nuts.geom, ghs.geom)WHERE
= 'Porto Alegre'),
nuts.shapename --- Bounding Box that contained the GHS in Porto Alegre
AS(
porto_alegre_ghs_bbox SELECT
4326) as geom_bbox
st_setsrid(st_extent(geom),FROM
porto_alegre_ghs)SELECT * FROM porto_alegre_ghs_bbox
---Use the bounding box to select hospitals
CREATE TABLE hospital_rs_node_v2 AS
WITH hospital_rs_porto AS (
SELECT
*
h.FROM
AS h,
hospitals_bed_rs
porto_alegre_bbox bboxWHERE st_intersects(h.geom, bbox.geom_bbox))
SELECT DISTINCT ON (h.cd_cnes)
cd_cnes,
ds_cnes,id,
f.<-> h.geom AS distance,
f.the_geom AS geom_hospital,
h.geom AS geom_node
f.the_geom FROM hospital_rs_porto h
---- Snapping the hospitals to the closest vertices in the network
LEFT JOIN LATERAL
SELECT
(id,
the_geomFROM porto_alegre_net_largest_vertices_pgr AS net
ORDER BY
<-> h.geom
net.the_geom LIMIT 1) AS f ON true
---- Create index to optimize fuerther queries
CREATE INDEX idx_hospital_rs_node_v2 ON hospital_rs_node_v2 USING btree(id);
3.3.2 Carrying out centrality analysis
The graph network obtained from OpenRouteService provided the vertices, edges and weight required for the function pgr_dijkstra. The variable toid and fromid represented the source and target, while weight was the cost. After including this information in the function, the OD matrix included as two arrays completed the required inputs to run the dijkstra algorithm. The table 1 shown the data from this graph network without including the geometry.
The table generated included the ogc_fid variable making possible to join original geometry from the graph network table. This table contained all the shortest paths, where on the one hand, the variables start_vid and end_vid were the source (fromid) and destination (toid) from the original OD matrix. On the other hand, edge indicated the edge id from the graph network.
3.3.2.1 Assessing network centrality
The aggregated count of these shortest path determined the centrality of each segment. A further processing required was to use group these values by the bidirectid, obtaining the final betweenness centrality value. This process applied to the pre-disaster and post-disaster network using the two OD matrix allowed to compare the change of the betweenness centrality.
PostGIS: Calculating edge betweenness centrality on the pre-disaster network
--- 1) Pre-disaster network
--- 1.1) Creating centrality
CREATE TABLE centrality_100_100_dijkstra AS
SELECT b.id,
b.the_geom,count(the_geom) as centrality
FROM pgr_dijkstra('SELECT id,
source,
target,
cost
FROM porto_alegre_net_largest',
ARRAY(SELECT net_id AS start_id FROM weight_sampling_100_origin ),
ARRAY(SELECT net_id AS end_id FROM weight_sampling_100_destination ),
:= TRUE) j
directed left JOIN porto_alegre_street_united AS b
ON j.edge = b.id
GROUP BY b.id, b.the_geom
ORDER BY centrality DESC;
---- Adding the bidirectid by joining the dijkstra table with the original
CREATE TABLE centrality_weighted_100_bidirect AS
SELECT t1.*,
"bidirectid"
t2.FROM
centrality_100_100_dijkstra t1JOIN
porto_alegre_net_largest t2ON
id = t2.id;
t1.--- The final product requries 79941
select max("bidirectid") from centrality_weighted_100_bidirect;
create sequence bididirect_id_weight start 79941;
update centrality_weighted_100_bidirect
set "bidirectid" = nextval('bididirect_id_weight')
where "bidirectid" is null ;
---- verify
select count(*)
from centrality_weighted_100_bidirect
where "bidirectid" is null; --- 0
----
create table centrality_weighted_100_bidirect_group as
select
"bidirectid",
sum(centrality) as centrality
from centrality_weighted_100_bidirect
group by "bidirectid"; --- this sum the centrality for duplicated bidirect id
---
---- now recover the id
create table centrality_weighted_100_bidirect_group_id as
select t1.*, t2.id
from centrality_weighted_100_bidirect_group t1
join centrality_weighted_100_bidirect t2
on t1."bidirectid" = t2."bidirectid";
---- add geometries
create table centrality_weighted_100_bidirect_cleaned as
select t1.*,
t2.the_geom,
t2.target,source,
t2.cost,
t2."unidirectid"
t2.from
centrality_weighted_100_bidirect_group_id t1join
porto_alegre_net_largest t2on
id = t2.id;
t1.--- The final product is: centrality_weighted_100_bidirect_cleaned
--- 2) Post scenario
----routing
CREATE TABLE centrality_100_100_dijkstra_post AS
SELECT b.id,
b.the_geom,count(the_geom) as centrality
FROM pgr_dijkstra('SELECT id,
source,
target,
cost
FROM prueba_largest_network_post',
ARRAY(SELECT net_id AS start_id FROM weight_sampling_100_origin_post ),
ARRAY(SELECT net_id AS end_id FROM weight_sampling_100_destination_post ),
:= TRUE) j
directed left JOIN prueba_largest_network_post AS b
ON j.edge = b.id
GROUP BY b.id, b.the_geom
ORDER BY centrality DESC;
---- Adding the bidirectid by joining the dijkstra table with the original
CREATE TABLE centrality_weighted_100_bidirect_post AS
SELECT t1.*,
"bidirectid"
t2.FROM
centrality_100_100_dijkstra_post t1JOIN
prueba_largest_network_post t2ON
id = t2.id;
t1.--- The final product requries 79918
select max("bidirectid") from centrality_weighted_100_bidirect_post; ---79918
create sequence bididirect_id_weight_post start 79918;
update centrality_weighted_100_bidirect_post
set "bidirectid" = nextval('bididirect_id_weight_post')
where "bidirectid" is null ;
---- verify
select count(*)
from centrality_weighted_100_bidirect_post
where "bidirectid" is null; --- 0
----
create table centrality_weighted_100_bidirect_group_post as
select
"bidirectid",
sum(centrality) as centrality
from centrality_weighted_100_bidirect_post
group by "bidirectid"; --- this sum the centrality for duplicated bidirect id
---
---- now recover the id
create table centrality_weighted_100_bidirect_group_post_id as
select t1.*, t2.id
from centrality_weighted_100_bidirect_group_post t1
join centrality_weighted_100_bidirect_post t2
on t1."bidirectid" = t2."bidirectid";
---- add geometries
create table centrality_weighted_100_bidirect_cleaned_post as
select t1.*,
t2.the_geom,
t2.target,source,
t2.cost,
t2."unidirectid"
t2.from
centrality_weighted_100_bidirect_group_post_id t1join
prueba_largest_network_post t2on
id = t2.id; t1.
3.3.2.2 Assessing hospitals centrality
A subset of 474 points from the weighted sampling was the origin table (source or fromid), while 22 hospitals within the AoI were the destination table (target or toid). Similarly to the network, counting the number of geometries returned the centrality value. However, for the hospital centrality centrality values were also grouped by the destination, named as end_vid. Applying this process to the pre-network and post-network with their respective OD matrix allowed us to assess the change of centrality on the healthcare facilities.
Show the code
------ PRE-EVENT
---- Based on a max of 100*100 / 22 = 454
SELECT count(*) FROM hospital_rs_node_v2; --- 22 POI: Hospitals
----- Create origin
CREATE TABLE weight_sampling_454_origin AS
WITH porto_454_origin AS (
SELECT
*
FROM
od_2728_snapped ORDER BY random()
LIMIT 454)
SELECT * FROM porto_454_origin;
--- Create destinations in this case hospitals
CREATE TABLE hospital_rs_destination AS
SELECT
cd_cnes,
ds_cnes,id,
geom_nodeFROM
--- 22 POI: Hospitals
hospital_rs_node_v2;
---- Create hospitald destinations from closeness
CREATE TABLE hospital_rs_destination AS
WITH clossness_hospital_porto_id AS (
SELECT
*,
n.id
v.FROM
AS n
clossness_hospital_porto JOIN
AS v
porto_alegre_net_largest_vertices_pgr ON st_intersects(n.geom_node, v.the_geom))
SELECT
cd_cnes,
ds_cnes,id,
closeness,
geom_nodeFROM
clossness_hospital_porto_id; ---- Create index for origin
CREATE INDEX weight_sampling_454_origin_net_id ON weight_sampling_454_origin USING hash(net_id);
CREATE INDEX weight_sampling_454_origin_geom ON weight_sampling_454_origin USING gist(the_geom);
---- Create index for destination
CREATE INDEX hospital_rs_destination_net_id ON hospital_rs_destination USING btree(id);
CREATE INDEX hospital_rs_destination_geom ON hospital_rs_destination USING gist(geom_node);
---- Cluster
CLUSTER porto_alegre_net_largest USING idx_porto_alegre_net_largest_geom;
---- Vacuum clean
full, ANALYZE) weight_sampling_454_origin;
VACUUM(full, ANALYZE) hospital_rs_destination;
VACUUM(full, ANALYZE) porto_alegre_net_largest;
VACUUM(---- Running the query
EXPLAIN ANALYZE
CREATE TABLE centrality_424_hospitals_porto_end_id_centrality AS
SELECT
b.vid,
j.end_id,
b.the_geom,count(the_geom) as centrality
FROM pgr_dijkstra('SELECT id,
source,
target,
cost
FROM porto_alegre_net_largest',
ARRAY(SELECT net_id AS origin_id FROM weight_sampling_454_origin),
ARRAY(SELECT id AS destination_id FROM hospital_rs_destination),
:= TRUE) j
directed left JOIN porto_alegre_net_largest AS b
ON j.edge = b.id
GROUP BY b.id, j.end_vid, b.the_geom
ORDER BY centrality DESC;
---- Group by end_vid that represent destination
select * from hospital_rs_destination ;
SELECT * FROM centrality_424_hospitals_porto_end_id_centrality;
SELECT
centrality.end_vid,
hospitals.cd_cnes,
hospitals.ds_cnes,count(centrality.the_geom)::int AS sum_centrality,
max(st_length(centrality.the_geom::geography))::int AS length
FROM
AS centrality
centrality_424_hospitals_porto_end_id_centrality LEFT JOIN
AS hospitals ON centrality.end_vid = hospitals.id
hospital_rs_destination GROUP BY end_vid, cd_cnes, ds_cnes;
-scenario
### Post
SELECT count(*) FROM hospital_rs_node_v2; --- 22 POI: Hospitals
----- Create origin
CREATE TABLE weight_sampling_454_origin_post AS
WITH porto_454_origin_post AS (
SELECT
*
FROM
od_2728_snapped_post ORDER BY random()
LIMIT 454)
SELECT * FROM porto_454_origin_post;
--- Create destinations in this case hospitals
CREATE TABLE hospital_rs_destination AS
SELECT
cd_cnes,
ds_cnes,id,
geom_nodeFROM
--- 22 POI: Hospitals
hospital_rs_node_v2;
---- Create hospitald destinations from closeness
CREATE TABLE hospital_rs_destination AS
WITH clossness_hospital_porto_id AS (
SELECT
*,
n.id
v.FROM
AS n
clossness_hospital_porto JOIN
AS v
porto_alegre_net_largest_vertices_pgr ON st_intersects(n.geom_node, v.the_geom))
SELECT
cd_cnes,
ds_cnes,id,
closeness,
geom_nodeFROM
clossness_hospital_porto_id; ---- Create index for origin
CREATE INDEX weight_sampling_454_origin_net_id ON weight_sampling_454_origin_post USING hash(net_id);
CREATE INDEX weight_sampling_454_origin_geom ON weight_sampling_454_origin_post USING gist(the_geom);
---- Create index for destination
CREATE INDEX hospital_rs_destination_net_id ON hospital_rs_destination USING btree(id);
CREATE INDEX hospital_rs_destination_geom ON hospital_rs_destination USING gist(geom_node);
---- Cluster
CLUSTER porto_alegre_net_largest USING idx_porto_alegre_net_largest_geom;
---- Vacuum clean
full, ANALYZE) weight_sampling_454_origin_post;
VACUUM(full, ANALYZE) hospital_rs_destination;
VACUUM(full, ANALYZE) prueba_largest_network_post;
VACUUM(---- Running the query
EXPLAIN ANALYZE
CREATE TABLE centrality_424_hospitals_porto_end_id_centrality_post AS
SELECT
b.ogc_fid,
j.end_vid,
b.the_geom,count(the_geom) as centrality
FROM pgr_dijkstra('SELECT ogc_fid AS id,
fromid AS source,
toid AS target,
weight AS cost
FROM prueba_largest_network_post',
ARRAY(SELECT net_id AS origin_id FROM weight_sampling_454_origin_post),
ARRAY(SELECT id AS destination_id FROM hospital_rs_destination),
:= TRUE) j
directed left JOIN prueba_largest_network_post AS b
ON j.edge = b.ogc_fid
GROUP BY b.ogc_fid, j.end_vid, b.the_geom
ORDER BY centrality DESC;
---- Group by end_vid that represent destination
CREATE TABLE centrality_424_hospitals_porto_end_id_centrality_post_group AS
SELECT
centrality.end_vid,
hospitals.cd_cnes,
hospitals.ds_cnes,max(centrality.centrality)::int AS max_centrality,
max(st_length(centrality.the_geom::geography))::int AS length
FROM
AS centrality
centrality_424_hospitals_porto_end_id_centrality_post LEFT JOIN
AS hospitals ON centrality.end_vid = hospitals.id
hospital_rs_destination GROUP BY end_vid, cd_cnes, ds_cnes;
3.3.3 Carrying out accessibility analysis
Closenness
3.3.3.1 Asessing hospitals accessibility
The function pgr_dijkstraCostMatrix calculated the sum of the costs of the shortest path for pair combination of nodes in the graph. Grouping the aggregated costs of these cost matrix by origin returned the closeness metrics. Lastly, using the ID of the vertices from the hospital tables the closeness values are included obtaining the most affected healthcare facilities
PostGIS: Calculating closeness for each hospital
--- POST-EVENT
CREATE TABLE weight_sampling_454_origin_post AS
WITH porto_454_origin_post AS (
SELECT
*
FROM
od_2728_snapped_postORDER BY random()
LIMIT 454)
SELECT * FROM porto_454_origin_post;
---- Create index for origin
CREATE INDEX weight_sampling_454_origin_post_net_id ON weight_sampling_454_origin_post USING hash(net_id);
CREATE INDEX weight_sampling_454_origin_post_geom ON weight_sampling_454_origin_post USING gist(the_geom);
---- Running the query
---- Group by end_vid that represent destination
EXPLAIN ANALYZE
CREATE TABLE centrality_424_hospitals_porto_end_id_centrality_post AS
SELECT
id,
b.
j.end_vid,
b.the_geom,count(the_geom) as centrality
FROM pgr_dijkstra('SELECT id,
source,
target,
cost
FROM prueba_largest_network_post',
ARRAY(SELECT net_id AS origin_id FROM weight_sampling_454_origin_post),
ARRAY(SELECT id AS destination_id FROM hospital_rs_destination),
:= TRUE) j
directed LEFT JOIN prueba_largest_network_post AS b
ON j.edge = b.id
GROUP BY b.id, j.end_vid, b.the_geom
ORDER BY centrality DESC;
---- Pre-Event
CREATE TABLE clossness_hospital_porto AS
WITH dijkstra_cost AS (
SELECT * FROM pgr_dijkstraCostMatrix(
'SELECT id, source, target, cost FROM porto_alegre_net_largest',
SELECT array_agg(id)
(FROM porto_alegre_net_largest_vertices_pgr
WHERE id IN (SELECT id FROM hospital_rs_node_v3)),
true)),
AS (
closeness SELECT dc.start_vid,
sum(agg_cost)::int AS closeness
FROM dijkstra_cost dc
GROUP BY dc.start_vid
ORDER BY closeness DESC)
SELECT
h.cd_cnes,
h.ds_cnes,
c.closeness,
h.distance,
h.geom_node,
h.geom_hospitalFROM
AS c
closeness LEFT JOIN
AS h ON c.start_vid = h.id;
hospital_rs_node_v3 --- Post event
CREATE TABLE clossness_hospital_porto_post AS
WITH dijkstra_cost AS (
SELECT * FROM pgr_dijkstraCostMatrix(
'SELECT id, source, target, cost FROM prueba_largest_network_post',
SELECT array_agg(id)
(FROM prueba_largest_network_post_vertices_pgr
WHERE id IN (SELECT id FROM hospital_rs_node_v3)),
true)),
AS (
closeness SELECT dc.start_vid,
sum(agg_cost)::int AS closeness
FROM dijkstra_cost dc
GROUP BY dc.start_vid
ORDER BY closeness DESC)
SELECT
h.cd_cnes,
h.ds_cnes,
c.closeness,
h.distance,
h.geom_node,
h.geom_hospitalFROM
AS c
closeness LEFT JOIN
AS h ON c.start_vid = h.id;
hospital_rs_node_v3
---- Add centrality from post-event to pre-event
CREATE TABLE hospitals_closeness_both AS
SELECT
pre.cd_cnes,
pre.ds_cnes,AS closeness_pre,
pre.closeness coalesce(post.closeness,0) AS closeness_post,
pre.geom_node,
pre.geom_hospitalFROM
AS pre
clossness_hospital_porto LEFT JOIN
AS post ON pre.cd_cnes = post.cd_cnes clossness_hospital_porto_post
3.3.4 Assesing vulnerability based on socioeconomic indicators
3.4 Validating results
Nicht wichtig, vieliecht weg lassen.
---- add ID
ALTER TABLE od_77763
ADD COLUMN id serial;
---- Rename the column name
ALTER TABLE od_77763
rename "GHS_BUILT_V_E2020_GLOBE_R2023A_54009_100_V1_0"
to "build";
---- create spatial index
CREATE INDEX idx_od_77763_geom ON od_77763
USING gist(geometry);
CREATE INDEX idx_od_77763_id on weighted_sampling
USING btree(id);
CREATE INDEX idx_od_77763_geom_build on weighted_sampling
USING btree("build");
---- The current total is 77763
----
CREATE TABLE od_40420_snapped_origin AS
SELECT DISTINCT ON (net.id)
id AS pt_id,
pt.id AS net_id,
net.
net.the_geomFROM
select *
(FROM
as pt) as pt
od_77763 CROSS JOIN
SELECT
LATERAL (*
FROM porto_alegre_net_largest_vertices_pgr AS net
ORDER BY net.the_geom <-> pt.geometry
LIMIT 1) AS net;
----
CREATE TABLE od_40420_snapped_destination AS
SELECT DISTINCT ON (net.id)
id AS pt_id,
pt.id AS net_id,
net.
net.the_geomFROM
select *
(FROM
as pt) as pt
od_77763 CROSS JOIN
SELECT
LATERAL (*
FROM porto_alegre_net_largest_vertices_pgr AS net
ORDER BY net.the_geom <-> pt.geometry
LIMIT 1) AS net;