diff --git a/r/.Rbuildignore b/r/.Rbuildignore
new file mode 100644
index 0000000..687a27e
--- /dev/null
+++ b/r/.Rbuildignore
@@ -0,0 +1,8 @@
+^README\.qmd$
+^input$
+^LICENSE\.md$
+^\.quarto$
+^\.devcontainer$
+^output$
+^intermediate$
+^config\.json$
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/.gitignore b/r/.gitignore
new file mode 100644
index 0000000..638e7a2
--- /dev/null
+++ b/r/.gitignore
@@ -0,0 +1,4 @@
+/.quarto/
+input/
+output/
+intermediate/
diff --git a/r/DESCRIPTION b/r/DESCRIPTION
new file mode 100644
index 0000000..eee7619
--- /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: Apache License (>= 2)
+Encoding: UTF-8
+Roxygen: list(markdown = TRUE)
+RoxygenNote: 7.3.2
+Imports:
+ osmextract,
+ sf
+Suggests:
+ simodels (>= 0.1.0),
+ readr,
+ dplyr,
+ R.utils,
+Depends:
+ R (>= 2.10)
+LazyData: true
+URL: https://urban-analytics-technology-platform.github.io/od2net/r/, https://github.com/urban-analytics-technology-platform/od2net
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
new file mode 100644
index 0000000..1730d8a
--- /dev/null
+++ b/r/NAMESPACE
@@ -0,0 +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
new file mode 100644
index 0000000..7353769
--- /dev/null
+++ b/r/R/setup.R
@@ -0,0 +1,66 @@
+#' 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
+#'
+#' 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"}.
+#' @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
+#' 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",
+ 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 ", output_file, " --overwrite")
+ system(msg)
+}
+
+#' 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, output_file, delete_dsn = TRUE)
+}
diff --git a/r/README.md b/r/README.md
new file mode 100644
index 0000000..1bd6a25
--- /dev/null
+++ b/r/README.md
@@ -0,0 +1,153 @@
+# 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.
+
+## 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
+# 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)
+# 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 = simodels::destinations_york # Provided in the R package
+names(destinations)[1] = "name"
+destinations = destinations[1]
+class(destinations$name) = "character"
+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() |>
+ 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:
+
+``` 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:
+
+``` 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 something like 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..e0a80e4
--- /dev/null
+++ b/r/README.qmd
@@ -0,0 +1,148 @@
+---
+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.
+
+## 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}
+# 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
+# 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)
+# 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 = simodels::destinations_york # Provided in the R package
+names(destinations)[1] = "name"
+destinations = destinations[1]
+class(destinations$name) = "character"
+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() |>
+ 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:
+
+```{r}
+#| output: asis
+readLines("config.json")
+```
+
+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 something like the following in the output folder:
+
+```{r}
+#| eval: false
+fs::dir_tree("output")
+```
+
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
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_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.
+}
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")
+}
+}