Met deze rapportage wordt de deelscore op het kenmerk ‘gescheiden voorziening voor fietsers’ van de SPI Veilige Infrastructuur beschikbaar gesteld. De rapportage bevat tevens de programmacode en biedt daarmee een verantwoording voor het bepalen van de deelscore vanuit de beschikbare data.
De lezer die op zoek is naar de deelscore vindt deze onder hoofdstuk 6, ‘Scores en Benchmark SPI Veilige Infrastructuur - kenmerk gescheiden voorziening voor fietsers’. De hoofdstukken daarvoor vormen de meer inhoudelijke en technische verantwoording om tot die score te komen. Deze verantwoording omvat tevens alle programmeercode voor het gepresenteerde resultaat.
Deze rapportage is opgemaakt als R Markdown document (.Rmd) met behulp van R in Rstudio. Het html-bestand is zelfstandig leesbaar. Het voorwoord is bedoeld voor personen die het script (het .Rmd bestand) willen draaien en/of geïnteresseerd zijn in de technische verantwoording van de bepaling van de kenmerkscore gescheiden voorziening voor fietsers van de SPI. Lezers van de rapportage (het html bestand) kunnen dit voorwoord verder overslaan.
Het voorwoord bevat de programmacode voor het controleren van de mappenstructuur, het installeren/inladen van de benodigde packages en het aanmaken van een aantal hulpvariabelen.
# list required libraries
libraries_to_load <- c("here", "tidyverse", "data.table", "dtplyr", "sf", "mapview", "stringr",
"this.path", "units", "kableExtra", "curl", "openxlsx2", "readr", "gt",
"plotly", "qgisprocess", "future.apply", "writexl", "nngeo", "doParallel",
"foreach", "lwgeom", "stplanr", "parallel")
# Install and load libraries if they are missing
for (library_name in libraries_to_load) {
if (!requireNamespace(library_name, quietly = TRUE)) {
install.packages(library_name, dependencies = TRUE, repos = "https://cloud.r-project.org/")
}
library(library_name, character.only = TRUE)
}
# unload here to reload it later after defining the location of the script and the relative path
# for the data downloads and outputs.
detach("package:here", unload = TRUE)
# set locale naar nl in utf8
Sys.setlocale("LC_ALL", "Dutch_Netherlands.utf8")
Bij het runnen van het script (.rmd bestand) worden de benodigde data en resulterende outputs in mappen geplaatst in de map waar het script zich bevindt.
# Define a function for getting the relative path of the script based on the level of the subfolder
# relative to the folder where the script resides. With the script folder level = 0
relative_path_script <- function(level = 0) {
# Split the string by "/"
split_string <- stringr::str_split(this.path::this.path(), "/")[[1]]
# Calculate the starting index for the nth part from the right
start_index <- length(split_string) - level
# Check if the start_index is valid
if (start_index == 1 || start_index > length(split_string)) {
print("The value of level is absolute and not relative, thus not applicable for use with i_am")
}
# Check if the start_index is valid
if (start_index < 1 || start_index > length(split_string)) {
stop("The value of level is out of bounds for the given string.")
}
# Reconstruct the substring from the specified index
subset_string <- paste(split_string[start_index:length(split_string)], collapse = "/")
return(subset_string)
}
# Verwijs here naar de map waarin het script zelf staat met level = 0
here_locatie <- relative_path_script()
#
here::i_am(here_locatie)
library(here)
# controleer de aanwezigheid van '.here'
if (!file.exists(here(".here"))) { # If not, create the folder
file.create(".here")
cat(paste("Bestand '", ".here", "' aangemaakt.\n", sep = ""))
} else {
cat(paste("Bestand '", ".here", "' bestaat al.\n", sep = ""))
}
De code gaat uit van een vaste mappenstructuur. Deze wordt gecontroleerd en aangemaakt als deze ontbreekt.
# folder structure
# Check list of folders
folder_list <- c("Data", "Output", "RDS")
# Check for folders and create missing folders
for (folder_name in folder_list) {
if (!file.exists(here(folder_name))) {
# If not, create the folder
dir.create(here(folder_name))
cat(paste("Folder '", folder_name, "' created.\n", sep = ""))
} else {
cat(paste("Folder '", folder_name, "' already exists.\n", sep = ""))
}
}
Ten slotte worden een aantal hulpfuncties aangemaakt die in het script worden gebruikt.
# helper function to deal with missing values. Missing values as indexer are
# considered to be false.
na.as.false <- function(x){
x[is.na(x)] <- FALSE
x
}
na.as.true <- function(x){
x[is.na(x)] <- TRUE
x
}
# helper function for negate
`%nin%` = Negate(`%in%`)
blue_rgb <- c("39 42 102", "99 94 138",
"136 130 164", "168 163 189",
"206 203 217", "219 216 226")
blue_vals <- sapply(strsplit(blue_rgb, " "), function(x)
rgb(x[1], x[2], x[3], maxColorValue=255))
orange_rgb <- c("216 87 44", "224 126 85",
"233 164 130", "241 197 173",
"247 224 210", "249 233 222")
orange_vals <- sapply(strsplit(orange_rgb, " "), function(x)
rgb(x[1], x[2], x[3], maxColorValue=255))
green_rgb <- c("184 203 51", "204 215 115",
"217 224 152", # 35% komt enkel bij groen voor
"227 232 182", "231 235 191",
"238 241 211", "245 247 231")
green_vals <- sapply(strsplit(green_rgb, " "), function(x)
rgb(x[1], x[2], x[3], maxColorValue=255))
col_comb <- c(blue_vals[1], green_vals[2], orange_vals[3],
blue_vals[4], green_vals[5], orange_vals[6],
blue_vals[2], green_vals[3], orange_vals[4])
long_list <- c(blue_vals[1:3], green_vals[1:3], orange_vals[1:3])
long_list_full <- c(blue_vals[1:6], orange_vals[1:6], green_vals[1:7] )
tabel_preview <- function(data, title = NULL, rows = 9, top_n = 7, bottom_n = 1, incl_rownums = TRUE) {
gt_preview(data, top_n = top_n, bottom_n = bottom_n, incl_rownums = TRUE) %>%
tab_header(title = title) %>%
# Table background color and other style options
tab_options(
#table.background.color = blue_vals[6],
column_labels.background.color = blue_vals[1],
column_labels.font.size = px(16),
table.font.size = px(12),
data_row.padding = px(4)
# Uncomment and set table.width if needed
# table.width = px(250)
) %>%
tab_style(
style = cell_borders(sides = "bottom", color = blue_vals[1], weight = px(3)),
locations = cells_body(rows = rows)
)
}
scroltabel_gt <- function (data, title = NULL, table.height = px(350)) {
gt(data) %>%
# tabel titel
tab_header(title = title) %>%
# tabel opmaak opties
tab_options(
#table.background.color = blue_vals[6],
column_labels.background.color = blue_vals[1],
column_labels.font.size = px(16),
table.font.size = px(12),
data_row.padding = px(4),
table.width = pct(100), # px(250)
container.height = table.height,
container.overflow.y = TRUE # px(250)
) %>%
# tabel opmaak optie onderkant
tab_style(
style = cell_borders(sides = "bottom", color = blue_vals[1], weight = px(3)),
locations = cells_body(rows = nrow(data))
)
}
De SPI Veilige Infrastructuur bevat het kenmerk gescheiden voorziening voor fietsers. Menging van fietsers met motorvoertuigen of zwaar verkeer wordt op wegen met een snelheidslimiet van 50 km/uur, 70 km/uur of 80 km/uur als onveilig beoordeeld gelet op de kwetsbaarheid van fietsers. Fietsers dienen, indien aanwezig in een straat/weg, gebruik te kunnen maken van een vrijliggend fietspad of een parallelweg met een veilige snelheidslimiet. Deze rapportage beschrijft de bepaling van scores van de SPI Veilige Infrastructuur op dit kenmerk en de resultaten daarvan. Een wegvak scoort positief op dit kenmerk wanneer er de rijbaan gesloten is verklaard voor voor fietsers en/of wanneer een gescheiden voorziening voor fietsers aanwezig is. Daaronder vallen een vrijliggend fietspad of een parallelweg met een veilige snelheidslimiet. Per wegbeheerder wordt bepaald welk deel van het wegennet positief scoort. Dit vormt de score van de wegbeheerder op dit kenmerk. Deze score wordt bepaald over het voor dit kenmerk relevante deel van het wegennet.
Een verantwoording van de bepaling van scores wordt mede gegeven door het meeleveren van de programmacode waarmee deze scores zijn bepaald. Meer informatie over de SPI Infrastructuur is te vinden via de website van Aanpak SPV.
In de analyse wordt gebruik gemaakt van het NWB (Nationaal Wegenbestand) en WKD (Wegkenmerkendatabase) data voor snelheidslimieten en verkeerstypen. Deze laatste dataset bevat informatie over geslotenverklaringen voor fietsers op basis van het verkeersbordenbestand. Na het vaststellen van de verkeerstypen zijn deze resultaten door het NDW (Nationaal Dataportaal Wegverkeer) vergeleken met de coderingen zoals beschikbaar in OSM (OpenStreetMap) en met de coderingen zoals beschikbaar in het netwerk (Routeplanner) van de Fietsersbond. Van het NWB worden zowel de wegen als de fietspaden gebruikt. Voor de fietspaden worden ook data gebruikt over de breedtelabels van fietspaden. Dit is openbare data van het NDW.
Documentatie over het NWB is te vinden bij het NDW: https://docs.ndw.nu/handleidingen/nwb/
Documentatie over de WKD is te vinden onder de download pagina van rijkswaterstaatdata https://downloads.rijkswaterstaatdata.nl/wkd/Documentatie/ en via het NDW op https://docs.ndw.nu/en/handleidingen/wkd/
Het update beleid kan verschillen tussen de WKD onderdelen. In ieder geval krijgt elk onderdeel een ‘grote’ jaarlijkse update gekoppeld aan het januari bestand van het NWB. Daarom worden bij het gebruik van de WKD de januari bestanden gebruikt. Deze updates komen in de loop van elk jaar binnen. Een planning daarvoor is nog niet beschikbaar. De laatste updates zijn te vinden op de WKD download-pagina en het NDW Verkeersveiligheid Dataportaal. Berichten hierover worden tevens geplaatst op het Nationaal Toegangspunt Mobiliteitsdata.
Verder wordt er ruimtelijke data gebruikt van de Basisregistratie Adressen en Gebouwen (BAG) en de topografische data van TOP10NL. Uit de BAG worden de gebouwen gebruikt en uit de TOP10NL data over wateroppervlakten.
# -------------KIES JAAR------------------------
# indien gewenst, kies een ander jaar, bijv 2025
# Jaar versie nwb
nwbjaar <- "2025"
# ----------------------------------------------
#' Deze code chunk bevat het jaar waarvan de data wordt gedownload en waarvoor de analyse wordt gedraaid.
#' Het is niet mogelijk om de analyse op jaren voor 2024 te draaien vanwege beperkingen aan de datakwaliteit.
#' Nieuwe januari bestanden komen in de loop van 2025 beschikbaar. Wanneer deze beschikbaar komt,
#' kan het jaar worden aangepast.
De analyse is uitgevoerd op het NWB van januari 2025. Door het NWB-jaar (variabele ‘nwbjaar’) aan te passen in het script worden een aantal van de databronnen updatete naar de versie van dat jaar. Dit geldt echter niet voor de fietsintensiteiten data, de BAG en TOP10NL.
Het NWB is gedownload van: https://downloads.rijkswaterstaatdata.nl/nwb-wegen/geogegevens/shapefile/Nederland_totaal/
# Deze code chunk verzorgt de download en het inladen in R van het NWB.
#' Wanneer het script eerder lokaal is gedraaid en het NWB al is gedownload en verwerkt wordt de .RDS
#' versie van het NWB geladen. Wanneer de RDS niet beschikbaar is, wordt het bestand gedownload en
#' verwerkt.
# rds bestandsnaam op basis van jaar
nwb_rds_naam <- paste0("nwb", nwbjaar, "jan.sf.RDS")
# nwb datum op basis van jaar
nwb_datum <- paste0("01-01-", nwbjaar)
# zip bestandsnaam op basis van jaar
nwb_zip <- paste0("nwb", nwbjaar, "jan.zip")
# conditioneel laden of downloaden en verwerken van het NWB
if (file.exists(here("RDS", nwb_rds_naam))) {
# if file exist load RDS
nwb <- readRDS(here("RDS", nwb_rds_naam))
cat(paste("Bestand geladen uit .RDS"))
} else {
# url for dutch open data
nwbjaarjan_url <- paste0("https://downloads.rijkswaterstaatdata.nl/nwb-wegen/geogegevens/shapefile/Nederland_totaal/01-01-", nwbjaar, ".zip")
# destination file + folder
dest_file_nwbjaarjan <- here("Data", nwb_zip)
dest_folder <- here("Data", paste0("nwb", nwbjaar, "jan"))
# download file
curl_download(nwbjaarjan_url, destfile = dest_file_nwbjaarjan, mode = "wb")
# unzip
unzip(dest_file_nwbjaarjan, exdir = dest_folder)
# load the shapefile
nwb <- st_read(here(dest_folder, nwb_datum, "Wegvakken", "Wegvakken.shp"))
# Save as RDS
saveRDS(nwb, here("RDS", nwb_rds_naam))
# clear zip
file.remove(dest_file_nwbjaarjan)
# code resultaat bericht
cat(paste("Bestand opgehaald van internet van https://downloads.rijkswaterstaatdata.nl/nwb-wegen/"))
}
De analyse maakt gebruik van snelheidslimieten, gekoppeld aan het NWB, uit de WKD van januari 2025. Ook hier geldt dat door het NWB-jaar aan te passen in het script, de volledige analyse op een ander jaar kan worden gedraaid. De snelheidslimieten data is gedownload van: https://downloads.rijkswaterstaatdata.nl/wkd/Maximum%20Snelheden/
# Deze code chunk verzorgt de download en het inladen in R van de wkd snelheidslimieten
#' Wanneer het script eerder lokaal is gedraaid en het NWB al is gedownload en verwerkt wordt de .RDS
#' versie van het bestand geladen. Wanneer de RDS niet beschikbaar is, wordt het bestand gedownload en
#' verwerkt.
# rds bestandsnaam op basis van jaar
maxsnelheden_rds_naam <- paste0("maxsnelheden", nwbjaar, "jan.sf.RDS")
# nwb datum op basis van jaar
maxsnelheden_datum <- paste0("01-01-", nwbjaar)
# zip bestandsnaam op basis van jaar
maxsnelheden_zip <- paste0("maxsnelheden", nwbjaar, "jan.zip")
# conditioneel laden of downloaden en verwerken van de data
if (file.exists(here("RDS", maxsnelheden_rds_naam))) {
# if file exist load RDS
wkd_maxsnelheden <- readRDS(here("RDS", maxsnelheden_rds_naam))
cat(paste("Bestand geladen uit .RDS"))
} else {
# url for dutch open data
maxsnelhedenjaarjan_url <- paste0("https://downloads.rijkswaterstaatdata.nl/wkd/Maximum%20Snelheden/01-01-", nwbjaar, ".zip")
# destination file + folder
dest_file_maxsnelhedenjaarjan <- here("Data", maxsnelheden_zip)
dest_folder <- here("Data", paste0("maxsnelheden", nwbjaar,"jan"))
# download file
curl_download(maxsnelhedenjaarjan_url, destfile = dest_file_maxsnelhedenjaarjan, mode = "wb")
# unzip
unzip(dest_file_maxsnelhedenjaarjan, exdir = dest_folder)
# load the shapefile
wkd_maxsnelheden <- st_read(here(dest_folder, maxsnelheden_datum, "Snelheden.shp"))
# Save as RDS
saveRDS(wkd_maxsnelheden, here("RDS", maxsnelheden_rds_naam))
# clear zip
file.remove(dest_file_maxsnelhedenjaarjan)
# code resultaat bericht
cat(paste("Bestand opgehaald van internet van https://downloads.rijkswaterstaatdata.nl/wkd/"))
}
Om wegen te onderscheiden met een geslotenverklaring voor fietsers, heeft het NDW gebruik gemaakt van de het verkeersbordenbestand en de resultaat gecontroleeerd met de coderingen uit OSM en de Routeplannerdata van de Fietsersbond. Deze informatie is toegevoegd aan de WKD voor Verkeerstypen. De data uit de WKD van januari 2025 wordt gekoppeld aan het NWB. Hier geldt wederom dat door het NWB-jaar aan te passen in het script, de volledige analyse op een ander jaar kan worden gedraaid. De dataset is uitgebreid beschreven op NDW docs. De verkeerstypen-data zijn gedownload van: https://downloads.rijkswaterstaatdata.nl/wkd/Verkeerstypen/
# Deze code chunk verzorgt de download en het inladen in R van de wkd verkeerstypen
#' Wanneer het script eerder lokaal is gedraaid en het NWB al is gedownload en verwerkt wordt de .RDS
#' versie van het bestand geladen. Wanneer de RDS niet beschikbaar is, wordt het bestand gedownload en
#' verwerkt.
# rds bestandsnaam op basis van jaar
verkeerstypen_rds_naam <- paste0("verkeerstypen", nwbjaar, "jan.sf.RDS")
# nwb datum op basis van jaar
verkeerstypen_datum <- paste0("01-01-", nwbjaar)
# zip bestandsnaam op basis van jaar
verkeerstypen_zip <- paste0("verkeerstypen", nwbjaar, "jan.zip")
# conditioneel laden of downloaden en verwerken van de data
if (file.exists(here("RDS", verkeerstypen_rds_naam))) {
# if file exist load RDS
wkd_verkeerstypen <- readRDS(here("RDS", verkeerstypen_rds_naam))
cat(paste("Bestand geladen uit .RDS"))
} else {
# url for dutch open data
verkeerstypenjaarjan_url <- paste0("https://downloads.rijkswaterstaatdata.nl/wkd/Verkeerstypen/01-01-", nwbjaar, ".zip")
# destination file + folder
dest_file_verkeerstypenjaarjan <- here("Data", verkeerstypen_zip)
dest_folder <- here("Data", paste0("verkeerstypen", nwbjaar,"jan"))
# download file
curl_download(verkeerstypenjaarjan_url, destfile = dest_file_verkeerstypenjaarjan, mode = "wb")
# unzip
unzip(dest_file_verkeerstypenjaarjan, exdir = dest_folder)
# load the csv file
wkd_verkeerstypen <- read_csv2(here(dest_folder, verkeerstypen_datum, "wkd_035-VRKRSTPNV2.csv"))
# Save as RDS
saveRDS(wkd_verkeerstypen, here("RDS", verkeerstypen_rds_naam))
# clear zip
file.remove(dest_file_verkeerstypenjaarjan)
# code resultaat bericht
cat(paste("Bestand opgehaald van internet van https://downloads.rijkswaterstaatdata.nl/wkd/"))
}
Voor deze analyse is data van gebouwen nodig, in dit geval polygonen. De data komt uit de Basisregistratie Adressen en Gebouwen (BAG). Hieronder worden de juiste layers ingeladen. Verderop wordt uitgelegd hoe deze data gebruikt worden.
if (file.exists(here("RDS", "bag_gebouwen.sf.RDS"))) {
# if file exist load RDS
bag_gebouwen <- readRDS(here("RDS", "bag_gebouwen.sf.RDS"))
cat(paste("Bestand geladen uit .RDS"))
} else {
# url voor openbare data
bag_gebouwen_url <- "https://service.pdok.nl/lv/bag/atom/downloads/bag-light.gpkg"
# destination file + folder
dest_file_bag_gebouwen <- here("Data", "bag-light.gpkg")
dest_folder <- here("Data")
# download file
curl_download(bag_gebouwen_url, destfile = dest_file_bag_gebouwen, mode = "wb")
# load the buildings gpkg layer
bag_gebouwen <- st_read(dest_file_bag_gebouwen, layer = "pand")
# Save as RDS
saveRDS(bag_gebouwen, here("RDS", "bag_gebouwen.sf.RDS"))
# clear zip
file.remove(dest_file_bag_gebouwen)
}
## Bestand geladen uit .RDS
Voor deze analyse is data van wateroppervlakten nodig, ook polygonen. De data voor wateroppervlakten komt uit de TOP10NL data. Hieronder worden de juiste layers ingeladen. Verderop wordt uitgelegd hoe deze data gebruikt worden.
# wateroppervlakten inladen
if (file.exists(here("RDS", "top10nl_water_mpol.sf.RDS"))) {
# if file exist load RDS
# laad waterdelen
top10nl_water_mpol <- readRDS(here("RDS", "top10nl_water_mpol.sf.RDS"))
} else {
# url for dutch open data
top10nlgml_url <- "https://service.pdok.nl/brt/topnl/atom/downloads/top10nl-gml-nl-nohist.zip"
# destination file + folder
dest_file_top10 <- here("Data", "top10nl_gml.zip")
dest_folder <- here("Data")
# download top10nl
curl_download(top10nlgml_url, destfile = dest_file_top10, mode = "wb")
# unzip
unzip(dest_file_top10, files = "top10nl_waterdeel.gml", exdir = dest_folder)
# load the gml
top10nl_water_gml <- st_read(here("Data", "top10nl_waterdeel.gml"))
# subset polygons and multipolygons and cast all to the type MULTOPOLYGON
top10nl_water_mpol <- top10nl_water_gml[st_geometry_type(top10nl_water_gml) %in% c("POLYGON", "MULTIPOLYGON"), ] %>%
st_cast(., "MULTIPOLYGON") %>%
# set crs 28992 RD new
st_set_crs(., 28992)
# Save as RDS
saveRDS(top10nl_water_mpol, here("RDS", "top10nl_water_mpol.sf.RDS"))
# clear zip
file.remove(dest_file_top10)
# clean
rm(top10nl_water_gml)
}
# registratiegebieden inladen
if (file.exists(here("RDS", "top10nl_reggebied_mpol.sf.RDS"))) {
# if file exist load RDS
# laad waterdelen
top10nl_reggebied_mpol <- readRDS(here("RDS", "top10nl_reggebied_mpol.sf.RDS"))
} else {
# url for dutch open data
top10nlgml_url <- "https://service.pdok.nl/brt/topnl/atom/downloads/top10nl-gml-nl-nohist.zip"
# destination file + folder
dest_file_top10 <- here("Data", "top10nl_gml.zip")
dest_folder <- here("Data")
# download top10nl
curl_download(top10nlgml_url, destfile = dest_file_top10, mode = "wb")
# unzip
unzip(dest_file_top10, files = "top10nl_registratiefgebied.gml", exdir = dest_folder)
# load the gml
top10nl_reggebied_gml <- st_read(here("Data", "top10nl_registratiefgebied.gml"))
# subset polygons and multipolygons and cast all to the type MULTOPOLYGON
top10nl_reggebied_mpol <- top10nl_reggebied_gml[st_geometry_type(top10nl_reggebied_gml) %in% c("POLYGON", "MULTIPOLYGON"), ] %>%
st_cast(., "MULTIPOLYGON") %>%
# set crs 28992 RD new
st_set_crs(., 28992)
# Save as RDS
saveRDS(top10nl_reggebied_mpol, here("RDS", "top10nl_reggebied_mpol.sf.RDS"))
# clear zip
file.remove(dest_file_top10)
# clean
rm(top10nl_reggebied_gml)
}
Als laatste worden de fietsintensiteiten en bijbehorende breedtelabels van NDW ingeladen. Verderop wordt uitgelegd waar deze voor gebruikt worden.
if (file.exists(here("RDS", "fietsintensiteiten.RDS"))) {
# if file exist load RDS
ndw_fietsintensiteiten <- readRDS(here("RDS", "fietsintensiteiten.RDS"))
cat(paste("Bestand geladen uit RDS"))
} else {
# url for dutch open data
fietsintensiteiten_url <- "https://opendata.ndw.nu/2023_fietsintensiteiten.zip"
# destination file + folder
dest_file_fietsintensiteiten <- here("Data", "fietsintensiteiten.zip")
dest_folder <- here("Data", "fietsintensiteiten" )
# download file
curl_download(fietsintensiteiten_url, destfile = dest_file_fietsintensiteiten, mode = "wb")
# unzip
unzip(dest_file_fietsintensiteiten, exdir = dest_folder)
# load the csv
ndw_fietsintensiteiten <- st_read(here(dest_folder, "nwb_breedtelabels.gpkg"))
# Save as RDS
saveRDS(ndw_fietsintensiteiten, here("RDS", "fietsintensiteiten.RDS"))
# clear zip
file.remove(dest_file_fietsintensiteiten)
}
## Bestand geladen uit RDS
#https://opendata.ndw.nu/2023_fietsintensiteiten.zip
#https://opendata.ndw.nu/
De data dient te worden geprepareerd voorafgaand aan de analyses en bepaling van het kenmerk gescheiden voorziening voor fietsers van de SPI Veilige Infrastructuur. De preparatie bevat onder meer het koppelen van de verschillende datasets, het selecteren van de juiste wegvakken in het NWB en het aanmaken van transects.
Na het inladen van de meest recente versie van het NWB kunnen hierin de fietspaden geselecteerd worden. Hier wordt de Baansubsoort-code (BST_CODE) voor gebruikt, welke het type wegvak specificeert (wegvak, fietspad etc.). De verschillende betekenissen van de BST_CODEs kan in de online handleiding gevonden worden: https://docs.ndw.nu/en/handleidingen/nwb/nwb-basisstructuur/baansubsoort/
De BST_CODE “FP” wordt gebruikt om fietspaden te selecteren.
nwb_fietspaden <- nwb %>%
# selecteer alleen fietspaden
filter(BST_CODE == "FP") %>%
# selecteer aantal relevante variabelen
select(WVK_ID, STT_NAAM, GME_NAAM, WPSNAAM, BST_CODE, RPE_CODE, RIJRICHTNG, WEGBEHSRT) %>%
# bereken de wegvaklengte
mutate(lengte_fietspad = as.numeric(st_length(.)))
Om de juiste data te kunnen analyseren, is het van belang om het NWB te koppelen aan de snelheidslimieten en de verkeerstypen. Voor de snelheidslimieten geldt dat we ons richten op wegen met een limiet van 50, 70 of 80 km/uur. Er zijn wegvakken waarop meerdere snelheidslimieten aanwezig zijn. Hiervoor bepalen we de maatgevende limiet door de snelheidslimiet te selecteren die op het grootste deel van het wegvak geldt. Voor verkeerstypen geldt dat we ons richten op een geslotenverklaring voor fietsers. Met een koppeling tussen deze databronnen kan deze filtering uitgevoerd worden.
# Eerst worden de snelheidslimieten uit de WKD aan het NWB gekoppeld op basis van de WVK_ID
nwb_snelheid <- nwb %>%
# we maken als eerste een variabele aan die de lengte van een wegvak weergeeft
mutate(wvk_lengte = as.numeric(st_length(.))) %>%
#st_drop_geometry() %>%
left_join(select(st_drop_geometry(wkd_maxsnelheden), MAXSHD, VAN, NAAR, WVK_ID), "WVK_ID") %>%
# initiate dtplyr
as.data.frame() %>%
lazy_dt() %>%
# vervolgens wordt bepaald op welk deel van een wegvak de snelheidslimiet geldt
mutate(shd_lengte = NAAR - VAN) %>%
# met deze lengte wordt de maatgevende limiet vooor een wegvak bepaald, omdat een wegvak meerdere limieten kan hebben
# Hiervoor groeperen we per WVK_ID en nemen we de snelheidlimiet die op het langste deel van het wegvak geldt
group_by(WVK_ID) %>%
mutate(shd_maatgevend = MAXSHD[which.max(shd_lengte)]) %>%
# sorteer op lengte limiet
arrange(desc(shd_lengte)) %>%
# bewaar het record met de maximale lengte van de wkd limiet segmenten
slice_head(n=1) %>%
ungroup() %>%
select(c(WVK_ID, JTE_ID_BEG, JTE_ID_END, WEGBEHSRT, BST_CODE, RIJRICHTNG, STT_NAAM, WPSNAAM, GME_NAAM,
WEGBEHNAAM, MAXSHD, wvk_lengte, shd_lengte, shd_maatgevend, geometry)) %>%
# stop dtplyr
as.data.frame() %>%
# herinitieer sf obv de geometry kolom
st_sf()
# Als tweede wordt de informatie over geslotenverklaringen aan de hierboven aangemaakte dataset gekoppeld
nwb_snelheid_gesloten_fiets <- nwb_snelheid %>%
# Alleen gesloten verklarnig voor fietsers wordt meegenomen
left_join(select(wkd_verkeerstypen, FIETS_H, FIETS_T, WVK_ID), "WVK_ID") %>%
mutate(rijrichting = ifelse(RIJRICHTNG == "H" | RIJRICHTNG == "T", "eenrichting", "tweerichting")) %>%
# filter op codes die we niet tot de rijbaan rekenen
filter(BST_CODE %nin% c("FP", "VP", "CADO", "VZ", "VD", "VDA", "VDF", "VDV", "RP", "VV", "PP", "BVP"))
Het uitgangspunt voor deze SPI is dat wegen met een 50, 70 en 80 km/u limiet een geslotenverklaring hebben voor fietsers. In deze stap worden de wegvakken met deze limieten geselecteerd. Daarnaast wordt een variabele aangemaakt die aangeeft of een wegvak een geslotenverklaring voor fietsers heeft. Op dit moment is per rijrichting bepaald of er een geslotenverklaring is. Er zijn echter situaties waarin de ene rijrichting wel gesloten is voor fietsers en de andere niet. In deze situaties wordt het gehele wegvak beschouwd als opengesteld voor fietsers omdat er tenminste in een rijrichting fietsers op de rijbaan rijden.
In deze analyse laten we wegvakken met de volgende BST_CODEs buiten beschouwing: “FP”, “VP”, “CADO”, “VZ”, “VD”, “VDA”, “VDF”, “VDV”, “RP”, “VV”, “PP”, “BVP”. Deze codes zijn gekozen om een aantal NA-probelemen met de koppeling van het NWB en WKD Verkeerstypen op te lossen.
nwb_50_70_80_gesloten_fiets <- nwb_snelheid_gesloten_fiets %>%
# filter snelheden
filter(shd_maatgevend %in% c("50", "70", "80")) %>%
# creëer variabele voor geslotenverklaring met 1 = gesloten voor fieters
mutate(gesloten_fiets = case_when(
# beide richtingen gesloten voor fiets
na.as.false(FIETS_H == "N") & na.as.false(FIETS_T == "N") ~ 1,
# 1 richting straat met heenrichting gesloten voor fiets en andere richting open of onbekend
na.as.false(FIETS_H == "N") & na.as.true(FIETS_T == "J") & rijrichting == "eenrichting" ~ 1,
# 1 richting straat met terugrichting gesloten voor fiets en andere richting open of onbekend
na.as.true(FIETS_H == "J") & na.as.false(FIETS_T == "N") & rijrichting == "eenrichting" ~ 1,
.default = 0
),
# variabele om te testen welk verschil het meenemen van de rijtichtingen in het beoordelen van
# de gesloten verklaring maakt
gesloten_fiets2 = case_when(
# beide richtingen gesloten voor fiets
na.as.false(FIETS_H == "N") & na.as.false(FIETS_T == "N") ~ 1,
.default = 0
))
test_gesloten <- table(nwb_50_70_80_gesloten_fiets$gesloten_fiets, nwb_50_70_80_gesloten_fiets$rijrichting)
# test_gesloten
test2_gesloten <- table(nwb_50_70_80_gesloten_fiets$gesloten_fiets2, nwb_50_70_80_gesloten_fiets$rijrichting)
# test2_gesloten
nwb_50_70_80_gesloten_fiets <- select(nwb_50_70_80_gesloten_fiets, -gesloten_fiets2)
Transects zijn loodlijnen die op wegvakken geplaatst worden. In deze analyse zijn de loodlijnen 30 meter lang en worden ze om de 10 meter op een wegvak geplaatst. Met deze transects wordt gekeken of deze een vrijliggend fietspad of parallelweg naast een wegvak kruisen. Zo kan beoordeeld worden of er een vrijliggend fietspad of parallelweg met een veilige snelheidslimiet van 30 km/uur bij het wegvak hoort om deze informatie later toe te schrijven aan het betreffende wegvak.
In deze stap worden transects aangemaakt die gebruikt worden in vervolgstappen. Deze stap duurt vaak lang omdat hij erg intensief is voor het geheugen. Geadviseerd wordt om een krachtige computer voor deze stap te gebruiken.
Transects zijn loodlijnen van 30 meter lang welke om de 10 meter aan beide kanten op een wegvak gezet worden. In vervolgstappen worden deze transects afgeknipt op plekken waar zich gebouwen of wateroppervlakten binnen 30 meter van het wegvak bevinden.
LET OP: Wees ervan bewust dat dit een intensieve en tijdrovende klus is.
# let op, dit script kan meerdere dagen nodig hebben
# We kijken eerst of de transects al eerder zijn aangemaakt
if (all(file.exists(here("RDS", c("transectsL.rds", "transectsR.rds"))))) {
# if file exist load RDS
transectsL <- readRDS(here("RDS", "transectsL.rds"))
transectsR <- readRDS(here("RDS", "transectsR.rds"))
cat(paste("Bestanden geladen uit .RDS"))
#Zo niet, dan gaan we door met het aanmaken van de transects
} else {
# bepaal de gewenste transect lengte in m
transect_lengte_benodigd <- 30
# Bepaal de segmentlength waarin het wegvak wordt opgesplits. De afstand tussen transecten is tevens gelijk aan de segementlengte
segmentlengte <- 5
# afstand waarover transects worden uitgezet vanaf het beginpunt van het wegvak gerekend
transect_afstand <- 10
# groepsgrootte van wegvaken die tegelijkertijd berekend worden
groepsgrootte <- 50
# creëer groepsid
nwb_id <- nwb_50_70_80_gesloten_fiets %>%
mutate(groupid = (row_number()-1) %/% groepsgrootte + 1) %>%
relocate(geometry, last_col())
# groepeer ids voor gelijktijdige berekening
nwb_groupid <- unique(nwb_id$groupid)
# Bepaal paralelle clusters voor foreach
n.cores <- detectCores() - 2
my.cluster <- makeCluster(n.cores, type = "PSOCK")
registerDoParallel(my.cluster)
transectsLR <- foreach(i = seq_along(nwb_groupid), #1:length(NWB_UTR_groupid)
.combine = function(...) rbindlist(list(...)),
.multicombine = TRUE,
.packages = c("dplyr", "sf", "nngeo", "lwgeom", "stplanr")) %dopar% {
# initieer transects dataset
transectsLR_par <- filter(nwb_id, groupid %in% nwb_groupid[i]) %>%
# selecteer beperkte selectie aan variabelen
#select(groupid, limiet, STT_NAAM, WPSNAAMNEN, WPSNAAM) %>%
# Bepaal segmentlengte per wegvak voor hele wegvaklengte
mutate(segmentlengte_wvkid = ifelse(wvk_lengte <= 50, wvk_lengte/2, segmentlengte)) %>%
# Verdichting an wegvakken met vertices voor elk x meter per segmentlengte_wvkid, behalve als wegvakken korter of even lang als 50m zijn
st_segmentize(., dfMaxLength = segmentlengte) %>%
# splits het wegvak op basis van vertices
st_segments() %>%
# creëer segment ids per groep
group_by(WVK_ID) %>%
mutate(segmentID = row_number(),
# Afstand van het startpunt van het wegvak naar de transect
transect_sp_afstand = segmentlengte + (segmentID * segmentlengte)) %>%
# Selecter segmentIDs
# Voor korte wegvakenn selecteer segmentID dichtbij het midden en op zijn minst de eerste
filter((wvk_lengte <= 50 & segmentID == max(round(wvk_lengte/2/segmentlengte),1)) |
# Voor langere wegvakken selecteer segmentID's minimaal 25 meter van een node
(segmentID >= (25/segmentlengte) &
segmentID <= round((wvk_lengte-25) / segmentlengte) & segmentID %% 5 == 0)
) %>%
ungroup() %>%
# Bepaal geometrie van de segmenten: beginpunt, eindpunt, middelpunt, oriëntatie
mutate(XYcentrum = st_geometry(st_centroid(.)), # middelpunt of centrum lijnsegment
XYstart = st_startpoint(.), # beginpunt lijnsegment
XYend = st_endpoint(.), # eindpunt lijnsegment
# Bepaal de lijn oriëntatie met line_bearing. De functie bepaalt de hoek ten opzichte van een noordelijke
# richting (0 graden) met een hoek tussen de -180 to 180 graden, waarbij een oostwaartse richting positief is
# en een westwaardse richting negatief. De functie werkt niet met crs 28992, wel met 4326.
orientatie = line_bearing(st_transform(., crs = 4326), bidirectional = FALSE),
# Vorm een 360 graden hoek. hier vormen alle lijnen het beginpunt op het kruispunt. Dus van het kruispunt af
hoek360 = case_when(
orientatie >= 0 ~ orientatie,
orientatie < 0 ~ orientatie + 360,
TRUE ~ NA_real_
),
lengte = as.numeric(st_length(.)),
extend = transect_lengte_benodigd #
) %>%
rowwise() %>%
# bepaal de ligging van de punten van de transects
mutate(
# door 90 graden bij de oriëntatie van het segment op te tellen zetten we het nieuwe punt haaks op het segment
hoek = hoek360 + 90,
R_Xend = XYcentrum[[1]][1] + extend * sin(hoek * pi / 180), # dX = sin(alpha) * extend
R_Yend = XYcentrum[[1]][2] + extend * cos(hoek * pi / 180),
L_Xend = XYcentrum[[1]][1] - extend * sin(hoek * pi / 180),
L_Yend = XYcentrum[[1]][2] - extend * cos(hoek * pi / 180)) %>%
ungroup()
# creëer nieuwe XY start en eindpunt
transectsLR_par$R_XYend <- st_geometry(st_as_sf(select(as.data.frame(transectsLR_par), R_Xend, R_Yend),
coords = c("R_Xend", "R_Yend")))
transectsLR_par$L_XYend <- st_geometry(st_as_sf(select(as.data.frame(transectsLR_par), L_Xend, L_Yend),
coords = c("L_Xend", "L_Yend")))
# initialiseer data frame met correct aantal rijen
geom_transectLR <- data.frame(1:nrow(transectsLR_par))
# crëer transects vanaf nieuwe begin- en eindpunten
for (j in 1:nrow(transectsLR_par)) {
pairL <- st_combine(c(st_set_crs(transectsLR_par$XYcentrum[j], 28992), st_set_crs(transectsLR_par$L_XYend[j], 28992)))
lineL <- st_cast(pairL, "LINESTRING")
geom_transectLR$geom_transectL[j] <- st_geometry(lineL)
pairR <- st_combine(c(st_set_crs(transectsLR_par$XYcentrum[j], 28992), st_set_crs(transectsLR_par$R_XYend[j], 28992)))
lineR <- st_cast(pairR, "LINESTRING")
geom_transectLR$geom_transectR[j] <- st_geometry(lineR)
}
# creëer sf om een goede geometrie kolom te krijgen
geom_transectL <- st_sf(select(geom_transectLR, geom_transectL)) %>%
st_set_crs(28992)
geom_transectR <- st_sf(select(geom_transectLR, geom_transectR)) %>%
st_set_crs(28992)
# Voeg nieuwe geometrie kolom to aan originele dataset
transectsLR_par$geom_transectL <- geom_transectL$geom_transectL
transectsLR_par$geom_transectR <- geom_transectR$geom_transectR
# resultaat per iteratie
# Sommige opdrachten leveren geen hits op. We willen geen lege objecten als resultaat opvoeren omdat daarmee
# conflicten tussen variabelen typen ontstaan. Daarom gebruiken we rm() om object bij return te verwijderen
if (nrow(transectsLR_par) == 0) {
NULL
} else {
transectsLR_par
}
}
# Merk de nieuwe geometrie kolom aan als geom kolom in aparte transectL en transectR objecten
transectsL <- as.data.frame(transectsLR) %>%
st_sf(., sf_column_name = "geom_transectL", crs = 28992) #%>%
#select(-geom)
transectsR <- as.data.frame(transectsLR) %>%
st_sf(., sf_column_name = "geom_transectR", crs = 28992) #%>%
#select(-geom)
rm(geom_transectL, geom_transectR, transectsLR)
# Beëindig parallel cluster
stopCluster(cl = my.cluster)
# Opslaan voor later gebruik
saveRDS(transectsL, here("RDS", "transectsL.RDS"))
saveRDS(transectsR, here("RDS", "transectsR.RDS"))
}
## Bestanden geladen uit .RDS
Voordat de transects in de volgende stap afgeknipt worden bekijken we eerst of het berekenen van de transects goed is gegaan. Dit doen we voor één gemeente.
# Selecteer NWB in IJsselstein
nwb_gemeente_50plus <- nwb_snelheid_gesloten_fiets %>%
filter(GME_NAAM == "IJsselstein" & MAXSHD %in% c("50", "70", "80"))
nwb_gemeente_30min <- nwb_snelheid_gesloten_fiets %>%
filter(GME_NAAM == "IJsselstein" & MAXSHD %in% c("15", "30"))
# Selecteer fietspaden in IJsselstein
nwb_fietspaden_gemeente <- nwb_fietspaden %>%
filter(GME_NAAM == "IJsselstein")
# Filter transects voor gemeente IJsselstein
transectsL_gemeente <- transectsL %>%
filter(GME_NAAM == "IJsselstein")
transectsR_gemeente <- transectsR %>%
filter(GME_NAAM == "IJsselstein")
mapview(nwb_gemeente_50plus, color = "purple") +
mapview(nwb_gemeente_30min, color = "black") +
mapview(nwb_fietspaden_gemeente, color = "orange") +
mapview(transectsL_gemeente, color = "blue") +
mapview(transectsR_gemeente, color = "red")