GISSB - Netverksanalyse i R

Sindre Mikael Haugen

2023-01-09

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:

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:

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