GISSB - Netverksanalyse i R
Sindre Mikael Haugen
2023-01-09
GISSB_vignette.Rmd
GISSB
er en pakke som inneholder GIS-funksjoner i R. I
denne vignetten demonstreres det hvordan GISSB kan brukes til å
gjennomføre nettverksanalyser i R.
Pakken inneholder følgende funksjoner:
address_to_coords
coords_to_google
vegnett_to_R
coords_to_node
shortest_path_igraph
path_leaflet
shortest_path_cppRouting
Ved å bruke disse funksjonene kan man bl.a. finne koordinater til adresser og beregne kjøretid og -avstand mellom disse ved å bruke vegnettet (NVDB Ruteplan nettverksdatasett). Dette kan være nyttig dersom man f.eks. ønsker å analysere regionale forskjeller i befolkningens tilgang til offentlige tjenester.
Se Hvor lang tid tar det å kjøre til nærmeste fødested? for et eksempel der denne pakken har blitt brukt til å lage offisiell statiatikk.
Finne koordinater til adresser (address_to_coords
)
Funksjonen address_to_coords
bruker Kartverkets adresse-API
for å finne koordinater til adresser. Denne API-en krever nettilgang og
baserer seg på matrikkelen, som er Norges offisielle eiendomsregister og
inneholder alle offisielle adresser med tilhørende koordinater. Ved å
skrive inn postnummer og adresse returneres et sf-objekt
med koordinatene til denne adressen. Dersom det er én eller flere
adresser som ikke finnes i matrikkelen returneres det et datasett med
manglenede verdier for lon/lat for adressene som mangler.
Standardverdi for projeksjonen av koordinatene er satt til CRS 25833. Dette er det offisielle koordinatsystemet i Norge og brukes i de fleste norske kartfiler.
fra <- GISSB::address_to_coords(zip_code = "0177",
address = "Akersveien 26")
fra
#> Simple feature collection with 1 feature and 5 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 262320 ymin: 6650367 xmax: 262320 ymax: 6650367
#> Projected CRS: ETRS89 / UTM zone 33N
#> address zip_code kommunenummer kommunenavn ID geometry
#> 0177 Akersveien 26 0177 0301 OSLO 1 POINT (262320 6650367)
til <- GISSB::address_to_coords(zip_code = "2211",
address = "Otervegen 23")
til
#> Simple feature collection with 1 feature and 5 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 334492 ymin: 6674510 xmax: 334492 ymax: 6674510
#> Projected CRS: ETRS89 / UTM zone 33N
#> address zip_code kommunenummer kommunenavn ID geometry
#> 2211 Otervegen 23 2211 3401 KONGSVINGER 1 POINT (334492 6674510)
Det er også mulig å benytte CRS 4326 for å plassere koordinatene på kart med åpen kildekode (som f.eks. Leaflet) eller for å enklere søke opp koordinater i Google Maps. Koordinatsystemet CRS 4326 er derimot ikke metrisk og man kan derfor ikke måle avstander i meter når man bruker dette. Derfor anbefales det kun å bruke CRS 4326 i forbindelse med visualisering.
For å få koordinatene med CRS 4326 må dette spesifiseres i funksjonen
address_to_coords
. Det er også mulig å angi en vektor med
flere postnummere og adresser for å få oppgitt koordinatene til flere
adresser:
postnummere = c("0177", "2211")
adresser = c("Akersveien 26", "Otervegen 23")
fra_4326 <- GISSB::address_to_coords(zip_code = postnummere,
address = adresser,
crs_out = 4326)
fra_4326
#> Simple feature collection with 2 features and 5 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 10.74685 ymin: 59.92203 xmax: 12.01638 ymax: 60.17383
#> Geodetic CRS: WGS 84
#> address zip_code kommunenummer kommunenavn ID
#> 0177 Akersveien 26 0177 0301 OSLO 1
#> 2211 Otervegen 23 2211 3401 KONGSVINGER 2
#> geometry
#> 0177 POINT (10.74685 59.92203)
#> 2211 POINT (12.01638 60.17383)
Etter å ha omgjort koordinatene til CRS 4326 kan de plasseres på internasjonale kart, her med pakken Leaflet:
leaflet::leaflet(width = "100%") %>%
leaflet::addTiles() %>%
leaflet::addMarkers(data = fra_4326$geometry)
Søke opp koordinater i Google Maps
(coords_to_google
)
Når man skal kombinere flere kartfiler er det viktig at alle filene
er angitt med samme koordinatsystem. Om man derfor ønsker å beholde
sf-objektet med CRS 25833, men ønsker å legge til en kolonne med
koordinatene i CRS 4326 (som er enklere å klippe og lime inn i Google
Maps) kan man bruke funksjonen coords_to_google
. Da legges
det til en kolonne som heter coords_google
.
fra <- GISSB::coords_to_google(fra)
fra$coords_google
#> [1] "59.9220274187717, 10.7468516818247"
til <- GISSB::coords_to_google(til)
til$coords_google
#> [1] "60.1738313973094, 12.0163762410388"
Databehandling av vegnettet (vegnett_to_R
)
For å beregne kjøretid og -avstand mellom ulike koordinater benyttes vegnettet som kan lastes ned fra Geonorge. Når man laster inn vegnettsfilen som et sf-objekt ser den slik ut (OBS: det kan ta opptil 3 minutter å laste inn filen):
vegnett <- sf::read_sf("vegnettRuteplan_FGDB_20210528.gdb", 'ERFKPS')
ggplot2::ggplot() +
ggplot2::geom_sf(data = vegnett)
Vegnettet består av punkter som er plassert ca. 50 meter fra hverandre. Disse punktene kalles for noder. Lenkene som forbinder nodene/punktene sammen heter edges.
For at vegnettet skal kunne brukes til nettverksanalyser i R må
dataene behandles og formatet omgjøres (med funksjoner bl.a. fra pakkene
igraph og cppRouting).
Denne databehandlingen av vegnettet er samlet i funksjonen
vegnett_to_R
. Resultatet er en liste med følgende
objekter:
- [1]
graph
(tbl_graph) - [2]
nodes
(sf) - [3]
edges
(data.frame) - [4]
graph_cppRouting_minutes
(cppRouting) - [5]
graph_cppRouting_meters
(cppRouting)
Objektene nodes
og edges
benyttes for å
plassere koordinatene til sitt nærmeste punkt i vegnettet med funksjonen
coords_to_node
.
Objektet graph
benyttes for å beregne korteste
kjøretid/-vei i meter eller minutter med funksjonen
shortest_path_igraph
. Denne benyttes også for å vise hvilke
noder i hvilken rekkefølge som utgjør den korteste kjøreruten mellom to
punkter i vegnettet.
Objektet graph_cppRouting_minutes
benyttes for å beregne
korteste kjøretid i minutter med funksjonen
shortest_path_cppRouting
. Tilsvarende benyttes objektet
graph_cppRouting_meters
for å beregne korteste kjøreavstand
i meter.
vegnett_list <- vegnett_to_R(vegnett = vegnett_sampledata,
year = 2021,
fromnodeID = "FROMNODEID",
tonodeID = "TONODEID",
FT_minutes = "FT_MINUTES",
TF_minutes = "TF_MINUTES",
meters = "SHAPE_LENGTH")
graph <- vegnett_list[[1]]
nodes <- vegnett_list[[2]]
edges <- vegnett_list[[3]]
graph_cppRouting_minutes <- vegnett_list[[4]]
graph_cppRouting_meters <- vegnett_list[[5]]
Merk at vegnettsfilene fra Geonorge endret struktur og kolonnenavn i
2022. Standardverdiene for funksjonen vegnett_to_R
er
gyldige for den nye strukturen, mens for vegnett fra 2021 og tidligere
må følgende argumenter spesifiseres: year = 2021
,
fromnodeID = "FROMNODEID"
,
tonodeID = "TONODEID"
,
FT_minutes = "FT_MINUTES"
,
TF_minutes = "TF_MINUTES"
og
meters = "SHAPE_LENGTH"
.
Det er også mulig å legge til svingrestriksjoner (“turn
restrictions”) i vegnettet. Dette finnes som et separat kartlag i
.gdb-filene man laster ned fra Geonorge. Disse filene inneholder flere
feil (?) og derfor anbefales det ikke å ta disse i bruk (før problemene
har blitt løst). Om man allikevel ønsker å teste med svingrestriksjoner
må man laste inn kartlaget i et objekt som heter
turnrestrictions_geom
og sette argumentet
turn_restrictions = TRUE
i funksjonen
vegnett_to_R
.
Knytte koordinater til vegnettet (coords_to_node
)
For å kunne beregne kjøretid/-vei fra et valgt start- og stoppunkt
(uten å vite hvilken node-ID disse har) må man knytte adressene man
ønsker å analysere til deres nærmeste punkt i vegnettet. Dette gjøres
ved å finne hvilken node som er den nærmeste et gitt koordinatpunkt (X
og Y) i vegnettet. Ved å bruke funksjonen knn
fra pakken nabor finner man det
nærmeste punktet i meter (målt i luftlinje) fra et gitt koordinatpunkt.
knn = 1
betyr at man kun er interessert i å lokalisere det
nærmeste punktet til koordinatene og dette er satt som standardverdi i
funksjonen coords_to_node
. Dersom denne settes til et annet
tall (gjøres ved å endre argumentet knn = X
i funksjonen)
finner man det nærmeste punktet (knn = 1
), to nærmeste
punktene (knn = 2
), tre nærmeste punktene
(knn = 3
) osv. Dette kan være nyttig dersom det nærmeste
punktet i vegnettet ikke finner en gyldig rute pga. enveiskjørte veier
eller andre brudd i vegnettet.
I funksjonen coords_to_node
legger man inn et sf-objekt
(med CRS 25833) som inneholder koordinatene til ett eller flere punkter.
Resultatet er et objekt der node-ID-en til den nærmeste noden til
koordinatene målt i luftlinje har blitt lagt til
(from_node
/to_node
). I tillegg viser kolonnen
dist_coord_node_from
/dist_coord_node_to
hvor
mange meter det er i luftlinje mellom koordinatene og det nærmeste
punktet i vegnettet til hvert koordinatpunkt. Dette kan være nyttig for
å avgjøre om det er enkelte koordinater som har blitt tildelt noder som
er for langt unna den opprinnelige plasseringen (og som man dermed kan
vurdere å utelate fra den videre analysen). Det kan også være lurt å
legge til en ekstra kostnad for avstanden mellom koordinatene til en
adresse og den nærmeste noden i vegnettet. F.eks. kan man regne ut hvor
lang tid det tar å kjøre avstanden i 30 km/t i minutter ved å gange
avstanden i
dist_coord_node_from
/dist_coord_node_to
med
0,002. Man bør også gange selve avstanden først med et tall høyere enn 1
(f.eks. 1,5) for å kompensere for svinger i veien ettersom det sjelden
er mulig å reise i luftlinje.
For å benytte funksjonen coords_to_node
må objektene
nodes
og edges
(som lages med funksjonen
vegnett_to_R
) være lastet inn.
from_node <- GISSB::coords_to_node(coords = fra, direction = "from")
from_node
to_node <- GISSB::coords_to_node(coords = til, direction = "to")
to_node
Vegnettet består av flere mindre deler/vegnett der flere av disse
ikke har noen tilknytning til hverandre. Dersom man har et startpunkt
som tilhører en annen del av vegnettet enn stoppunktet vil det ikke være
mulig å finne en gyldig rute mellom disse. Ved å legge til argumentet
membership = TRUE
begrenses listen over hvilke noder man
leter etter til å kun gjelde noder som er tilknyttet samme del av
vegnettet som enten start- eller stoppnoden. Dette kan bare gjøres for
enten “from” eller “to” og koden som har angitt argumentet
membership = FALSE
må kjøres før koden som har
membership = TRUE
:
from_node <- GISSB::coords_to_node(coords = fra, direction = "from", membership = FALSE)
to_node <- GISSB::coords_to_node(coords = til, direction = "to", membership = TRUE)
Beregne korteste kjøretid og -avstand med tidygraph
(shortest_path_igraph
)
Når man har en node-ID for både et start- og stoppunkt kan man
benytte funksjonen shortest_path_igraph
for å beregne den
korteste kjøreavstanden mellom disse. For å beregne korteste kjøretid i
minutter angis argumentet unit = "minutes"
(standardverdi),
mens unit = "meters"
beregner korteste kjøreavstand i
meter.
For å kjøre koden må objektet graph
(som lages med
funksjonen vegnett_to_R
) være lastet inn.
OBS: shortest_path_igraph
fungerer ikke med vektorer,
dvs. en liste over flere fra- eller til-noder. Med
shortest_path_igraph
er det altså kun mulig å beregne
avstander én til én. For å beregne avstand med flere fra- og/eller
til-noder brukes funksjonen shortest_path_cppRouting
(se
nedenfor).
avstand_min <- GISSB::shortest_path_igraph(from_node_ID = from_node$from_node,
to_node_ID = to_node$to_node,
unit = "minutes",
path = F)
paste0(round(avstand_min$length, digits = 1), " minutter / ",
substr(avstand_min$length/60, 1, 1),
" timer og ",
round(avstand_min$length, digits = 0)-as.numeric(substr(avstand_min$length/60, 1, 1))*60, " minutter")
avstand_meter <- GISSB::shortest_path_igraph(from_node_ID = from_node$from_node,
to_node_ID = to_node$to_node,
unit = "meters",
path = F)
paste0(round(avstand_meter$length, digits = 1), " meter / ",
round(avstand_meter$length/1000, digits = 1), " km."
)
For å få oppgitt hvilke noder i hvilken rekkefølge som utgjør den
korteste ruten mellom start- og stoppunktet legges argumentet
path = TRUE
til:
path <- GISSB::shortest_path_igraph(from_node_ID = from_node$from_node,
to_node_ID = to_node$to_node,
unit = "minutes",
path = TRUE)
path$epath
Det er mulig å visualisere kjøreruter laget med funksjonen
shortest_path_igraph
(der path = TRUE
) ved å
bruke funksjonen path_leaflet
:
GISSB::path_leaflet(path)
Beregne korteste kjøretid og -avstand med cppRouting
(shortest_path_cppRouting
)
Det er mulig å bruke funksjonen shortest_path_igraph
til
å beregne kjøretid/-vei, men dette kan ta veldig lang tid dersom man
skal beregne kjøreruter mellom mange koordinater. Derfor er det best å
heller bruke funksjonen shortest_path_cppRouting
. Denne
funksjonen bruker pakken cppRouting
som benytter et annet format på dataene som krever mindre minne og
benytter parallellprosessering, noe som gjør at kjøringene går mye
raskere. I tillegg lagres ikke informasjonen om selve ruten, kun
avstanden mellom start- og stoppunktene i minutter eller meter. Dette er
en fordel mtp. på tiden det tar å kjøre koden, men ulempen er at man
ikke får sjekket hvilke veglenker hver rute består av. Dette kan derfor
funksjonen shortest_path_igraph
(med
path = TRUE
) brukes til dersom man ønsker å kontrollere
eller visualisere kjøreruter mellom ett start- og stoppunkt.
I funksjonen shortest_path_cppRouting
kan man legge inn
vektorer med flere noder i både argumentene from_node
og
to_node
. Det betyr at dersom man legger inn to noder i
from_node
og fire noder i to_node
beregnes den
korteste kjøreruten fra begge startnodene til alle de fire stoppnodene.
Resultatet blir et objekt med åtte rader. Om man kun er interessert i
den korteste kjøreavstanden fra hver av startnodene til én av
stoppnodene legger man inn argumentet dist = "min"
i
funksjonen. Om man derimot vil ha hvilken av stoppnodene som har lengst
kjøreavstand per startnode settes dist = "max"
.
For å beregne korteste kjøretid i minutter med
shortest_path_cppRouting
settes
graph_cppRouting = graph_cppRouting_minutes
, mens
graph_cppRouting = graph_cppRouting_meters
beregner
korteste kjøreavstand i meter. Objektet/objektene
graph_cppRouting_minutes
/graph_cppRouting_meters
må være lastet inn (gjøres med funksjonen vegnett_to_R
) før
funksjonen shortest_path_cppRouting
kan brukes.
avstand_cpp_min <- GISSB::shortest_path_cppRouting(from_node$from_node,
to_node$to_node,
unit = "minutes",
graph_cppRouting_object = graph_cppRouting_minutes)
avstand_cpp_min
avstand_cpp_meter <- GISSB::shortest_path_cppRouting(from_node$from_node,
to_node$to_node,
unit = "meters",
graph_cppRouting_object = graph_cppRouting_meters)
avstand_cpp_meter
Eksempel: beregnet kjøretid til nærmeste fødested
I dette eksempelet beregnes korteste og lengste kjøretid fra to adresser (“Akersveien 26, 0177” og “Otervegen 23, 2211”) til syv ulike adresser (fødesteder).
adresser <- c("Sykehusveien 25",
"Sognsvannsveien 20",
"Kirkeveien 166",
"Parkvegen 35",
"Kirkevegen 31",
"Sjukehusveien 9",
"Sogneprest Munthe-Kaas vei 100")
postnummere <- c("1474",
"0372",
"0450",
"2212",
"2413",
"2500",
"1346")
til <- GISSB::address_to_coords(zip_code = postnummere,
address = adresser) %>%
GISSB::coords_to_google()
to_node <- GISSB::coords_to_node(coords = til, direction = "to", membership = F)
to_node
fra <- GISSB::address_to_coords(zip_code = c("0177", "2211"),
address = c("Akersveien 26", "Otervegen 23")) %>%
GISSB::coords_to_google()
from_node <- GISSB::coords_to_node(coords = fra, direction = "from", membership = T)
from_node
avstand_cpp <- GISSB::shortest_path_cppRouting(unique(from_node$from_node),
unique(to_node$to_node),
unit = "minutes")
avstand_cpp
avstand_cpp_min <- GISSB::shortest_path_cppRouting(unique(from_node$from_node),
unique(to_node$to_node),
unit = "minutes",
dist = "min")
avstand_cpp_min
avstand_cpp_max <- GISSB::shortest_path_cppRouting(unique(from_node$from_node),
unique(to_node$to_node),
unit = "minutes",
dist = "max")
avstand_cpp_max