From e3c1df179d08447804b3ac480c4b7b1c96cd21a7 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Wed, 4 Sep 2024 19:28:38 +0100 Subject: [PATCH 01/10] Add minimal R package (#67) --- r/.gitignore | 1 + r/DESCRIPTION | 23 +++++++++++ r/NAMESPACE | 2 + r/R/setup.R | 90 +++++++++++++++++++++++++++++++++++++++++ r/README.md | 65 +++++++++++++++++++++++++++++ r/README.qmd | 84 ++++++++++++++++++++++++++++++++++++++ r/man/make_elevation.Rd | 24 +++++++++++ r/man/make_zones.Rd | 17 ++++++++ 8 files changed, 306 insertions(+) create mode 100644 r/.gitignore create mode 100644 r/DESCRIPTION create mode 100644 r/NAMESPACE create mode 100644 r/R/setup.R create mode 100644 r/README.md create mode 100644 r/README.qmd create mode 100644 r/man/make_elevation.Rd create mode 100644 r/man/make_zones.Rd diff --git a/r/.gitignore b/r/.gitignore new file mode 100644 index 0000000..075b254 --- /dev/null +++ b/r/.gitignore @@ -0,0 +1 @@ +/.quarto/ diff --git a/r/DESCRIPTION b/r/DESCRIPTION new file mode 100644 index 0000000..abaeea4 --- /dev/null +++ b/r/DESCRIPTION @@ -0,0 +1,23 @@ +Package: od2net +Title: What the Package Does (One Line, Title Case) +Version: 0.0.0.9000 +Authors@R: + person("First", "Last", , "first.last@example.com", role = c("aut", "cre"), + comment = c(ORCID = "YOUR-ORCID-ID")) +Description: What the package does (one paragraph). +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.2 +Imports: + dplyr, + osmextract, + R.utils, + readr, + sf +Suggests: + simodels (>= 0.1.0) +Depends: + R (>= 2.10) +LazyData: true +URL: https://acteng.github.io/od2net/ diff --git a/r/NAMESPACE b/r/NAMESPACE new file mode 100644 index 0000000..6ae9268 --- /dev/null +++ b/r/NAMESPACE @@ -0,0 +1,2 @@ +# Generated by roxygen2: do not edit by hand + diff --git a/r/R/setup.R b/r/R/setup.R new file mode 100644 index 0000000..c54e644 --- /dev/null +++ b/r/R/setup.R @@ -0,0 +1,90 @@ +# Aim: generate input data for od2net with R + +#' Generate a 'zones.geojson' file +#' +#' This function requires a zones file, e.g. +#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" +#' or a file on your computer. +#' It will generate a file in the input/ folder +#' +#' @param file Location or URL of zones file +make_zones = function(file) { + zones = sf::read_sf(file)[1] + names(zones)[1] = "name" + sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +} + +getbbox_from_zones = function() { + zones = sf::st_read("input/zones.geojson") + bbox = sf::st_bbox(zones) + paste0(bbox, collapse = ",") +} + +make_osm = function(force_download = FALSE) { + zones = sf::read_sf("input/zones.geojson") + zones_union = sf::st_union(zones) + osmextract_match = osmextract::oe_match(place = zones_union) + osmextract::oe_download(file_url = osmextract_match$url, download_directory = "input", force_download = force_download) + input_pbf = list.files(path = "input", pattern = basename(osmextract_match$url), full.names = TRUE) + bb = getbbox_from_zones() + msg = paste0("osmium extract -b ", bb, " ", input_pbf, " -o input/input.osm.pbf --overwrite") + system(msg) +} + +#' Get elevation data +#' +#' This function downloads elevation data from a source such as +#' https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz +#' or https://assets.od2net.org/input/LisboaIST_10m_4326.tif +#' +#' @param url Full URL of the elevation dataset if available +#' @param file File name if hosted on a known site +#' @param base_url Base URL associated with the 'file' argument +#' +make_elevation = function( + url = NULL, + file = "UK-dem-50m-4326.tif.gz", + base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" + ) { + if (is.null(url)) { + url = paste0(base_url, file) + } + is_gzip = grepl(pattern = "gz", url) + # Download the file + if (!file.exists("input/elevation.tif") && is_gzip) { + download.file( + url = url, + destfile = "input/elevation.tif.gz" + ) + R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") + } else { + download.file( + url = url, + destfile = "input/elevation.tif" + ) + } +} + +make_origins = function() { + buildings = sf::read_sf("input/input.osm.pbf", query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL") + use_sf = sf::sf_use_s2(FALSE) + centroids = sf::st_centroid(buildings) + sf::sf_use_s2(use_sf) + sf::write_sf(centroids, "input/buildings.geojson", delete_dsn = TRUE) +} + +make_od = function() { + od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") + od = od |> + dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) + readr::write_csv(od, "input/od.csv") +} + +main = function() { + dir.create("input", showWarnings = FALSE) + make_zones() + make_osm() + # make_elevation() + make_origins() + make_od() +} diff --git a/r/README.md b/r/README.md new file mode 100644 index 0000000..e480e4d --- /dev/null +++ b/r/README.md @@ -0,0 +1,65 @@ +# Preparing OD data for network generation with od2net + + +This R package provides functions to prepare OD data for network +generation with the `od2net` tool, as illustrated in the example below. + +``` r +source("R/setup.R") +dir.create("input", showWarnings = FALSE) +make_zones("https://github.com/acteng/netgen/raw/main/input/zones_york.geojson") +make_osm() +make_origins() +make_elevation() +destinations = destinations_york # Provided in the R package +names(destinations)[1] = "name" +destinations = destinations[1] +class(destinations$name) = "character" +sf::write_sf(destinations, "https://github.com/acteng/netgen/raw/main/input/destinations.geojson", delete_dsn = TRUE) +# Save the OD dataset: +od = od_geo |> + sf::st_drop_geometry() |> + transmute(from = O, to = as.character(D), count = round(trips_modelled)) +readr::write_csv(od, "input/od.csv", quote = "all") +``` + +Then create a config.json file, e.g. with the following content: + +``` json +{ + "requests": { + "description": "Test data for SchoolRoutes project.", + "pattern": { + "ZoneToPoint": { + "zones_path": "zones.geojson", + "destinations_path": "destinations.geojson", + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false + } + }, + "origins_path": "buildings.geojson", + "destinations_path": "destinations.geojson" + }, + "cost": "Distance", + "uptake": "Identity", + "lts": "BikeOttawa", + "elevation_geotiff": "elevation.tif" +} +``` + +Then run the following code to generate the network: + +Run the tool with Docker as follows: + +``` bash +# On Linux: +sudo docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json +# or in Windows: +sudo docker run -v ${pwd}:/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json +``` + +After that you should see the following in the output folder: + +``` r +fs::dir_tree("output") +``` diff --git a/r/README.qmd b/r/README.qmd new file mode 100644 index 0000000..85da2a1 --- /dev/null +++ b/r/README.qmd @@ -0,0 +1,84 @@ +--- +title: Preparing OD data for network generation with od2net +#| eval: false +#| echo: false +format: gfm +execute: + message: false + warning: false +--- + +This R package provides functions to prepare OD data for network generation with the `od2net` tool, as illustrated in the example below. + + +```{r} +#| eval: false +source("R/setup.R") +dir.create("input", showWarnings = FALSE) +make_zones("https://github.com/acteng/netgen/raw/main/input/zones_york.geojson") +make_osm() +make_origins() +make_elevation() +destinations = destinations_york # Provided in the R package +names(destinations)[1] = "name" +destinations = destinations[1] +class(destinations$name) = "character" +sf::write_sf(destinations, "https://github.com/acteng/netgen/raw/main/input/destinations.geojson", delete_dsn = TRUE) +# Save the OD dataset: +od = od_geo |> + sf::st_drop_geometry() |> + transmute(from = O, to = as.character(D), count = round(trips_modelled)) +readr::write_csv(od, "input/od.csv", quote = "all") +``` + +Then create a config.json file, e.g. with the following content: + +```json +{ + "requests": { + "description": "Test data for SchoolRoutes project.", + "pattern": { + "ZoneToPoint": { + "zones_path": "zones.geojson", + "destinations_path": "destinations.geojson", + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false + } + }, + "origins_path": "buildings.geojson", + "destinations_path": "destinations.geojson" + }, + "cost": "Distance", + "uptake": "Identity", + "lts": "BikeOttawa", + "elevation_geotiff": "elevation.tif" +} +``` + +Then run the following code to generate the network: + +Run the tool with Docker as follows: + + +```{bash} +#| eval: false +# On Linux: +sudo docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json +# or in Windows: +sudo docker run -v ${pwd}:/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json +``` + +```{r} +#| eval: false +#| echo: false +system("docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json") +``` + +After that you should see the following in the output folder: + +```{r} +#| eval: false +fs::dir_tree("output") +``` + + diff --git a/r/man/make_elevation.Rd b/r/man/make_elevation.Rd new file mode 100644 index 0000000..af111f8 --- /dev/null +++ b/r/man/make_elevation.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{make_elevation} +\alias{make_elevation} +\title{Get elevation data} +\usage{ +make_elevation( + url = NULL, + file = "UK-dem-50m-4326.tif.gz", + base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" +) +} +\arguments{ +\item{url}{Full URL of the elevation dataset if available} + +\item{file}{File name if hosted on a known site} + +\item{base_url}{Base URL associated with the 'file' argument} +} +\description{ +This function downloads elevation data from a source such as +https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz +or https://assets.od2net.org/input/LisboaIST_10m_4326.tif +} diff --git a/r/man/make_zones.Rd b/r/man/make_zones.Rd new file mode 100644 index 0000000..bfcedee --- /dev/null +++ b/r/man/make_zones.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{make_zones} +\alias{make_zones} +\title{Generate a 'zones.geojson' file} +\usage{ +make_zones(file) +} +\arguments{ +\item{file}{Location or URL of zones file} +} +\description{ +This function requires a zones file, e.g. +"https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" +or a file on your computer. +It will generate a file in the input/ folder +} From e6be6eb2bd6db0595b36ab2ee62cb5ac0cdf42dd Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:03:16 +0100 Subject: [PATCH 02/10] Add URL --- r/DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r/DESCRIPTION b/r/DESCRIPTION index abaeea4..cf6d735 100644 --- a/r/DESCRIPTION +++ b/r/DESCRIPTION @@ -20,4 +20,4 @@ Suggests: Depends: R (>= 2.10) LazyData: true -URL: https://acteng.github.io/od2net/ +URL: https://urban-analytics-technology-platform.github.io/od2net/r/, https://github.com/urban-analytics-technology-platform/od2net From 2e54269447645403e841ce2e7990edada2548692 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:05:01 +0100 Subject: [PATCH 03/10] Port make_zones() to README, style file --- r/R/setup.R | 53 ++++++++++++++++++---------------------------------- r/README.qmd | 29 +++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/r/R/setup.R b/r/R/setup.R index c54e644..ce05477 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -1,27 +1,11 @@ -# Aim: generate input data for od2net with R - -#' Generate a 'zones.geojson' file -#' -#' This function requires a zones file, e.g. -#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" -#' or a file on your computer. -#' It will generate a file in the input/ folder -#' -#' @param file Location or URL of zones file -make_zones = function(file) { - zones = sf::read_sf(file)[1] - names(zones)[1] = "name" - sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) -} - getbbox_from_zones = function() { zones = sf::st_read("input/zones.geojson") bbox = sf::st_bbox(zones) paste0(bbox, collapse = ",") } -make_osm = function(force_download = FALSE) { - zones = sf::read_sf("input/zones.geojson") +make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { + zones = sf::read_sf(zones_file) zones_union = sf::st_union(zones) osmextract_match = osmextract::oe_match(place = zones_union) osmextract::oe_download(file_url = osmextract_match$url, download_directory = "input", force_download = force_download) @@ -32,37 +16,36 @@ make_osm = function(force_download = FALSE) { } #' Get elevation data -#' +#' #' This function downloads elevation data from a source such as #' https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz #' or https://assets.od2net.org/input/LisboaIST_10m_4326.tif -#' +#' #' @param url Full URL of the elevation dataset if available #' @param file File name if hosted on a known site #' @param base_url Base URL associated with the 'file' argument -#' +#' make_elevation = function( url = NULL, file = "UK-dem-50m-4326.tif.gz", - base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" - ) { + base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/") { if (is.null(url)) { url = paste0(base_url, file) } is_gzip = grepl(pattern = "gz", url) # Download the file - if (!file.exists("input/elevation.tif") && is_gzip) { - download.file( - url = url, - destfile = "input/elevation.tif.gz" - ) - R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") - } else { - download.file( - url = url, - destfile = "input/elevation.tif" - ) - } + if (!file.exists("input/elevation.tif") && is_gzip) { + download.file( + url = url, + destfile = "input/elevation.tif.gz" + ) + R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") + } else { + download.file( + url = url, + destfile = "input/elevation.tif" + ) + } } make_origins = function() { diff --git a/r/README.qmd b/r/README.qmd index 85da2a1..b332280 100644 --- a/r/README.qmd +++ b/r/README.qmd @@ -10,10 +10,18 @@ execute: This R package provides functions to prepare OD data for network generation with the `od2net` tool, as illustrated in the example below. +## Example + +Imagine you want to generate a route network with values on the links representing the number of pupils/parents on their way to school each school day. The following code shows how to prepare the data for the `od2net` tool. + ```{r} #| eval: false -source("R/setup.R") +# Install pak if not already installed: +if (!requireNamespace("pak", quietly = TRUE)) { + install.packages("pak") +} +pak::pkg_install("robinlovelace/od2net/r@67-r-port") dir.create("input", showWarnings = FALSE) make_zones("https://github.com/acteng/netgen/raw/main/input/zones_york.geojson") make_osm() @@ -81,4 +89,23 @@ After that you should see the following in the output folder: fs::dir_tree("output") ``` +Helper functions + +```{r} +# Aim: generate input data for od2net with R + +#' Generate a 'zones.geojson' file +#' +#' This function requires a zones file, e.g. +#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" +#' or a file on your computer. +#' It will generate a file in the input/ folder +#' +#' @param file Location or URL of zones file +make_zones = function(file) { + zones = sf::read_sf(file)[1] + names(zones)[1] = "name" + sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +} + From 0ff2e2051fa9d1a9361dfcd4d3e6325fbdefd62f Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:09:12 +0100 Subject: [PATCH 04/10] More tweaks to pkg --- r/R/setup.R | 19 ++----------------- r/README.qmd | 13 +++++++++++++ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/r/R/setup.R b/r/R/setup.R index ce05477..49320a9 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -24,7 +24,8 @@ make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") #' @param url Full URL of the elevation dataset if available #' @param file File name if hosted on a known site #' @param base_url Base URL associated with the 'file' argument -#' +#' @return NULL +#' @export make_elevation = function( url = NULL, file = "UK-dem-50m-4326.tif.gz", @@ -55,19 +56,3 @@ make_origins = function() { sf::sf_use_s2(use_sf) sf::write_sf(centroids, "input/buildings.geojson", delete_dsn = TRUE) } - -make_od = function() { - od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") - od = od |> - dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) - readr::write_csv(od, "input/od.csv") -} - -main = function() { - dir.create("input", showWarnings = FALSE) - make_zones() - make_osm() - # make_elevation() - make_origins() - make_od() -} diff --git a/r/README.qmd b/r/README.qmd index b332280..5785e24 100644 --- a/r/README.qmd +++ b/r/README.qmd @@ -91,6 +91,12 @@ fs::dir_tree("output") Helper functions +
+ +The following functions could be useful when you're preparing the data, and show the kind of data preparation that is required. + + + ```{r} # Aim: generate input data for od2net with R @@ -108,4 +114,11 @@ make_zones = function(file) { sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) } +make_od = function() { + od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") + od = od |> + dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) + readr::write_csv(od, "input/od.csv") +} +``` From c2be2b0974892933e1e4d6e6d3eba25b84dbdc34 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:11:33 +0100 Subject: [PATCH 05/10] Document functions --- r/R/setup.R | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/r/R/setup.R b/r/R/setup.R index 49320a9..84634db 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -1,33 +1,37 @@ -getbbox_from_zones = function() { - zones = sf::st_read("input/zones.geojson") +#' Get bounding box from zones +#' +#' This function reads a GeoJSON file containing zones and calculates the bounding box of the zones. +#' +#' @param zones_file Path to the GeoJSON file containing zones. Default is 'input/zones.geojson'. +#' +#' @return A character string representing the bounding box in the format "xmin, ymin, xmax, ymax". +#' @export +getbbox_from_zones = function(zones_file = "input/zones.geojson") { + zones = sf::st_read(zones_file) bbox = sf::st_bbox(zones) paste0(bbox, collapse = ",") } -make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { - zones = sf::read_sf(zones_file) - zones_union = sf::st_union(zones) - osmextract_match = osmextract::oe_match(place = zones_union) - osmextract::oe_download(file_url = osmextract_match$url, download_directory = "input", force_download = force_download) - input_pbf = list.files(path = "input", pattern = basename(osmextract_match$url), full.names = TRUE) - bb = getbbox_from_zones() - msg = paste0("osmium extract -b ", bb, " ", input_pbf, " -o input/input.osm.pbf --overwrite") - system(msg) -} - -#' Get elevation data +#' make_osm Function +#' +#' This function is used to download and extract OpenStreetMap (OSM) data based on specified zones. +#' +#' @param force_download A logical value indicating whether to force the download of OSM data even if it already exists. Default is \code{FALSE}. +#' @param zones_file The file path or name of the zones file in GeoJSON format. Default is \code{"input/zones.geojson"}. +#' +#' @return This function does not return any value. It downloads and extracts OSM data based on the specified zones. +#' +#' @examples +#' make_osm(force_download = TRUE, zones_file = "input/zones.geojson") #' -#' This function downloads elevation data from a source such as -#' https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz -#' or https://assets.od2net.org/input/LisboaIST_10m_4326.tif +#' @import sf +#' @import osmextract +#' @importFrom osmextract oe_match oe_download +#' @importFrom osmium getbbox_from_zones +#' @importFrom base system #' -#' @param url Full URL of the elevation dataset if available -#' @param file File name if hosted on a known site -#' @param base_url Base URL associated with the 'file' argument -#' @return NULL #' @export -make_elevation = function( - url = NULL, +make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { # Function coderl} file = "UK-dem-50m-4326.tif.gz", base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/") { if (is.null(url)) { From 6f48b48dea0ecbec3ed45044f3bdcc0a10a5fc31 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:11:51 +0100 Subject: [PATCH 06/10] Reformat --- r/R/setup.R | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/r/R/setup.R b/r/R/setup.R index 84634db..684dfb2 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -31,26 +31,15 @@ getbbox_from_zones = function(zones_file = "input/zones.geojson") { #' @importFrom base system #' #' @export -make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { # Function coderl} - file = "UK-dem-50m-4326.tif.gz", - base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/") { - if (is.null(url)) { - url = paste0(base_url, file) - } - is_gzip = grepl(pattern = "gz", url) - # Download the file - if (!file.exists("input/elevation.tif") && is_gzip) { - download.file( - url = url, - destfile = "input/elevation.tif.gz" - ) - R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") - } else { - download.file( - url = url, - destfile = "input/elevation.tif" - ) - } +make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { + zones = sf::read_sf(zones_file) + zones_union = sf::st_union(zones) + osmextract_match = osmextract::oe_match(place = zones_union) + osmextract::oe_download(file_url = osmextract_match$url, download_directory = "input", force_download = force_download) + input_pbf = list.files(path = "input", pattern = basename(osmextract_match$url), full.names = TRUE) + bb = getbbox_from_zones() + msg = paste0("osmium extract -b ", bb, " ", input_pbf, " -o input/input.osm.pbf --overwrite") + system(msg) } make_origins = function() { From dc140363d9bd43119e5f7ae70e44edf77c8a941b Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:17:11 +0100 Subject: [PATCH 07/10] Re-document --- r/.devcontainer/devcontainer.json | 39 +++++++++++++++++++++++++++++++ r/NAMESPACE | 2 ++ r/R/setup.R | 20 ++++++++-------- r/man/getbbox_from_zones.Rd | 17 ++++++++++++++ r/man/make_elevation.Rd | 24 ------------------- r/man/make_osm.Rd | 30 ++++++++++++++++++++++++ r/man/make_zones.Rd | 17 -------------- 7 files changed, 98 insertions(+), 51 deletions(-) create mode 100644 r/.devcontainer/devcontainer.json create mode 100644 r/man/getbbox_from_zones.Rd delete mode 100644 r/man/make_elevation.Rd create mode 100644 r/man/make_osm.Rd delete mode 100644 r/man/make_zones.Rd diff --git a/r/.devcontainer/devcontainer.json b/r/.devcontainer/devcontainer.json new file mode 100644 index 0000000..fc3e717 --- /dev/null +++ b/r/.devcontainer/devcontainer.json @@ -0,0 +1,39 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "Debian", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "ghcr.io/geocompx/docker:rust", + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Add the IDs of extensions you want installed when the container is created. + // Including reditorsupport.r and the quarto extension. + "extensions": [ + "reditor-support.r", + "quarto.quarto", + "ms-python.python", + "ms-toolsai.jupyter" + ] + } + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" + + // These are now set in the Dockerfile: + // https://github.com/geocompx/docker/blob/master/rust/Dockerfile + // "postCreateCommand": [ + // "bash .devcontainer/install-additional-dependencies.sh" + // ] + +} diff --git a/r/NAMESPACE b/r/NAMESPACE index 6ae9268..54e3111 100644 --- a/r/NAMESPACE +++ b/r/NAMESPACE @@ -1,2 +1,4 @@ # Generated by roxygen2: do not edit by hand +export(getbbox_from_zones) +export(make_osm) diff --git a/r/R/setup.R b/r/R/setup.R index 684dfb2..ce57a31 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -18,27 +18,27 @@ getbbox_from_zones = function(zones_file = "input/zones.geojson") { #' #' @param force_download A logical value indicating whether to force the download of OSM data even if it already exists. Default is \code{FALSE}. #' @param zones_file The file path or name of the zones file in GeoJSON format. Default is \code{"input/zones.geojson"}. +#' @param output_file The file path or name of the output OSM file in PBF format. Default is "input/input.osm.pbf". #' #' @return This function does not return any value. It downloads and extracts OSM data based on the specified zones. #' #' @examples -#' make_osm(force_download = TRUE, zones_file = "input/zones.geojson") -#' -#' @import sf -#' @import osmextract -#' @importFrom osmextract oe_match oe_download -#' @importFrom osmium getbbox_from_zones -#' @importFrom base system -#' +#' if (file.exists("input/zones.geojson")) { +#' make_osm(force_download = TRUE, zones_file = "input/zones.geojson") +#' } #' @export -make_osm = function(force_download = FALSE, zones_file = "input/zones.geojson") { +make_osm = function( + force_download = FALSE, + zones_file = "input/zones.geojson", + output_file = "input/input.osm.pbf" + ) { zones = sf::read_sf(zones_file) zones_union = sf::st_union(zones) osmextract_match = osmextract::oe_match(place = zones_union) osmextract::oe_download(file_url = osmextract_match$url, download_directory = "input", force_download = force_download) input_pbf = list.files(path = "input", pattern = basename(osmextract_match$url), full.names = TRUE) bb = getbbox_from_zones() - msg = paste0("osmium extract -b ", bb, " ", input_pbf, " -o input/input.osm.pbf --overwrite") + msg = paste0("osmium extract -b ", bb, " ", input_pbf, " -o ", output_file, " --overwrite") system(msg) } diff --git a/r/man/getbbox_from_zones.Rd b/r/man/getbbox_from_zones.Rd new file mode 100644 index 0000000..0e89a8e --- /dev/null +++ b/r/man/getbbox_from_zones.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{getbbox_from_zones} +\alias{getbbox_from_zones} +\title{Get bounding box from zones} +\usage{ +getbbox_from_zones(zones_file = "input/zones.geojson") +} +\arguments{ +\item{zones_file}{Path to the GeoJSON file containing zones. Default is 'input/zones.geojson'.} +} +\value{ +A character string representing the bounding box in the format "xmin, ymin, xmax, ymax". +} +\description{ +This function reads a GeoJSON file containing zones and calculates the bounding box of the zones. +} diff --git a/r/man/make_elevation.Rd b/r/man/make_elevation.Rd deleted file mode 100644 index af111f8..0000000 --- a/r/man/make_elevation.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/setup.R -\name{make_elevation} -\alias{make_elevation} -\title{Get elevation data} -\usage{ -make_elevation( - url = NULL, - file = "UK-dem-50m-4326.tif.gz", - base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" -) -} -\arguments{ -\item{url}{Full URL of the elevation dataset if available} - -\item{file}{File name if hosted on a known site} - -\item{base_url}{Base URL associated with the 'file' argument} -} -\description{ -This function downloads elevation data from a source such as -https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz -or https://assets.od2net.org/input/LisboaIST_10m_4326.tif -} diff --git a/r/man/make_osm.Rd b/r/man/make_osm.Rd new file mode 100644 index 0000000..10a7af9 --- /dev/null +++ b/r/man/make_osm.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{make_osm} +\alias{make_osm} +\title{make_osm Function} +\usage{ +make_osm( + force_download = FALSE, + zones_file = "input/zones.geojson", + output_file = "input/input.osm.pbf" +) +} +\arguments{ +\item{force_download}{A logical value indicating whether to force the download of OSM data even if it already exists. Default is \code{FALSE}.} + +\item{zones_file}{The file path or name of the zones file in GeoJSON format. Default is \code{"input/zones.geojson"}.} + +\item{output_file}{The file path or name of the output OSM file in PBF format. Default is "input/input.osm.pbf".} +} +\value{ +This function does not return any value. It downloads and extracts OSM data based on the specified zones. +} +\description{ +This function is used to download and extract OpenStreetMap (OSM) data based on specified zones. +} +\examples{ +if (file.exists("input/zones.geojson")) { + make_osm(force_download = TRUE, zones_file = "input/zones.geojson") +} +} diff --git a/r/man/make_zones.Rd b/r/man/make_zones.Rd deleted file mode 100644 index bfcedee..0000000 --- a/r/man/make_zones.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/setup.R -\name{make_zones} -\alias{make_zones} -\title{Generate a 'zones.geojson' file} -\usage{ -make_zones(file) -} -\arguments{ -\item{file}{Location or URL of zones file} -} -\description{ -This function requires a zones file, e.g. -"https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" -or a file on your computer. -It will generate a file in the input/ folder -} From 0a66a15ee14ff3c9559a46560a66325e3169c803 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:36:39 +0100 Subject: [PATCH 08/10] Changes for all R pkg checks to pass --- r/.Rbuildignore | 5 ++ r/.gitignore | 1 + r/DESCRIPTION | 10 +-- r/LICENSE.md | 194 ++++++++++++++++++++++++++++++++++++++++++ r/NAMESPACE | 1 + r/R/setup.R | 21 ++++- r/man/make_origins.Rd | 26 ++++++ 7 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 r/.Rbuildignore create mode 100644 r/LICENSE.md create mode 100644 r/man/make_origins.Rd diff --git a/r/.Rbuildignore b/r/.Rbuildignore new file mode 100644 index 0000000..2a2d5c2 --- /dev/null +++ b/r/.Rbuildignore @@ -0,0 +1,5 @@ +^README\.qmd$ +^input$ +^LICENSE\.md$ +^\.quarto$ +^\.devcontainer$ diff --git a/r/.gitignore b/r/.gitignore index 075b254..d97264f 100644 --- a/r/.gitignore +++ b/r/.gitignore @@ -1 +1,2 @@ /.quarto/ +input/ diff --git a/r/DESCRIPTION b/r/DESCRIPTION index cf6d735..eee7619 100644 --- a/r/DESCRIPTION +++ b/r/DESCRIPTION @@ -5,18 +5,18 @@ Authors@R: person("First", "Last", , "first.last@example.com", role = c("aut", "cre"), comment = c(ORCID = "YOUR-ORCID-ID")) Description: What the package does (one paragraph). -License: MIT + file LICENSE +License: Apache License (>= 2) Encoding: UTF-8 Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 Imports: - dplyr, osmextract, - R.utils, - readr, sf Suggests: - simodels (>= 0.1.0) + simodels (>= 0.1.0), + readr, + dplyr, + R.utils, Depends: R (>= 2.10) LazyData: true diff --git a/r/LICENSE.md b/r/LICENSE.md new file mode 100644 index 0000000..b62a9b5 --- /dev/null +++ b/r/LICENSE.md @@ -0,0 +1,194 @@ +Apache License +============== + +_Version 2.0, January 2004_ +_<>_ + +### Terms and Conditions for use, reproduction, and distribution + +#### 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +#### 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +#### 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +#### 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. + +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +#### 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +#### 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +#### 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +#### 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +#### 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +### APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets `[]` replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same “printed page” as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/r/NAMESPACE b/r/NAMESPACE index 54e3111..1730d8a 100644 --- a/r/NAMESPACE +++ b/r/NAMESPACE @@ -1,4 +1,5 @@ # Generated by roxygen2: do not edit by hand export(getbbox_from_zones) +export(make_origins) export(make_osm) diff --git a/r/R/setup.R b/r/R/setup.R index ce57a31..7353769 100644 --- a/r/R/setup.R +++ b/r/R/setup.R @@ -42,10 +42,25 @@ make_osm = function( system(msg) } -make_origins = function() { - buildings = sf::read_sf("input/input.osm.pbf", query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL") +#' make_origins function +#' +#' This function reads an OpenStreetMap file, selects the multipolygons with a non-null building attribute, +#' calculates the centroids of the selected buildings, and writes the resulting centroids to a GeoJSON file. +#' +#' @param osm_file The file path or name of the OSM file in PBF format. Default is "input/input.osm.pbf". +#' @param query The SQL query to select the multipolygons with a non-null building attribute. Default is "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL". +#' @param output_file The file path or name of the output GeoJSON file containing the centroids of the selected buildings. Default is "input/buildings.geojson". +#' +#' @return None +#' @export +make_origins = function( + osm_file = "input/input.osm.pbf", + query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL", + output_file = "input/buildings.geojson" + ) { + buildings = sf::read_sf(osm_file, query = query) use_sf = sf::sf_use_s2(FALSE) centroids = sf::st_centroid(buildings) sf::sf_use_s2(use_sf) - sf::write_sf(centroids, "input/buildings.geojson", delete_dsn = TRUE) + sf::write_sf(centroids, output_file, delete_dsn = TRUE) } diff --git a/r/man/make_origins.Rd b/r/man/make_origins.Rd new file mode 100644 index 0000000..3eabb2a --- /dev/null +++ b/r/man/make_origins.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{make_origins} +\alias{make_origins} +\title{make_origins function} +\usage{ +make_origins( + osm_file = "input/input.osm.pbf", + query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL", + output_file = "input/buildings.geojson" +) +} +\arguments{ +\item{osm_file}{The file path or name of the OSM file in PBF format. Default is "input/input.osm.pbf".} + +\item{query}{The SQL query to select the multipolygons with a non-null building attribute. Default is "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL".} + +\item{output_file}{The file path or name of the output GeoJSON file containing the centroids of the selected buildings. Default is "input/buildings.geojson".} +} +\value{ +None +} +\description{ +This function reads an OpenStreetMap file, selects the multipolygons with a non-null building attribute, +calculates the centroids of the selected buildings, and writes the resulting centroids to a GeoJSON file. +} From c698bccc421e544022d086915f9f1de4d54adf02 Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:53:38 +0100 Subject: [PATCH 09/10] Document, ready for review --- r/.Rbuildignore | 3 + r/.gitignore | 2 + r/README.md | 144 ++++++++++++++++++++++++++++++++++++++---------- r/README.qmd | 144 ++++++++++++++++++++++++++++-------------------- 4 files changed, 205 insertions(+), 88 deletions(-) diff --git a/r/.Rbuildignore b/r/.Rbuildignore index 2a2d5c2..687a27e 100644 --- a/r/.Rbuildignore +++ b/r/.Rbuildignore @@ -3,3 +3,6 @@ ^LICENSE\.md$ ^\.quarto$ ^\.devcontainer$ +^output$ +^intermediate$ +^config\.json$ diff --git a/r/.gitignore b/r/.gitignore index d97264f..638e7a2 100644 --- a/r/.gitignore +++ b/r/.gitignore @@ -1,2 +1,4 @@ /.quarto/ input/ +output/ +intermediate/ diff --git a/r/README.md b/r/README.md index e480e4d..1bd6a25 100644 --- a/r/README.md +++ b/r/README.md @@ -4,49 +4,136 @@ This R package provides functions to prepare OD data for network generation with the `od2net` tool, as illustrated in the example below. +## Example + +Imagine you want to generate a route network with values on the links +representing the number of pupils/parents on their way to school each +school day. The following code shows how to prepare the data for the +`od2net` tool. + +The following functions could be useful when you’re preparing the data, +and show the kind of data preparation that is required. + +
+ + ``` r -source("R/setup.R") +# Aim: generate input data for od2net with R + +#' Generate a 'zones.geojson' file +#' +#' This function requires a zones file, e.g. +#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" +#' or a file on your computer. +#' It will generate a file in the input/ folder +#' +#' @param file Location or URL of zones file +make_zones = function(file) { + zones = sf::read_sf(file)[1] + names(zones)[1] = "name" + sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +} + +make_od = function() { + od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") + od = od |> + dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) + readr::write_csv(od, "input/od.csv") +} +#' Get elevation data +#' +#' This function downloads elevation data from a source such as +#' https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz +#' or https://assets.od2net.org/input/LisboaIST_10m_4326.tif +#' +#' @param url Full URL of the elevation dataset if available +#' @param file File name if hosted on a known site +#' @param base_url Base URL associated with the 'file' argument +#' +make_elevation = function( + url = NULL, + file = "UK-dem-50m-4326.tif.gz", + base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" + ) { + if (is.null(url)) { + url = paste0(base_url, file) + } + is_gzip = grepl(pattern = "gz", url) + # Download the file + if (!file.exists("input/elevation.tif") && is_gzip) { + download.file( + url = url, + destfile = "input/elevation.tif.gz" + ) + R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") + } else { + download.file( + url = url, + destfile = "input/elevation.tif" + ) + } +} +``` + +
+ +``` r +# Install pak if not already installed: +if (!requireNamespace("pak", quietly = TRUE)) { + install.packages("pak") +} +pak::pkg_install("robinlovelace/od2net/r@67-r-port") dir.create("input", showWarnings = FALSE) -make_zones("https://github.com/acteng/netgen/raw/main/input/zones_york.geojson") -make_osm() -make_origins() +# Get some zones from a URL: +uz = "https://github.com/acteng/netgen/raw/main/input/zones_york.geojson" +make_zones(uz) +sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +make_osm(zones_file = "input/zones.geojson", output_file = "input/input.osm.pbf") +make_origins( + osm_file = "input/input.osm.pbf", + query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL", + output_file = "input/buildings.geojson" +) make_elevation() -destinations = destinations_york # Provided in the R package +destinations = simodels::destinations_york # Provided in the R package names(destinations)[1] = "name" destinations = destinations[1] class(destinations$name) = "character" -sf::write_sf(destinations, "https://github.com/acteng/netgen/raw/main/input/destinations.geojson", delete_dsn = TRUE) +sf::write_sf(destinations, "input/destinations.geojson", delete_dsn = TRUE) +od_geo = sf::read_sf("https://github.com/acteng/netgen/releases/download/v0.1.0/res_output.geojson") # Save the OD dataset: od = od_geo |> sf::st_drop_geometry() |> - transmute(from = O, to = as.character(D), count = round(trips_modelled)) + dplyr::transmute(from = O, to = as.character(D), count = round(trips_modelled)) readr::write_csv(od, "input/od.csv", quote = "all") ``` Then create a config.json file, e.g. with the following content: -``` json -{ - "requests": { - "description": "Test data for SchoolRoutes project.", - "pattern": { - "ZoneToPoint": { - "zones_path": "zones.geojson", - "destinations_path": "destinations.geojson", - "csv_path": "od.csv", - "origin_zone_centroid_fallback": false - } - }, - "origins_path": "buildings.geojson", - "destinations_path": "destinations.geojson" - }, - "cost": "Distance", - "uptake": "Identity", - "lts": "BikeOttawa", - "elevation_geotiff": "elevation.tif" -} +``` r +readLines("config.json") ``` +\[1\] “{” +\[2\] ” "requests": {” +\[3\] ” "description": "Test data for SchoolRoutes project.",” \[4\] ” +"pattern": {” +\[5\] ” "ZoneToPoint": {” +\[6\] ” "zones_path": "zones.geojson",” +\[7\] ” "destinations_path": "destinations.geojson",” +\[8\] ” "csv_path": "od.csv",” +\[9\] ” "origin_zone_centroid_fallback": false” +\[10\] ” }” +\[11\] ” },” +\[12\] ” "origins_path": "buildings.geojson",” +\[13\] ” "destinations_path": "destinations.geojson"” +\[14\] ” },” +\[15\] ” "cost": "Distance",” +\[16\] ” "uptake": "Identity",” +\[17\] ” "lts": "BikeOttawa",” +\[18\] ” "elevation_geotiff": "elevation.tif"” +\[19\] “}” + Then run the following code to generate the network: Run the tool with Docker as follows: @@ -58,7 +145,8 @@ sudo docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2ne sudo docker run -v ${pwd}:/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json ``` -After that you should see the following in the output folder: +After that you should see something like the following in the output +folder: ``` r fs::dir_tree("output") diff --git a/r/README.qmd b/r/README.qmd index 5785e24..e0a80e4 100644 --- a/r/README.qmd +++ b/r/README.qmd @@ -14,6 +14,72 @@ This R package provides functions to prepare OD data for network generation with Imagine you want to generate a route network with values on the links representing the number of pupils/parents on their way to school each school day. The following code shows how to prepare the data for the `od2net` tool. +The following functions could be useful when you're preparing the data, and show the kind of data preparation that is required. + +
+ + + +```{r} +# Aim: generate input data for od2net with R + +#' Generate a 'zones.geojson' file +#' +#' This function requires a zones file, e.g. +#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" +#' or a file on your computer. +#' It will generate a file in the input/ folder +#' +#' @param file Location or URL of zones file +make_zones = function(file) { + zones = sf::read_sf(file)[1] + names(zones)[1] = "name" + sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +} + +make_od = function() { + od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") + od = od |> + dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) + readr::write_csv(od, "input/od.csv") +} +#' Get elevation data +#' +#' This function downloads elevation data from a source such as +#' https://play.abstreet.org/dev/data/input/shared/elevation/UK-dem-50m-4326.tif.gz +#' or https://assets.od2net.org/input/LisboaIST_10m_4326.tif +#' +#' @param url Full URL of the elevation dataset if available +#' @param file File name if hosted on a known site +#' @param base_url Base URL associated with the 'file' argument +#' +make_elevation = function( + url = NULL, + file = "UK-dem-50m-4326.tif.gz", + base_url = "https://play.abstreet.org/dev/data/input/shared/elevation/" + ) { + if (is.null(url)) { + url = paste0(base_url, file) + } + is_gzip = grepl(pattern = "gz", url) + # Download the file + if (!file.exists("input/elevation.tif") && is_gzip) { + download.file( + url = url, + destfile = "input/elevation.tif.gz" + ) + R.utils::gunzip("input/elevation.tif.gz", destname = "input/elevation.tif") + } else { + download.file( + url = url, + destfile = "input/elevation.tif" + ) + } +} +``` + +
+ ```{r} #| eval: false @@ -23,44 +89,35 @@ if (!requireNamespace("pak", quietly = TRUE)) { } pak::pkg_install("robinlovelace/od2net/r@67-r-port") dir.create("input", showWarnings = FALSE) -make_zones("https://github.com/acteng/netgen/raw/main/input/zones_york.geojson") -make_osm() -make_origins() +# Get some zones from a URL: +uz = "https://github.com/acteng/netgen/raw/main/input/zones_york.geojson" +make_zones(uz) +sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) +make_osm(zones_file = "input/zones.geojson", output_file = "input/input.osm.pbf") +make_origins( + osm_file = "input/input.osm.pbf", + query = "SELECT osm_id FROM multipolygons WHERE building IS NOT NULL", + output_file = "input/buildings.geojson" +) make_elevation() -destinations = destinations_york # Provided in the R package +destinations = simodels::destinations_york # Provided in the R package names(destinations)[1] = "name" destinations = destinations[1] class(destinations$name) = "character" -sf::write_sf(destinations, "https://github.com/acteng/netgen/raw/main/input/destinations.geojson", delete_dsn = TRUE) +sf::write_sf(destinations, "input/destinations.geojson", delete_dsn = TRUE) +od_geo = sf::read_sf("https://github.com/acteng/netgen/releases/download/v0.1.0/res_output.geojson") # Save the OD dataset: od = od_geo |> sf::st_drop_geometry() |> - transmute(from = O, to = as.character(D), count = round(trips_modelled)) + dplyr::transmute(from = O, to = as.character(D), count = round(trips_modelled)) readr::write_csv(od, "input/od.csv", quote = "all") ``` Then create a config.json file, e.g. with the following content: -```json -{ - "requests": { - "description": "Test data for SchoolRoutes project.", - "pattern": { - "ZoneToPoint": { - "zones_path": "zones.geojson", - "destinations_path": "destinations.geojson", - "csv_path": "od.csv", - "origin_zone_centroid_fallback": false - } - }, - "origins_path": "buildings.geojson", - "destinations_path": "destinations.geojson" - }, - "cost": "Distance", - "uptake": "Identity", - "lts": "BikeOttawa", - "elevation_geotiff": "elevation.tif" -} +```{r} +#| output: asis +readLines("config.json") ``` Then run the following code to generate the network: @@ -82,43 +139,10 @@ sudo docker run -v ${pwd}:/app ghcr.io/urban-analytics-technology-platform/od2ne system("docker run -v $(pwd):/app ghcr.io/urban-analytics-technology-platform/od2net:main /app/config.json") ``` -After that you should see the following in the output folder: +After that you should see something like the following in the output folder: ```{r} #| eval: false fs::dir_tree("output") ``` -Helper functions - -
- -The following functions could be useful when you're preparing the data, and show the kind of data preparation that is required. - - - -```{r} -# Aim: generate input data for od2net with R - -#' Generate a 'zones.geojson' file -#' -#' This function requires a zones file, e.g. -#' "https://raw.githubusercontent.com/nptscot/npt/main/data-raw/zones_edinburgh.geojson" -#' or a file on your computer. -#' It will generate a file in the input/ folder -#' -#' @param file Location or URL of zones file -make_zones = function(file) { - zones = sf::read_sf(file)[1] - names(zones)[1] = "name" - sf::write_sf(zones, "input/zones.geojson", delete_dsn = TRUE) -} - -make_od = function() { - od = readr::read_csv("https://raw.githubusercontent.com/nptscot/npt/main/data-raw/od_subset.csv") - od = od |> - dplyr::transmute(from = geo_code1, to = geo_code2, count = bicycle) - readr::write_csv(od, "input/od.csv") -} -``` - From 9b919559907c465b3bdc07b92f19aa39036bd97d Mon Sep 17 00:00:00 2001 From: robinlovelace Date: Tue, 10 Sep 2024 10:53:48 +0100 Subject: [PATCH 10/10] Add demo config --- r/config.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 r/config.json diff --git a/r/config.json b/r/config.json new file mode 100644 index 0000000..aafce3b --- /dev/null +++ b/r/config.json @@ -0,0 +1,19 @@ +{ + "requests": { + "description": "Test data for SchoolRoutes project.", + "pattern": { + "ZoneToPoint": { + "zones_path": "zones.geojson", + "destinations_path": "destinations.geojson", + "csv_path": "od.csv", + "origin_zone_centroid_fallback": false + } + }, + "origins_path": "buildings.geojson", + "destinations_path": "destinations.geojson" + }, + "cost": "Distance", + "uptake": "Identity", + "lts": "BikeOttawa", + "elevation_geotiff": "elevation.tif" +} \ No newline at end of file