Repository: geocompx/geocompr Branch: main Commit: 2cb2a1a6ec6a Files: 204 Total size: 2.0 MB Directory structure: gitextract_34buill7/ ├── .Rbuildignore ├── .binder/ │ ├── Dockerfile │ ├── LICENSE │ └── README.md ├── .devcontainer.json ├── .gitattributes ├── .github/ │ ├── .gitignore │ ├── ISSUE_TEMPLATE.md │ └── workflows/ │ ├── dev-sf.yaml │ ├── main-no-deploy.yml │ ├── main.yaml │ └── qgis-ext.yaml ├── .gitignore ├── .htaccess ├── .lintr ├── .nojekyll ├── .vscode/ │ └── settings.json ├── 01-introduction.Rmd ├── 02-spatial-data.Rmd ├── 03-attribute-operations.Rmd ├── 04-spatial-operations.Rmd ├── 05-geometry-operations.Rmd ├── 06-raster-vector.Rmd ├── 07-reproj.Rmd ├── 08-read-write-plot.Rmd ├── 09-mapping.Rmd ├── 10-gis.Rmd ├── 11-algorithms.Rmd ├── 12-spatial-cv.Rmd ├── 13-transport.Rmd ├── 14-location.Rmd ├── 15-eco.Rmd ├── 16-synthesis.Rmd ├── CITATION.bib ├── CITATION_ed1.bib ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE.md ├── README.Rmd ├── README.md ├── _01-ex.Rmd ├── _02-ex.Rmd ├── _03-ex.Rmd ├── _04-ex.Rmd ├── _05-ex.Rmd ├── _06-ex.Rmd ├── _07-ex.Rmd ├── _08-ex.Rmd ├── _09-ex.Rmd ├── _10-ex.Rmd ├── _11-ex.Rmd ├── _12-ex.Rmd ├── _13-ex.Rmd ├── _14-ex.Rmd ├── _15-ex.Rmd ├── _404.Rmd ├── _bookdown.yml ├── _output.yml ├── _redirects ├── apps/ │ ├── CycleHireApp/ │ │ ├── app.R │ │ └── manifest.json │ └── coffeeApp/ │ ├── app.R │ └── manifest.json ├── benchmarks.csv ├── code/ │ ├── 01-cranlogs.R │ ├── 01-sf-revdep.R │ ├── 02-contpop.R │ ├── 02-datum-fig.R │ ├── 02-raster-crs.R │ ├── 02-raster-intro-plot.R │ ├── 02-raster-intro-plot2.R │ ├── 02-sfdiagram.R │ ├── 02-sfheaders.R │ ├── 02-vector-crs.R │ ├── 02-vectorplots.R │ ├── 03-cont-raster-plot.R │ ├── 04-areal-example.R │ ├── 04-focal-example.R │ ├── 04-local-operations.R │ ├── 04-ndvi.R │ ├── 04-raster-subset.R │ ├── 04-spatial-join.R │ ├── 05-bilinear.R │ ├── 05-extend-example.R │ ├── 05-us-regions.R │ ├── 05-venn-clip.R │ ├── 06-contour-tmap.R │ ├── 06-pointextr.R │ ├── 06-raster-vectorization1.R │ ├── 06-raster-vectorization2.R │ ├── 06-vector-rasterization1.R │ ├── 06-vector-rasterization2.R │ ├── 09-break-styles.R │ ├── 09-layout1.R │ ├── 09-layout2.R │ ├── 09-map-pkgs.R │ ├── 09-tmpal.R │ ├── 09-tmshape.R │ ├── 09-tmstyles.R │ ├── 09-urban-animation.R │ ├── 09-usboundaries.R │ ├── 10-qgis-raster.R │ ├── 10-saga-segments.R │ ├── 10-saga-wetness.R │ ├── 10-sliver.R │ ├── 10-tsp.R │ ├── 11-centroid-alg.R │ ├── 11-centroid-setup.R │ ├── 11-hello.R │ ├── 11-polycent.R │ ├── 12-cv.R │ ├── 12-partitioning.R │ ├── 13-cycleways.R │ ├── 13-desire.R │ ├── 13-transport-data-gen.R │ ├── 13-zones.R │ ├── 14-location-figures.R │ ├── 15-rf_mlr3.R │ ├── add-impact.R │ ├── before_script.R │ ├── benchmark.R │ ├── chapters/ │ │ ├── 01-introduction.R │ │ ├── 02-spatial-data.R │ │ ├── 03-attribute-operations.R │ │ ├── 04-spatial-operations.R │ │ ├── 05-geometry-operations.R │ │ ├── 06-raster-vector.R │ │ ├── 07-reproj.R │ │ ├── 08-read-write-plot.R │ │ ├── 09-mapping.R │ │ ├── 10-gis.R │ │ ├── 11-algorithms.R │ │ ├── 12-spatial-cv.R │ │ ├── 13-transport.R │ │ ├── 14-location.R │ │ ├── 15-eco.R │ │ ├── 16-synthesis.R │ │ ├── README.R │ │ ├── _01-ex.R │ │ ├── _02-ex.R │ │ ├── _03-ex.R │ │ ├── _04-ex.R │ │ ├── _05-ex.R │ │ ├── _06-ex.R │ │ ├── _07-ex.R │ │ ├── _08-ex.R │ │ ├── _10-ex.R │ │ ├── _12-ex.R │ │ ├── _13-ex.R │ │ ├── _15-ex.R │ │ ├── _404.R │ │ ├── index.R │ │ └── references.R │ ├── de_9im.R │ ├── extra-pkgs.R │ ├── front_cover2.R │ ├── frontcover.R │ ├── generate-chapter-code.R │ ├── hex_sticker.R │ ├── list-contributors.R │ ├── old-to-future-remove/ │ │ ├── 06_raster_reprojection_tests.R │ │ ├── 08-uscolonize.R │ │ ├── 10-centroid.R │ │ ├── 10-earthquakes.R │ │ ├── 12-code-extension.R │ │ ├── 12-desire-front.R │ │ ├── globe.R │ │ ├── sfr-class-diagram-gen.R │ │ └── spData.R │ ├── sf-classes.R │ └── sfheaders.Rmd ├── extdata/ │ ├── .gitignore │ ├── 12-bmr_score.rds │ ├── 15-bmr_exercises.rds │ ├── 15-nmds.rds │ ├── 15-rp_exercises.rds │ ├── 15-tune.rds │ ├── coffee-data-messy.csv │ ├── coffee-data.csv │ ├── contributors.csv │ ├── generic_map_pkgs.csv │ ├── gis-vs-gds-table.csv │ ├── package_list.csv │ ├── postgis_data.Rdata │ ├── sfs-st-cast.csv │ ├── specific_map_pkgs.csv │ ├── svm_sp_sp_rbf_50it.rds │ ├── top_dls.csv │ └── word-count-time.csv ├── geocompr.Rproj ├── geocompr.bib ├── images/ │ └── r_logo.tif ├── index.Rmd ├── krantz.cls ├── makefile ├── misc/ │ ├── our-impact.csv │ └── our-style.md ├── packages.bib ├── references.Rmd └── style/ ├── after_body.tex ├── before_body.tex ├── ga.html ├── preamble.tex └── style.css ================================================ FILE CONTENTS ================================================ ================================================ FILE: .Rbuildignore ================================================ ^.*\.Rproj$ ^\.Rproj\.user$ ^\.travis\.yml$ ^README\.Rmd$ ^README-.*\.png$ ^\.github$ ================================================ FILE: .binder/Dockerfile ================================================ FROM ghcr.io/geocompx/docker:binder ## Declares build arguments ARG NB_USER ARG NB_UID COPY --chown=${NB_USER} . /home/rstudio ================================================ FILE: .binder/LICENSE ================================================ BSD 3-Clause License Copyright (c) 2021, Yuvi Panda All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: .binder/README.md ================================================ # Template for RStudio on Binder / JupyterHub [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/yuvipanda/rstudio-binder-template/HEAD?urlpath=rstudio) Generate a Git repository that can run R code with RStudio on the browser via [mybinder.org](https://mybinder.org) or any JupyterHub from this template repository! Based on the excellent [rocker/binder](https://hub.docker.com/r/rocker/binder) image maintained by the [Rocker project](https://www.rocker-project.org/) ## How to use this reop ### 1. Create a new repo using this as a template Use the [Use this template](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template#creating-a-repository-from-a-template) button on GitHub. Use a descriptive name representing the GUI app you are running / demoing. You can then follow the rest of the instructions in this README from your newly created repository. ### 2. Install any packages you want You can create an `install.R` file that will be executed on build. Use `install.packages` or `devtools::install_version`. ```R install.packages("ggplot2") ``` Packages are installed from [packagemanager.rstudio.com](https://packagemanager.rstudio.com/client/#/), and binary packages are preferred wherever possible. For some R packages, you might need to install system packages via apt - you can do so by writing out a list of apt package names in `apt.txt`. ### 3. Modify the Binder Badge in the README.md The 'Launch on Binder' badge in this README points to the template repository. You should modify it to point to your own repository. Keep the `urlpath=rstudio` parameter intact - that is what makes sure your repo will launch directly into RStudio ### 4. Add your R code and update README Finally, add the R code you want to demo to the repository! Cleanup the README too so it talks about your code, not these instructions on setting up this repo ================================================ FILE: .devcontainer.json ================================================ { "image": "pixi-r", "customizations": { "vscode": { "extensions": ["reditorsupport.r"] } } } ================================================ FILE: .gitattributes ================================================ *.html linguist-vendored *.bib linguist-vendored latex/ linguist-vendored ================================================ FILE: .github/.gitignore ================================================ *.html ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ================================================ FILE: .github/workflows/dev-sf.yaml ================================================ name: dev-pkgs on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest container: ghcr.io/geocompx/suggests:latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout Project uses: actions/checkout@v1 - name: Build book run: | Rscript -e 'install.packages("geocompkg", repos = c("https://geocompr.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE)' Rscript -e 'remotes::install_github("r-spatial/sf")' Rscript -e 'remotes::install_github("r-spatial/stars")' Rscript -e 'remotes::install_github("rspatial/terra")' #Rscript -e 'remotes::install_github("geocompx/geocompkg", dependencies = TRUE, force = TRUE)' Rscript -e 'bookdown::render_book("index.Rmd")' ================================================ FILE: .github/workflows/main-no-deploy.yml ================================================ on: pull_request: branches: - main name: Render-no-deploy jobs: bookdown: name: Render-Book runs-on: ubuntu-latest container: ghcr.io/geocompx/suggests:latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 - name: Render Book run: | Rscript -e 'bookdown::render_book("index.Rmd")' ================================================ FILE: .github/workflows/main.yaml ================================================ on: push: branches: main name: Render jobs: bookdown: name: Render-Book runs-on: ubuntu-latest container: ghcr.io/geocompx/suggests:latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 - name: Render Book run: | #Rscript -e 'install.packages("remotes")' #Rscript -e 'remotes::install_github("geocompx/geocompkg", dependencies = TRUE, force = TRUE)' #Rscript -e 'install.packages("geocompkg", repos = c("https://geocompr.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE)' Rscript -e 'bookdown::render_book("index.Rmd")' cp -fvr _redirects _book/ cp -fvr .htaccess _book/ #cp -fvr es.html fr.html solutions.html htaccess.txt _book/ - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./_book publish_branch: gh-pages commit_message: 'Deploy commit: ${{ github.event.head_commit.message }}' ================================================ FILE: .github/workflows/qgis-ext.yaml ================================================ name: qgis on: push: branches: - main pull_request: branches: - main jobs: bookdown: name: Render-Book runs-on: ubuntu-latest container: ghcr.io/geocompx/qgis:latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 #- name: Install deps # run: Rscript -e 'install.packages("geocompkg", repos = c("https://geocompx.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE)' - name: Render Book run: Rscript -e 'bookdown::render_book("index.Rmd")' ================================================ FILE: .gitignore ================================================ .bash_history .rstudio/ .Rproj.user .Rhistory .RData _bookdown_files .DS_Store .Rapp.history _book _main.* *.html *.pdf *.utf8.md !ga.html libs/ geocompr2.rds *.gpkg figures/ .claude/ CLAUDE.md *_cache/ ================================================ FILE: .htaccess ================================================ RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)([^/])$ /$1$2/ [L,R=301] ================================================ FILE: .lintr ================================================ linters: with_defaults( line_length_linter(120), assignment_linter = NULL ) ================================================ FILE: .nojekyll ================================================ ================================================ FILE: .vscode/settings.json ================================================ { "editor.wordWrap": "on", "makefile.configureOnOpen": false } ================================================ FILE: 01-introduction.Rmd ================================================ ```{asis index-2, echo=knitr::is_latex_output()} \mainmatter ``` # Introduction {#intro} ```{r, include=FALSE} source("code/before_script.R") ``` This book is about using the power of computers to *do things* with geographic data. It teaches a range of spatial skills, including: reading, writing and manipulating geographic file formats; making static and interactive maps; and applying geocomputation\index{geocomputation} to support more evidence-based decision-making related to a range of geographic phenomena, from transport systems to ecosystems. By demonstrating how various geographic operations can be linked, in 'code chunks' that intersperse the prose, the book also teaches reproducible, open and thus scientific workflows. The book is not just about using the wealth of *existing tools* for geocomputation: it's also about understanding the geographic data structures and software needed to build *new tools*. The approach we teach throughout, and programming techniques covered in Chapter \@ref(algorithms)\index{algorithm} in particular, can remove constraints on your creativity imposed by software. After reading the book and completing the exercises, you should be ready to tackle real-world problems, communicate your work in maps and code, and contribute to the open source communities developing tools and documentation for reproducible geocomputation. Over the last few decades, free and open source software for geospatial (FOSS4G\index{FOSS4G}) has progressed at an astonishing rate. Thanks to organizations such as OSGeo, advanced geographic techniques are no longer the preserve of those with expensive hardware and software: anyone can now download and run high-performance software for geocomputation. Open source Geographic Information Systems (GIS\index{GIS}), such as [QGIS](https://qgis.org/en/site/)\index{QGIS}, have made geographic analysis accessible worldwide. GIS software products are powerful, but they tend to emphasize a graphical user interface\index{graphical user interface} (GUI) approach over the command-line interface (CLI) approach advocated in this book. The 'GUI focus' of many GIS products has the unintended consequence of disabling many users from making their work fully reproducible\index{reproducibility}, a problem that can be overcome by calling 'geoalgorithms' contained in GIS software from the command line, as we'll see in Chapter \@ref(gis). A simplistic comparison between the different approaches is illustrated in Table \@ref(tab:gdsl). ```{r gdsl, echo=FALSE, message=FALSE} d = readr::read_csv("extdata/gis-vs-gds-table.csv") knitr::kable(x = d, caption = paste("Differences in emphasis between software", "packages (Graphical User Interface (GUI) of", "Geographic Information Systems (GIS) and R)."), caption.short = "Differences between GUI and CLI", booktabs = TRUE) ``` R is not the only language providing a CLI for geocomputation. Other command environments with powerful geographic capabilities exist, including Python\index{Python} (covered in the book [Geocomputation with Python](https://py.geocompx.org/)), Julia, and JavaScript. However, R has advantages that make it a good language for learning geocomputation and for many geocomputation tasks, especially for statistics, modeling and visualization, as outlined in Section \@ref(why-open-source). This book is also motivated by the importance of reproducibility\index{reproducibility} for scientific research. It aims to make reproducible geographic data analysis\index{geographic data analysis} workflows more accessible, and demonstrate the power of open geospatial software available from the command line. R provides ways to interface with other languages [@eddelbuettel_extending_2018], enabling numerous spatial software libraries to be called from R, as explained in Section \@ref(why-use-r-for-geocomputation) and demonstrated in Chapter \@ref(gis). Before going into the details of the software, however, it is worth taking a step back and thinking about what we mean by geocomputation\index{geocomputation}. ```{block2 01-introduction-1, type='rmdnote'} Reproducibility is a major advantage of command-line interfaces, but what does it mean in practice? We define it as follows: "A process in which the same results can be generated by others using publicly accessible code". This may sound simple and easy to achieve (which it is if you carefully maintain your R code in script files), but it has profound implications for teaching and the scientific process [@pebesma_r_2012]. ``` \index{reproducibility} ## What is geocomputation? We define geocomputation as > Academic research, software development and practical applications that use geographic data to solve problems, with a focus on reproducibility, flexibility and tool development. Geocomputation\index{geocomputation!definition} is a young term, dating back to the first conference on the subject in 1996.^[ The first 'GeoComputation' conference took place at the University of Leeds, where one of the authors (Robin) is currently based. In 2017 the GeoComputation conference returned to University of Leeds, providing a chance for us to work on and present the book (see www.geocomputation.org for more on the conference series, and papers/presentations spanning more than two decades). ] What distinguished geocomputation from the (at the time) commonly used term 'quantitative geography' was its emphasis on "creative and experimental" applications [@longley_geocomputation_1998] and the development of new tools and methods. In the words of Stan Openshaw, a pioneer in the field who was an advocate (and possibly originator) of the term, "GeoComputation is about using the various different types of geodata and about developing relevant geo-tools within the overall context of a 'scientific' approach" [@openshaw_geocomputation_2000]. Building on this early definition, *Geocomputation with R* goes beyond data analysis and modeling to include the development of new tools and methods for work that is not just interesting academically but beneficial. Our approach differs from early definitions of geocomputation in one important way, however: in its emphasis on reproducibility\index{reproducibility} and collaboration. At the turn of the 21^st^ Century, it was unrealistic to expect readers to be able to reproduce code examples, due to barriers preventing access to the necessary hardware, software and data. Fast-forward to today and things have progressed rapidly. Anyone with access to a laptop with sufficient RAM (at least eight GB recommended) can install and run software for geocomputation, and reproduce the contents of this book. Financial and hardware barriers to geocomputation that existed in 1990s and early 2000s, when high-performance computers were too expensive for most people, have been removed.^[ A suitable laptop can be acquired second-hand for $100 or less in most countries today from websites such as [Ebay](https://www.ebay.com/sch/i.html?_from=R40&_nkw=laptop&_sacat=0&_oaa=1&_udhi=100&rt=nc&RAM%2520Size=4%2520GB%7C16%2520GB%7C8%2520GB&_dcat=177). Guidance on installing R and a suitable code editor is provided in Chapter \@ref(spatial-class). ] Geocomputation is also more accessible because publicly accessible datasets are more widely available than ever before, as we will see in Chapter \@ref(read-write). Unlike early works in the field, all the work presented in this book is reproducible using code and example data supplied alongside the book, in R\index{R} packages such as **spData**, the installation of which is covered in Chapter \@ref(spatial-class). Geocomputation\index{geocomputation} is closely related to other terms including: Geographic Information Science (GIScience); Geomatics; Geoinformatics; Spatial Information Science; Geoinformation Engineering [@longley_geographic_2015]; and Spatial Data Science\index{spatial data science}\index{geographical data science|see spatial data science} (SDS). Each term shares an emphasis on a 'scientific' (implying reproducible and falsifiable) approach influenced by GIS\index{GIS!definition}, although their origins and main fields of application differ. SDS, for example, emphasizes 'data science' skills and large datasets, while Geoinformatics tends to focus on data structures. But the overlaps between the terms are larger than the differences between them and we use geocomputation as a rough synonym encapsulating all of them: they all seek to use geographic data for applied scientific work. Unlike early users of the term, however, we do not seek to imply that there is any cohesive academic field called 'Geocomputation' (or 'GeoComputation' as Stan Openshaw called it). Geocomputation is a recent term but is influenced by old ideas. It can be seen as a part of Geography\index{geography}, which has a 2000+ year history [@talbert_ancient_2014]; and an extension of GIS\index{GIS} [@neteler_open_2008], which emerged in the 1960s [@coppock_history_1991]. Geography\index{geography} has played an important role in explaining and influencing humanity's relationship with the natural world long before the invention of the computer. The famous explorer, early geographer and pioneering polymath Alexander von Humboldt\index{von Humboldt} (who has dozens of species, geographic features, places and even universities named after him, such was his influence) illustrates this role: not only did his travels to South America in the early 1800s and resulting observations lay the foundations for physical geography and ecology, they also paved the way towards policies to protect the natural world [@wulf_invention_2015]. This book aims to contribute to the still-evolving 'Geographic Tradition' [@livingstone_geographical_1992] by harnessing the power of modern computers and open source software. The book's links to older disciplines were reflected in suggested titles for the book: *Geography with R* and *R for GIS*. Each has advantages. The former conveying the applied nature of the content, about more than where something is on the map. The latter communicates that this is a book about using R as a powerful command-line geographic information system, to perform spatial operations on *geographic data*. However, the term GIS has connotations which fail to communicate some of R's\index{R} greatest strengths: its abilities to seamlessly switch between geographic and non-geographic data processing, modeling and visualization tasks while enabling reproducibility go far beyond the capabilities of GIS. Geocomputation\index{geocomputation} implies working with geographic data in a reproducible code-driven environment and programming new results, methods and tools, which is what this book is all about.\index{GIS!connotations} ## Why use open source tools for geocomputation? {#why-open-source} Early geographers used a variety of tools including barometers, compasses and [sextants](https://en.wikipedia.org/wiki/Sextant) to advance knowledge about the world [@wulf_invention_2015]. It was only with the invention of the marine [chronometer](https://en.wikipedia.org/wiki/Marine_chronometer) in 1761 that it became possible to calculate longitude at sea, enabling ships to take more direct routes, for example. Before the turn of the century, there was an acute shortage of data and tools for geographic analysis. Nowadays, researchers and practitioners have no such limitations and in some cases face the opposite problem: too much data and too many tools. Most phones now have a global positioning (GPS\index{GPS}) receiver. Sensors ranging from satellites and semi-autonomous vehicles to citizen scientists incessantly measure every part of the world. The rate of data produced can be overwhelming, with emerging technologies such as autonomous vehicles generating hundreds or even thousands of gigabytes of data daily. Remote sensing\index{remote sensing} datasets from satellites are too large to analyze with a single computer, as outlined in Chapter \@ref(gis). This 'geodata revolution' drives demand for high performance computer hardware and efficient, scalable software to handle and extract signal from the noise. Evolving open source tools can import and process subsets from the vast geographic data stores directly, via application programming interfaces (APIs) and via interfaces to databases. \index{spatial database} With the rapidly changing hardware, software and data landscapes, it's important to choose tools that are future-proof. A major advantage of open source software is its **rate of development and longevity**, with thousands of potential contributors. Hundreds of people submit bug reports and suggest new features as well as documentation improvements to open source projects every day --- a rate of evolution that most proprietary solutions simply cannot keep up with. A linked advantage is **interoperability**. While proprietary products tend to be monolithic 'empires' that are difficult to maintain (linked to the previously mentioned advantage), open source software is more like a 'federation' of modular tools that can be combined in different ways. This has allowed open source data science languages such as R to rapidly incorporate new developments such as interfaces to high performance visualization libraries and file formats, while proprietary solutions struggle to keep up. Another major advantage is **reproducibility**. Being able to replicate findings is vital for scientific research, and open source software removes an important barrier of reproducibility by enabling others to check your findings or applying your methods in new contexts using the same tools. The combination of using tools that can be accessed by anyone for free with the ability to share code and data means that the results of your work can be checked and built upon by others, which is a huge advantage if you want your work to be used and cited. The biggest advantage of open source software combined with sharing of reproducible code for many people, however, is the **community**. The community enables you to get support far quicker and often of higher quality than is possible with a centralized and budget-limited support team associated with proprietary software. The community can provide feedback, ideas and, as discussed in the Chapter \@ref(conclusion)), can help you to develop your own tools and methods. R is an open source software project, a powerful language, and an ever-evolving community of statisticians and developers [@wickham_advanced_2019]. R is not the only language enabling reproducible geocomputation with open source software, as outlined in Section \@ref(software-for-geocomputation)). Many of the reasons for using R also apply to other open source languages for reproducible data science, such as Python\index{Python} and Julia. However, R has some key advantages, as outlined in Section \@ref(why-use-r-for-geocomputation). ## Why use R for geocomputation? {#why-use-r-for-geocomputation} R is a multi-platform, open source language and environment for statistical computing and graphics ([r-project.org/](https://www.r-project.org/)). With a wide range of packages, R also supports advanced geospatial statistics\index{statistics}, modeling and visualization. \index{R!language} Integrated development environments (IDEs\index{IDE}) such as RStudio\index{RStudio} have made R more user-friendly for many, easing map-making with a panel dedicated to interactive visualization. At its core, R is an object-oriented, [functional programming language](https://adv-r.hadley.nz/fp.html) [@wickham_advanced_2019] and was specifically designed as an interactive interface to other software [@chambers_extending_2016]. The latter also includes many 'bridges' to a treasure trove of GIS\index{GIS} software, 'geolibraries' and functions (see Chapter \@ref(gis)). It is thus ideal for quickly creating 'geo-tools', without needing to master lower level languages (compared to R) such as C\index{C}, FORTRAN\index{FORTRAN} or Java\index{Java} (see Section \@ref(software-for-geocomputation)). \index{R} This can feel like breaking free from the metaphorical 'glass ceiling' imposed by GUI-based or proprietary geographic information systems (see Table \@ref(tab:gdsl) for a definition of GUI\index{graphical user interface}). Furthermore, R facilitates access to other languages: the packages **Rcpp** and **reticulate** enable access to C++\index{C++} and Python\index{Python} code, for example. This means R can be used as a 'bridge' to a wide range of geospatial programs (see Section \@ref(software-for-geocomputation)). Another example showing R's flexibility and evolving geographic capabilities is interactive map-making\index{map-making!interactive maps}. As we'll see in Chapter \@ref(adv-map), the statement that R has "limited interactive [plotting] facilities" [@bivand_applied_2013] is no longer true. This is demonstrated by the following code chunk, which creates Figure \@ref(fig:interactive) (the functions that generate the plot are covered in Section \@ref(interactive-maps)). ```{r 01-introduction-2, eval=FALSE, echo=FALSE} a = osmdata::getbb("Hereford") b = osmdata::getbb("Bialystok") rowMeans(a) rowMeans(b) ``` ```{r interactive-demo, eval=FALSE} library(leaflet) popup = c("Robin", "Jakub", "Jannes") leaflet() |> addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") |> addMarkers(lng = c(-3, 23, 11), lat = c(52, 53, 49), popup = popup) ``` ```{r interactive, fig.cap="The blue markers indicate where the authors are from. The basemap is a tiled image of the Earthat night provided by NASA. Interact with the online version at r.geocompx.org, for example by zooming in and clicking on the pop-ups.", out.width="100%", fig.scap="Where the authors are from.", echo=FALSE} if (knitr::is_latex_output()){ knitr::include_graphics("images/interactive.png") } else if (knitr::is_html_output()){ # library(leaflet) # popup = c("Robin", "Jakub", "Jannes") # interactive = leaflet() |> # addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") |> # addMarkers(lng = c(-3, 23, 11), # lat = c(52, 53, 49), # popup = popup) # library(htmlwidgets) # saveWidget(interactive, file = "interactive.html") # file.copy("interactive.html", "~/geocompr/geocompr.github.io/static/img/interactive.html") knitr::include_url("https://geocompr.github.io/img/interactive.html") } ``` \index{map-making!interactive} It would have been difficult to produce Figure \@ref(fig:interactive) using R (or any open source language for data science) a few years ago, let alone as an interactive map. This illustrates R's flexibility and how, thanks to developments such as **knitr** and **leaflet**, it can be used as an interface to other software, a theme that will recur throughout this book. The use of R code, therefore, enables teaching geocomputation with reference to reproducible examples representing real-world phenomena, rather than just abstract concepts. The 'R-spatial stack' is easy to install and has comprehensive, well-maintained and highly interoperable packages. R has 'batteries included' with statistical functions as part of the base installation and hundreds of well-maintained packages implementing many cutting edge methods. With R, you can dive and get things working with surprisingly few lines of code, enabling you to focus on the geographic methods and data, rather than debugging and managing package dependencies. A particular strength of R is the ease with which it allows you to create publication quality interactive maps thanks to excellent mapping packages, as outlined in Chapter \@ref(adv-map). ## Software for geocomputation R is a powerful language for geocomputation, but there are many other options for geographic data analysis providing thousands of geographic functions\index{function}. Awareness of other languages for geocomputation will help decide when a different tool may be more appropriate for a specific task, and will place R in the wider geospatial ecosystem. This section briefly introduces the languages [C++](https://isocpp.org/)\index{C++}, [Java](https://www.oracle.com/java/)\index{Java} and [Python](https://www.python.org/)\index{Python} for geocomputation, in preparation for Chapter \@ref(gis). An important feature of R (and Python) is that it is an interpreted language. This is advantageous because it enables interactive programming in a Read–Eval–Print Loop (REPL):\index{REPL} code entered into the console is immediately executed and the result is printed, rather than waiting for the intermediate stage of compilation. On the other hand, compiled languages such as C++\index{C++} and Java\index{Java} tend to run faster (once they have been compiled). C++\index{C++} provides the basis for many GIS packages such as [QGIS](https://www.qgis.org/en/site/)\index{QGIS}, [GRASS GIS](https://grass.osgeo.org/)\index{GRASS GIS} and [SAGA](https://saga-gis.sourceforge.io/)\index{SAGA}, so it is a sensible starting point. Well-written C++\index{C++} is very fast, making it a good choice for performance-critical applications such as processing large geographic datasets, but is harder to learn than Python or R. C++\index{C++} has become more accessible with the **Rcpp** package, which provides a good 'way in' to C\index{C} programming for R users. Proficiency with such low-level languages opens the possibility of creating new, high-performance 'geoalgorithms' and a better understanding of how GIS software works (see Chapter \@ref(algorithms)). However, it is not necessary to learn C++\index{C++} to use R for geocomputation. Python\index{Python} is an important language for geocomputation, especially because many Desktop GIS\index{GIS} such as GRASS GIS\index{GRASS GIS}, SAGA\index{SAGA} and QGIS\index{QGIS} provide a Python API\index{API} (see Chapter \@ref(gis)). Like R\index{R}, Python is a popular language for data science. Both languages are object-oriented, and have many areas of overlap, leading to initiatives such as the **reticulate** package that facilitates access to Python\index{Python} from R and the [Ursa Labs](https://ursalabs.org/) initiative to support portable libraries to the benefit of the entire open source data science ecosystem. In practice both R and Python have their strengths. To some extent which you use is less important than the domain of application and communication of results. Learning either will provide a head-start in learning the other. However, there are major advantages of R\index{R} over Python\index{Python} for geocomputation\index{geocomputation}. This includes its much better support of the geographic raster data model in the language itself (see Chapter \@ref(spatial-class)) and corresponding visualization possibilities (see Chapters \@ref(spatial-class) and \@ref(adv-map)). Equally important, R has unparalleled support for statistics\index{statistics}, including spatial statistics\index{spatial!statistics}, with hundreds of packages (unmatched by Python\index{Python}) supporting thousands of statistical methods. The major advantage of Python is that it is a *general-purpose* programming language. It is used in many domains, including desktop software, computer games, websites and data science\index{data science}. Python\index{Python} is often the only shared language between different (geocomputation) communities and can be seen as the 'glue' that holds many GIS\index{GIS} programs together. Many geoalgorithms\index{geoalgorithm}, including those in QGIS\index{QGIS} and ArcMap, can be accessed from the Python command line, making it well suited as a starter language for command line GIS.^[ Python modules providing access to geoalgorithms\index{geoalgorithm} include `grass.script` for GRASS GIS\index{GRASS GIS}, `saga-python` for SAGA-GIS\index{SAGA}, `processing` for QGIS\index{QGIS} and `arcpy` for ArcGIS\index{ArcGIS}. ] For spatial statistics\index{spatial!statistics} and predictive modeling, however, R is second-to-none. This does not mean you must choose either R or Python: Python\index{Python} supports most common statistical techniques (though R tends to support new developments in spatial statistics earlier) and many concepts learned from Python can be applied to the R\index{R} world. Like R, Python also supports geographic data analysis and manipulation with packages such as **shapely**, **geopandas**, **rasterio** and **xarray**. ## R's spatial ecosystem {#r-ecosystem} There are many ways to handle geographic data in R, with dozens of packages\index{R-spatial} in the area.^[ An overview of R's spatial ecosystem can be found in the CRAN\index{CRAN} Task View on the Analysis of Spatial Data (see https://cran.r-project.org/view=Spatial). ] In this book, we endeavor to teach the state-of-the-art in the field whilst ensuring that the methods are future-proof. Like many areas of software development, R's spatial ecosystem is rapidly evolving (Figure \@ref(fig:cranlogs)). Because R is open source, these developments can easily build on previous work, by 'standing on the shoulders of giants', as Isaac Newton put it in [1675](https://digitallibrary.hsp.org/index.php/Detail/objects/9792). This approach is advantageous because it encourages collaboration and avoids 'reinventing the wheel'. The package **sf**\index{sf} (covered in Chapter \@ref(spatial-class)), for example, builds on its predecessor **sp**. A surge in development time (and interest) in 'R-spatial\index{R-spatial}' has followed the award of a grant by the R Consortium for the development of support for *simple features*, an open-source standard and model to store and access vector geometries. This resulted in the **sf** package (covered in Section \@ref(intro-sf)). Multiple places reflect the immense interest in **sf**. This is especially true for the [R-sig-Geo Archives](https://stat.ethz.ch/pipermail/r-sig-geo/), a long-standing open access email list containing much R-spatial wisdom accumulated over the years. ```{r cranlogs, fig.cap="Downloads of selected R packages for working with geographic data from early 2013 to present. The y axis shows the average number of daily downloads from the popular cloud.r-project.org CRAN mirror with a 91-day rolling window (log scale).", echo=FALSE, fig.scap="The popularity of spatial packages in R."} knitr::include_graphics("images/01-cranlogs.png") ``` It is noteworthy that shifts in the wider R community, as exemplified by the data processing package **dplyr** (released in [2014](https://cran.r-project.org/src/contrib/Archive/dplyr/)), influenced shifts in R's spatial ecosystem. Alongside other packages that have a shared style and emphasis on 'tidy data' (including, e.g., **ggplot2**), **dplyr** was placed in the **tidyverse** 'metapackage'\index{tidyverse (package)} in late [2016](https://cran.r-project.org/src/contrib/Archive/tidyverse/). The **tidyverse**\index{tidyverse (package)} approach, with its focus on long-form data and fast intuitively named functions, has become immensely popular. This has led to a demand for 'tidy geographic data' which has been partly met by **sf**. An obvious feature of the **tidyverse** is the tendency for packages to work in harmony. There is no equivalent 'geoverse', but the modern R-spatial ecosystem has consolidated around **sf**, as illustrated by key packages that depend on it shown in Table \@ref(tab:revdep), and **terra**, both of which are taught in this book. The stack is highly interoperable both between packages and with other languages, as outlined in Chapter \@ref(gis). ```{r revdep, echo=FALSE, message=FALSE} top_dls = readr::read_csv("extdata/top_dls.csv") knitr::kable(top_dls[1:5, 1:2], digits = 0, caption = paste("The top 5 most downloaded packages that depend", "on sf, in terms of average number of downloads", "per day over the previous month. As of", min(top_dls$date), ", there are ", nrow(top_dls), " packages which import sf."), caption.short = "Top 5 most downloaded packages depending on sf.", booktabs = TRUE, col.names = c("Package", "Downloads")) # cranlogs::cran_top_downloads(when = "last-month") # most downloaded pkgs ``` ## History of R-spatial There are many benefits of using modern spatial packages such as **sf**, but there is value in understanding the history of R's spatial capabilities. Many functions, use cases and teaching materials are contained in older packages, many of which are still useful, provided you know where to look. \index{R!history} \index{R-spatial!history} R's spatial capabilities originated in early spatial packages in the S language [@bivand_implementing_2000]. \index{S} The 1990s saw the development of numerous S scripts and a handful of packages for spatial statistics\index{statistics}. By the year 2000, there were R packages for various spatial methods, including "point pattern analysis, geostatistics, exploratory spatial data analysis and spatial econometrics" [@bivand_open_2000]. Some of these, notably **spatial**, **sgeostat** and **splancs** are still available on CRAN\index{CRAN} [@rowlingson_splancs_1993; @rowlingson_splancs_2017;@venables_modern_2002; @majure_sgeostat_2016]. Key spatial packages were described in @ripley_spatial_2001, which outlined R packages for spatial smoothing and interpolation and point pattern analysis. One of these (**spatstat**) is still being actively maintained, more than 20 years after its first release. A following commentary outlined the future prospects of spatial statistics [@bivand_more_2001], setting the stage for the development of the popular **spdep** package [@bivand_spdep_2017]. Notably, the commentary mentioned the need for standardization of spatial interfaces, efficient mechanisms for exchanging data with GIS\index{GIS}, and handling of spatial metadata such as coordinate reference systems (CRS\index{CRS}). These aims have largely been achieved. **maptools** [@bivand_maptools_2017] is another important package from this time, which provided an interface to the [shapelib](http://shapelib.maptools.org/) library for reading the Shapefile\index{Shapefile} file format and which fed into **sp**. An extended review of spatial packages proposed a class system to support the "data objects offered by GDAL"\index{GDAL}, including fundamental point, line, polygon, and raster types, and interfaces to external libraries [@hornik_approaches_2003]. To a large extent, these ideas were realized in the packages **rgdal** and **sp**, providing a foundation for the seminal book *Applied Spatial Data Analysis with R* (ASDAR) [@bivand_applied_2013], first published in 2008. R's spatial capabilities have evolved substantially since then, but they still build on the ideas of early pioneers. Interfaces to GDAL\index{GDAL} and PROJ\index{PROJ}, for example, still power R's high-performance geographic data I/O and CRS\index{CRS} transformation capabilities, as outlined in Chapters \@ref(reproj-geo-data) and \@ref(read-write), respectively. **rgdal**, released in 2003, provided GDAL\index{GDAL} bindings for R which greatly enhanced its ability to import data from previously unavailable geographic data formats. The initial release supported only raster drivers, but subsequent enhancements provided support for CRSs (via the PROJ library), reprojections and import of vector file formats. Many of these additional capabilities were developed by Barry Rowlingson and released in the **rgdal** codebase in 2006, as described in @rowlingson_rasp:_2003 and the [R-help](https://stat.ethz.ch/pipermail/r-help/2003-January/028413.html) email list. The **sp** package, released in 2005, was a significant advancement in R's spatial capabilities. It introduced classes and generic methods for handling geographic coordinates, including points, lines, polygons, and grids, as well as attribute data. With the S4 class system, **sp** stores information such as bounding box, coordinate reference system (CRS), and attributes in slots within `Spatial` objects. This allows for efficient data operations on geographic data. The package also provided generic methods like `summary()` and `plot()` for working with geographic data. In the following decade, **sp** classes rapidly became popular for geographic data in R and the number of packages that depended on it increased from around 20 in 2008 to over 100 in 2013 [@bivand_applied_2013]. By 2019 more than 500 packages imported **sp**. Although the number of packages that depend on **sp** has decreased since the release of **sf** it is still used by prominent R packages, including **gstat** (for spatial and spatiotemporal geostatistics)\index{spatial!statistics} and **geosphere** (for spherical trigonometry) [@R-gstat; @hijmans_geosphere_2016]. ```{r, eval=FALSE, echo=FALSE} # Aim: show n. pkgs that depend on sf and sp revdep_sp = devtools::revdep(pkg = "sp") length(revdep_sp) # 449 # 2023-11-16 revdep_sf = devtools::revdep(pkg = "sf") length(revdep_sf) # 739 # 2023-11-16 ``` While **rgdal** and **sp** solved many spatial issues, it was not until **rgeos** was developed during a Google Summer of Code project in 2010 [@R-rgeos] that geometry operations could be undertaken on **sp** objects. Functions such as `gIntersection()` enabled users to find spatial relationships between geographic objects and to modify their geometries (see Chapter \@ref(geometry-operations) for details on geometric operations with **sf**). \index{raster (package)} A limitation of the **sp** ecosystem was its limited support for raster data. This was overcome by **raster**\index{raster (package)}, first released in 2010 [@R-raster]. **raster**'s class system and functions enabled a range of raster operations, capabilities now implemented in the **terra** package, which supersedes **raster**, as outlined in Section \@ref(raster-data). An important capability of **raster** and **terra** is their ability to work with datasets that are too large to fit into RAM by supporting off-disk operations. **raster** and **terra** also supports map algebra, as described in Section \@ref(map-algebra). In parallel with these developments of class systems and methods, came the support for R as an interface to dedicated GIS software. **GRASS** [@bivand_using_2000] and follow-on packages **spgrass6**, **rgrass7** and **rgrass** were prominent examples in this direction [@bivand_rgrass7_2016;@bivand_spgrass6_2016;@R-rgrass]. Other examples of bridges between R and GIS include bridges to QGIS via **qgisprocess** [@R-qgisprocess], SAGA via **Rsagacmd** [@R-Rsagacmd] or **RSAGA** [@R-RSAGA]\index{RSAGA (package)} and ArcGIS via **RPyGeo** [@brenning_arcgis_2012, first published in 2008], and more (see Chapter \@ref(gis)). Visualization was not a focus initially, with the bulk of R-spatial development focused on analysis and geographic operations. **sp** provided methods for map-making using both the base and lattice plotting system, but demand was growing for advanced map-making capabilities. **RgoogleMaps** first released in 2009, allowed to overlay R spatial data on top of 'basemap' tiles from online services such as Google Maps or OpenStreetMap [@loecher_rgooglemaps_2015]. \index{ggplot2 (package)} It was followed by the **ggmap** package that added similar 'basemap' tiles capabilities to **ggplot2** [@kahle_ggmap_2013]. Though **ggmap** facilitated map-making with **ggplot2**, its utility was limited by the need to `fortify` spatial objects, which means converting them into long data frames. While this works well for points, it is computationally inefficient for lines and polygons, since each coordinate (vertex) is converted into a row, leading to huge data frames to represent complex geometries. Although geographic visualization tended to focus on vector data, raster visualization was supported in **raster** and received a boost with the release of **rasterVis** [@lamigueiro_displaying_2018]. Since then map-making in R has become a hot topic, with dedicated packages such as **tmap**, **leaflet** and **mapview** gaining popularity, as highlighted in Chapter \@ref(adv-map). Since 2018, when the First Edition of Geocomputation with R was published, the development of geographic R packages has accelerated. \index{terra (package)}\index{raster (package)} **terra**, a successor of the **raster** package, was firstly released in 2020 [@R-terra], bringing several benefits to R users working with raster datasets: it is faster and has more a straightforward user interface than its predecessor, as described in Section \@ref(raster-data). In mid-2021, **sf** started using the S2 spherical geometry engine for geometry operations on unprojected datasets, as described in Section \@ref(s2). Additional ways of representing and working with geographic data in R since 2018 have been developed, including with the **stars** and **lidR** packages [@pebesma_stars_2021; @Roussel2020]. \index{stars (package)} \index{lidR (package)} Such developments have been motivated by the emergence of new technologies, standards and software outside of the R environment [@bivand_progress_2021]. Major updates to the PROJ library\index{PROJ} beginning in 2018 forced the replacement of 'proj-string' representations of CRSs with 'Well Known Text', as described in Section \@ref(crs-intro) and Chapter \@ref(reproj-geo-data). \index{rayshader (package)} Since the publication of the first version of Geocomputation with R in 2018, several packages for spatial data visualization have been developed and improved. The **rayshader** package, for example, enables the development of striking and easy-to-animate 3D visualizations via raytracing and multiple hill-shading methods [@morganwall_rayshader_2021]. \index{ggplot2 (package)} The very popular **ggplot2** package gained new spatial capabilities, thanks to work on the **ggspatial** package, which provides scale bars and north arrows [@dunnington_ggspatial_2021]. **gganimate** enables smooth and customizable spatial animations [@pedersen_gganimate_2020]. Existing visualization packages have also been improved or rewritten. Large raster objects are automatically downscaled in **tmap** and high-performance interactive maps are now possible thanks to packages including **leafgl** and **mapdeck**. The **mapsf** package (successor of **cartography**) was rewritten to reduce dependencies and improve performance [@giraud_mapsf_2021]; and **tmap** underwent a major update in Version 4, in which most of the internal code was revised. In late 2021, the planned retirement of **rgdal**, **rgeos** and **maptools** [was announced](https://stat.ethz.ch/pipermail/r-sig-geo/2021-September/028760.html) and in October 2023 they were archived on CRAN. This retirement at the end of 2023 not only has had a large impact on existing workflows applying these packages, but also [influenced the packages that depend on them](https://geocompx.org/post/2023/rgdal-retirement/). Modern R packages such as **sf** and **terra**, described in Chapter \@ref(spatial-class) provide a strong and future-proof foundation for geocomputation that we build on in this book. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_01-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 02-spatial-data.Rmd ================================================ # (PART) Foundations {-} # Geographic data in R {#spatial-class} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} This is the first practical chapter of the book, and therefore it comes with some software requirements. You need access to a computer with a recent version of R installed (R [4.3.2](https://stat.ethz.ch/pipermail/r-announce/2023/000697.html) or a later version). We recommend not only reading the prose but also *running the code* in each chapter to build your geocomputational skills. To keep track of your learning journey, it may be worth starting by creating a new folder on your computer to save your R scripts, outputs and other things related to Geocomputation with R as you go. You can also [download](https://github.com/geocompx/geocompr/archive/refs/heads/main.zip) or [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) the [source code](https://github.com/geocompx/geocompr) underlying the book to support your learning. We strongly recommend using R with an integrated development environment (IDE) such as [RStudio](https://posit.co/download/rstudio-desktop/#download)\index{RStudio} (quicker to get up and running) or [VS Code](https://github.com/REditorSupport/vscode-R)\index{VS Code} (which requires additional setup). If you are new to R, we recommend following introductory R resources such as [Hands on Programming with R](https://rstudio-education.github.io/hopr/starting.html) and [Introduction to R](https://cengel.github.io/R-intro/) before you dive into Geocomputation with R code. These resources cover in detail how to install R, which simply involves downloading the latest version from the [Comprehensive R Archive Network (CRAN)](https://cran.r-project.org/). See the note below for more information on installing R for geocomputation on Mac and Linux. Organize your work into [projects](https://r4ds.had.co.nz/workflow-projects.html) and give scripts sensible names such as `chapter-02.R` (or equivalent RMarkdown or Quarto file names) to document the code as you learn. \index{R!prerequisites} \index{R!installation} ```{block2 02-spatial-data-2, type='rmdnote'} Mac and Linux operating systems (OSs) have additional systems requirements, which can be found in the README of the [**sf** package](https://github.com/r-spatial/sf). See also OS-specific instructions such as that provided by the website [rtask.thinkr.fr](https://rtask.thinkr.fr/installation-of-r-4-2-on-ubuntu-22-04-lts-and-tips-for-spatial-packages/), which covers installing R on the open source OS Ubuntu. ``` After you have got a good set-up, it's time to run some code! Unless you already have these packages installed, the first thing to do is to install foundational R packages used in this chapter, with the following commands:^[ **spDataLarge** is not on CRAN\index{CRAN}, meaning it must be installed via *r-universe* or with the following command: `remotes::install_github("Nowosad/spDataLarge")`. ] ```{r 02-spatial-data-1, eval=FALSE} install.packages("sf") install.packages("terra") install.packages("spData") install.packages("spDataLarge", repos = "https://geocompr.r-universe.dev") ``` ```{r, eval=FALSE, echo=FALSE, message=FALSE, results='hide'} remotes::install_github("r-tmap/tmap") ``` The packages needed to reproduce Part I of this book can be installed with the following command: `remotes::install_github("geocompx/geocompkg")`. This command uses the function `install_packages()` from the **remotes** package to install source code hosted on the GitHub code hosting, version and collaboration platform. The following command will install **all** dependencies required to reproduce the entire book (warning: this may take several minutes): `remotes::install_github("geocompx/geocompkg", dependencies = TRUE)`. The packages needed to run the code presented in this chapter can be 'loaded' (technically they are attached) with the `library()` function as follows: ```{r 02-spatial-data-3-1, message=TRUE} library(sf) # classes and functions for vector data ``` The output from `library(sf)` reports which versions of key geographic libraries such as GEOS the package is using, as outlined in Section \@ref(intro-sf). ```{r 02-spatial-data-3-2, message=FALSE} library(terra) # classes and functions for raster data ``` The other packages that were installed contain data that will be used in the book: ```{r 02-spatial-data-4} #| message: FALSE #| results: hide library(spData) # load geographic data library(spDataLarge) # load larger geographic data ``` ## Introduction {#intro-spatial-class} This chapter will provide explanations of the fundamental geographic data models:\index{data models} vector and raster. We will introduce the theory behind each data model and the disciplines in which they predominate, before demonstrating their implementation in R. The *vector data model* represents the world using points, lines and polygons. These have discrete, well-defined borders, meaning that vector datasets usually have a high level of precision (but not necessarily accuracy as we will see in Section \@ref(units)). The *raster data model* divides the surface up into cells of constant size. Raster datasets are the basis of background images used in web mapping and have been a vital source of geographic data since the origins of aerial photography and satellite-based remote sensing devices. Rasters aggregate spatially specific features to a given resolution, meaning that they are consistent over space and scalable (many worldwide raster datasets are available). Which to use? The answer likely depends on your domain of application: - Vector data tends to dominate the social sciences because human settlements tend to have discrete borders - Raster dominates many environmental sciences partially because of the reliance on remote sensing data Both raster and vector datasets are used in many fields and raster and vector datasets can be used together: ecologists and demographers, for example, commonly use both vector and raster data. Furthermore, it is possible to convert between the two forms (see Chapter \@ref(raster-vector)). Whether your work involves more use of vector or raster datasets, it is worth understanding the underlying data model before using them, as discussed in subsequent chapters. This book uses **sf** and **terra** packages to work with vector data and raster datasets, respectively. ## Vector data ```{block2 02-spatial-data-5, type="rmdnote"} Take care when using the word 'vector', as it can have two meanings in this book: geographic vector data and the `vector` class (note the `monospace` font) in R. The former is a data model, the latter is an R class just like `data.frame` and `matrix`. Still, there is a link between the two: the spatial coordinates which are at the heart of the geographic vector data model can be represented in R using `vector` objects. ``` The geographic vector data model\index{vector data model} is based on points located within a coordinate reference system\index{coordinate reference system|see {CRS}} (CRS\index{CRS}). Points can represent self-standing features (e.g., the location of a bus stop) or they can be linked together to form more complex geometries such as lines and polygons. Most point geometries contain only two dimensions (much less prominent three-dimensional geometries contain an additional $z$ value, typically representing height above sea level). In this system, for example, London can be represented by the coordinates `c(-0.1, 51.5)`. This means that its location is $-0.1$ degrees east and $51.5$ degrees north of the origin. The origin in this case is at 0 degrees longitude (Prime Meridian) and 0 degrees latitude (Equator) in a geographic ('lon/lat') CRS (Figure \@ref(fig:vectorplots), left panel). The same point could also be approximated in a projected CRS with 'Easting/Northing' values of `c(530000, 180000)` in the [British National Grid](https://en.wikipedia.org/wiki/Ordnance_Survey_National_Grid), meaning that London is located 530 km *East* and 180 km *North* of the $origin$ of the CRS. This can be verified visually: slightly more than 5 'boxes' --- square areas bounded by the gray grid lines 100 km in width --- separate the point representing London from the origin (Figure \@ref(fig:vectorplots), right panel). The location of National Grid's\index{National Grid} origin, in the sea beyond the South West peninsula, ensures that all locations in the UK have positive Easting and Northing values.^[ The origin we are referring to, depicted in blue in Figure \@ref(fig:vectorplots), is in fact the 'false' origin. The 'true' origin, the location at which distortions are at a minimum, is located at 2° W and 49° N. This was selected by the Ordnance Survey to be roughly in the center of the British landmass longitudinally. ] There is more to CRSs, as described in Section \@ref(crs-intro) and Chapter \@ref(reproj-geo-data), For this section, it is sufficient to know that coordinates consist of two numbers representing distance from an origin, usually in $x$ then $y$ dimensions. ```{r vectorplots-source, include=FALSE, eval=FALSE} source("https://github.com/geocompx/geocompr/raw/main/code/02-vectorplots.R") # generate subsequent figure ``` ```{r vectorplots, fig.cap="Vector (point) data in which the location of London (red X) is represented with reference to an origin (blue circle). The left plot represents a geographic CRS with an origin at 0° longitude and latitude. The right plot represents a projected CRS with an origin located in the sea west of the South West Peninsula.", out.width="49%", fig.show='hold', echo=FALSE, fig.scap="Vector (point) data."} knitr::include_graphics(c("images/vector_lonlat.png", "images/vector_projected.png")) ``` The **sf** package provides classes for geographic vector data and a consistent command line interface to important low-level libraries for geocomputation: - [GDAL](https://gdal.org/)\index{GDAL}, for reading, writing and manipulating a wide range of geographic data formats, covered in Chapter \@ref(read-write) - [PROJ](https://proj.org/), a powerful library for coordinate system transformations, which underlies the content covered in Chapter \@ref(reproj-geo-data) - [GEOS](https://libgeos.org/)\index{GEOS}, a planar geometry engine for operations such as calculating buffers and centroids on data with a projected CRS, covered in Chapter \@ref(geometry-operations) - [S2](https://s2geometry.io/)\index{S2}, a spherical geometry engine written in C++ developed by Google, via the [**s2**](https://r-spatial.github.io/s2/) package, covered in Section \@ref(s2) below and in Chapter \@ref(reproj-geo-data) Information about these interfaces is printed by **sf** the first time the package is loaded: the message `r print(capture.output(sf:::.onAttach(), type = "message"))` that appears below the `library(sf)` command at the beginning of this chapter tells us the versions of linked GEOS, GDAL and PROJ libraries (these vary between computers and over time) and whether or not the S2\index{S2} interface is turned on. We may take these low-level libraries for granted, but without their tight integration with languages such as R much reproducible geocomputation would be impossible. A neat feature of **sf** is that you can change the default geometry engine used on unprojected data: 'switching off' S2\index{S2} can be done with the command `sf::sf_use_s2(FALSE)`, meaning that the planar geometry engine GEOS\index{GEOS} will be used by default for all geometry operations, including geometry operations on unprojected data. As we will see in Section \@ref(s2), planar geometry is based on two-dimensional space. Planar geometry engines such as GEOS assume 'flat' (projected) coordinates, while spherical geometry engines such as S2 assume unprojected (lon/lat) coordinates. This section introduces **sf** classes in preparation for subsequent chapters (Chapters \@ref(geometry-operations) and \@ref(read-write) cover the GEOS and GDAL interface, respectively). ### Introduction to simple features {#intro-sf} Simple features is an [open standard](http://portal.opengeospatial.org/files/?artifact_id=25355) developed and endorsed by the Open Geospatial Consortium (OGC), a not-for-profit organization whose activities we will revisit in a later chapter (Section \@ref(file-formats)). \index{simple features|see {sf}} Simple features is a hierarchical data model that represents a wide range of geometry types. Of 18 geometry types supported by the specification, only seven are used in the vast majority of geographic research (see Figure \@ref(fig:sf-ogc)); these core geometry types are fully supported by the R package **sf** [@pebesma_simple_2018].^[ The full OGC standard includes rather exotic geometry types including 'surface' and 'curve' geometry types, which currently have limited application in real-world applications. You can find the whole list of possible feature types in the [PostGIS manual ](http://postgis.net/docs/using_postgis_dbmanagement.html). All 18 types can be represented with the **sf** package, although at the time of writing (2024), plotting only works for the 'core 7'. ] ```{r sf-ogc, fig.cap="Simple feature types fully supported by sf.", out.width="60%", echo=FALSE} knitr::include_graphics("images/sf-classes.png") ``` **sf** can represent all common vector geometry types (raster data classes are not supported by **sf**): points, lines, polygons and their respective 'multi' versions (which group together features of the same type into a single feature). \index{sf} \index{sf (package)|see {sf}} **sf** also supports geometry collections, which can contain multiple geometry types in a single object. **sf** provides the same functionality (and more) previously provided in three packages --- **sp**\index{sp (package)} for data classes [@R-sp], **rgdal** for data read/write via an interface to GDAL and PROJ [@R-rgdal] and **rgeos** for spatial operations via an interface to GEOS [@R-rgeos]. To reiterate the message from Chapter 1, geographic R packages have a long history of interfacing with lower level libraries, and **sf** continues this tradition with a unified interface to recent versions of GEOS for geometry operations, the GDAL library for reading and writing geographic data files, and the PROJ library for representing and transforming projected CRSs. Through **s2**\index{S2}, an R interface to Google's spherical geometry library, [`s2`](https://s2geometry.io/), **sf** also has access to fast and accurate "measurements and operations on non-planar geometries" [@bivand_progress_2021]. Since **sf** version 1.0.0, launched in [June 2021](https://cran.r-project.org/src/contrib/Archive/sf/), **s2** functionality is now used by [default](https://r-spatial.org/r/2020/06/17/s2.html) on geometries with geographic (longitude/latitude) coordinate systems, a unique feature of **sf** that differs from spatial libraries that only support GEOS for geometry operations such as the Python package [GeoPandas](geopandas/geopandas/issues/2098). We will discuss **s2** in subsequent chapters. **sf**'s ability to integrate multiple powerful libraries for geocomputation into a single framework is a notable achievement that reduces 'barriers to entry' into the world of reproducible geographic data analysis with high-performance libraries. **sf**'s functionality is well documented on its website at [r-spatial.github.io/sf/](https://r-spatial.github.io/sf/index.html) which contains seven vignettes. These can be viewed offline as follows: ```{r 02-spatial-data-6, eval=FALSE} vignette(package = "sf") # see which vignettes are available vignette("sf1") # an introduction to the package ``` ```{r 02-spatial-data-7, eval=FALSE, echo=FALSE} vignette("sf1") # an introduction to the package vignette("sf2") # reading, writing and converting simple features vignette("sf3") # manipulating simple feature geometries vignette("sf4") # manipulating simple features vignette("sf5") # plotting simple features vignette("sf6") # miscellaneous long-form documentation vignette("sf7") # spherical geometry operations ``` As the first vignette explains, simple feature objects in R are stored in a data frame, with geographic data occupying a special column, usually named 'geom' or 'geometry'\index{vector!geometry}. We will use the `world` dataset provided by **spData** [@R-spData], loaded at the beginning of this chapter, to show what `sf` objects are and how they work. `world` is an '`sf` data frame' containing spatial and attribute columns, the names of which are returned by the function `names()` (the last column in this example contains the geographic information). ```{r 02-spatial-data-8} class(world) names(world) ``` The contents of this `geom` column give `sf` objects their spatial powers: `world$geom` is a '[list column](https://adv-r.hadley.nz/vectors-chap.html#list-columns)' that contains all the coordinates of the country polygons. \index{list column} `sf` objects can be plotted quickly with the function `plot()`. Although part of R's default installation (base R), `plot()` is a [*generic*](https://adv-r.hadley.nz/s3.html#s3-methods) that is extended by other packages. **sf** contains the non-exported (hidden from users most of the time) `plot.sf()` function which is what is called behind the scenes in the following command, which creates Figure \@ref(fig:world-all). ```{r world-all, fig.cap="Map of the world using the sf package, with a facet for each attribute.", warning=FALSE, fig.scap="Map of the world using the sf package."} plot(world) ``` Note that instead of creating a single map by default for geographic objects, as most GIS programs do, `plot()`ing `sf` objects results in a map for each variable in the datasets. This behavior can be useful for exploring the spatial distribution of different variables and is discussed further in Section \@ref(basic-map). More broadly, treating geographic objects as regular data frames with spatial powers has many advantages, especially if you are already used to working with data frames. The commonly used `summary()` function, for example, provides a useful overview of the variables within the `world` object. ```{r 02-spatial-data-9} summary(world["lifeExp"]) ``` Although we have only selected one variable for the `summary()` command, it also outputs a report on the geometry. This demonstrates the 'sticky' behavior of the geometry columns of **sf** objects: they are kept unless the user deliberately removes them, as we'll see in Section \@ref(vector-attribute-manipulation). The result provides a quick summary of both the non-spatial and spatial data contained in `world`: the mean average life expectancy is 71 years (ranging from less than 51 to more than 83 years with a median of 73 years) across all countries. ```{block2 02-spatial-data-10, type='rmdnote'} The word `MULTIPOLYGON` in the summary output above refers to the geometry type of features (countries) in the `world` object. This representation is necessary for countries with islands such as Indonesia and Greece. Other geometry types are described in Section \@ref(geometry). ``` It is also worth taking a deeper look at the basic behavior and contents of this simple feature object, which can usefully be thought of as a '**s**patial data **f**rame'. `sf` objects are easy to subset: the code below shows how to return an object containing only the first two rows and the first three columns of the `world` object. The output shows two major differences compared with a regular `data.frame`: the inclusion of additional geographic metadata (`Geometry type`, `Dimension`, `Bounding box` and coordinate reference system information), and the presence of a 'geometry column', here named `geom`: ```{r 02-spatial-data-11} world_mini = world[1:2, 1:3] world_mini ``` All this may seem rather complex, especially for a class system that is supposed to be 'simple'! However, there are good reasons for organizing things this way and using **sf** to work with vector geographic datasets. Before describing each geometry type that the **sf** package supports, it is worth taking a step back to understand the building blocks of `sf` objects. Section \@ref(sf) shows how simple features objects are data frames, with special geometry columns. These spatial columns are often called `geom` or `geometry`: `world$geom` refers to the spatial element of the `world` object described above. These geometry columns are 'list columns' of class `sfc` (see Section \@ref(sfc)). In turn, `sfc` objects are composed of one or more objects of class `sfg`: simple feature geometries that we describe in Section \@ref(sfg). \index{sf!sfc} \index{simple feature columns|see {sf!sfc}} To understand how the spatial components of simple features work, it is vital to understand simple feature geometries. For this reason, we cover each currently supported simple features geometry type in Section \@ref(geometry) before moving on to describe how these can be represented in R using `sf` objects, which are based on `sfg` and `sfc` objects. ```{block2 assignment, type='rmdnote'} The preceding code chunk uses `=` to create a new object called `world_mini` in the command `world_mini = world[1:2, 1:3]`. This is called assignment. An equivalent command to achieve the same result is `world_mini <- world[1:2, 1:3]`. Although 'arrow assignment' is more commonly used, we use 'equals assignment' because it's slightly faster to type and easier to teach due to compatibility with commonly used languages such as Python and JavaScript. Which to use is largely a matter of preference as long as you're consistent (packages such as **styler** can be used to change style). ``` ### Why simple features? Simple features is a widely supported data model that underlies data structures in many GIS applications including QGIS\index{QGIS} and PostGIS\index{PostGIS}. A major advantage of this is that using the data model ensures your work is cross-transferable to other setups, for example importing from and exporting to spatial databases. \index{sf!why simple features} A more specific question from an R perspective is "why use the **sf** package?" There are many reasons (linked to the advantages of the simple features model): - Fast reading and writing of data - Enhanced plotting performance - **sf** objects can be treated as data frames in most operations - **sf** function names are relatively consistent and intuitive (all begin with `st_`) - **sf** functions can be combined with the `|>` operator and works well with the [tidyverse](https://www.tidyverse.org/) collection of R packages\index{tidyverse}. **sf**'s support for **tidyverse** packages is exemplified by `read_sf()`, a function for importing geographic vector data covered in detail in Section \@ref(iovec). Unlike the function `st_read()`, which returns attributes stored in a base R `data.frame` (and which emits verbose messages, not shown in the code chunk below), `read_sf()` silently returns data as a **tidyverse** `tibble`. This is demonstrated below: ```{r, message=FALSE} world_dfr = st_read(system.file("shapes/world.gpkg", package = "spData")) world_tbl = read_sf(system.file("shapes/world.gpkg", package = "spData")) class(world_dfr) class(world_tbl) ``` As described in Chapter \@ref(attr), which shows how to manipulate `sf` objects with **tidyverse** functions, **sf** is now the go-to package for analysis of spatial vector data in R. **spatstat**, a package ecosystem which provides numerous functions for spatial statistics, and **terra** both have vector geographic data classes, but neither has the same level of uptake as **sf** does for working with vector data. Many popular packages build on **sf**, as shown by the rise in its popularity in terms of number of downloads per day, as shown in Section \@ref(r-ecosystem) in the previous chapter. ### Basic maps {#basic-map} Basic geographic visualizations (maps) are created in **sf** with base R's `plot()` function. By default, this creates a multi-panel plot, one sub-plot for each variable of the object, as illustrated in the left-hand panel in Figure \@ref(fig:sfplot). A legend or 'key' with a continuous color is produced if the object to be plotted has a single variable (see the right-hand panel). You can also set fixed colors in `plot()` commands with `col` and `border` arguments. \index{map-making!basic} ```{r sfplot, fig.cap="Plotting with sf, with multiple variables (left) and a single variable (right).", out.width="49%", fig.show='hold', warning=FALSE, fig.scap="Plotting with sf."} plot(world[3:6]) plot(world["pop"]) ``` Plots are added as layers to existing images by setting `add = TRUE`.^[ `plot()`ing of **sf** objects uses `sf:::plot.sf()` behind the scenes. `plot()` is a generic method that behaves differently depending on the class of object being plotted. ] To demonstrate this, and to provide an insight into the contents of Chapters \@ref(attr) and \@ref(spatial-operations) on attribute and spatial data operations, the subsequent code chunk filters countries in Asia and combines them into a single feature: ```{r 02-spatial-data-14, warning=FALSE} world_asia = world[world$continent == "Asia", ] asia = st_union(world_asia) ``` We can now plot the Asian continent over a map of the world. Note that the first plot must only have one facet for `add = TRUE` to work. If the first plot has a key, `reset = FALSE` must be used: ```{r asia, out.width='50%', fig.cap="Plot of Asia added as a layer on top of countries worldwide."} plot(world["pop"], reset = FALSE) plot(asia, add = TRUE, col = "red") ``` ```{block2 plottingpacks, type='rmdnote'} Adding layers in this way can be used to verify the geographic correspondence between layers: the `plot()` function is fast and requires few lines of code, but its functionality is limited. For more advanced map-making we recommend using dedicated visualization packages such as **tmap** [@tmap2018] (see Chapter \@ref(adv-map)). ``` There are various ways to modify maps with **sf**'s `plot()` method. Because **sf** extends base R plotting methods, `plot()`'s arguments work with `sf` objects (see `?graphics::plot` and `?par` for information on arguments such as `main =`).^[ Note: many plot arguments are ignored in facet maps, when more than one `sf` column is plotted.] \index{base plot|see {map-making}}\index{map-making!base plotting} Figure \@ref(fig:contpop) illustrates this flexibility by overlaying circles, whose diameters (set with `cex =`) represent country populations, on a map of the world. An unprojected version of this figure can be created with the following commands (see exercises at the end of this chapter and the script [`02-contplot.R`](https://github.com/geocompx/geocompr/blob/main/code/02-contpop.R) to reproduce Figure \@ref(fig:contpop)): ```{r 02-spatial-data-16, eval=FALSE} plot(world["continent"], reset = FALSE) cex = sqrt(world$pop) / 10000 world_cents = st_centroid(world, of_largest = TRUE) plot(st_geometry(world_cents), add = TRUE, cex = cex) ``` ```{r contpop, fig.cap="Country continents (represented by fill color) and 2015 populations (represented by circles, with area proportional to population).", echo=FALSE, warning=FALSE, fig.scap="Country continents and 2015 populations."} source("https://github.com/geocompx/geocompr/raw/main/code/02-contpop.R") ``` The code above uses the function `st_centroid()` to convert one geometry type (polygons) to another (points) (see Chapter \@ref(geometry-operations)), the aesthetics of which are varied with the `cex` argument. \index{bounding box} **sf**'s plot method also has arguments specific to geographic data. `expandBB`, for example, can be used to plot an `sf` object in context: it takes a numeric vector of length four that expands the bounding box of the plot relative to zero in the following order: bottom, left, top, right. This is used to plot India in the context of its giant Asian neighbors, with an emphasis on China to the east, in the following code chunk, which generates Figure \@ref(fig:china) (see exercises below on adding text to plots):^[ Note the use of `st_geometry(india)` to return only the geometry associated with the object to prevent attributes being plotted in a simple feature column (`sfc`) object. An alternative is to use `india[0]`, which returns an `sf` object that contains no attribute data.. ] ```{r 02-spatial-data-17, eval=FALSE} india = world[world$name_long == "India", ] plot(st_geometry(india), expandBB = c(0, 0.2, 0.1, 1), col = "gray", lwd = 3) plot(st_geometry(world_asia), add = TRUE) ``` ```{r china, fig.cap="India in context, demonstrating the expandBB argument.", warning=FALSE, echo=FALSE, out.width="50%"} old_par = par(mar = rep(0, 4)) india = world[world$name_long == "India", ] indchi = world_asia[grepl("Indi|Chi", world_asia$name_long), ] indchi_points = st_centroid(indchi) indchi_coords = st_coordinates(indchi_points) plot(st_geometry(india), expandBB = c(-0.2, 0.5, 0, 1), col = "gray", lwd = 3) plot(world_asia[0], add = TRUE) text(indchi_coords[, 1], indchi_coords[, 2], indchi$name_long) par(old_par) ``` ```{r, eval=FALSE, echo=FALSE} waldo::compare(st_geometry(world), world[0]) ``` Note the use of `lwd` to emphasize India in the plotting code. See Section \@ref(static-maps) for other visualization techniques for representing a range of geometry types, the subject of the next section. ### Geometry types {#geometry} Geometries are the basic building blocks of simple features. Simple features in R can take on one of the 18 geometry types supported by the **sf** package. In this chapter we will focus on the seven most commonly used types: `POINT`, `LINESTRING`, `POLYGON`, `MULTIPOINT`, `MULTILINESTRING`, `MULTIPOLYGON` and `GEOMETRYCOLLECTION`. \index{geometry types|see {sf!geometry types}} \index{sf!geometry types} Generally, well-known binary (WKB) or well-known text (WKT) are the standard encoding for simple feature geometries. WKB representations are usually hexadecimal strings easily readable for computers. This is why GIS and spatial databases use WKB to transfer and store geometry objects. WKT, on the other hand, is a human-readable text markup description of simple features. Both formats are exchangeable, and if we present one, we will naturally choose the WKT representation. \index{well-known text} \index{WKT|see {well-known text}} \index{well-known binary} \index{WKB|see {well-known binary}} The basis for each geometry type is the point. A point is simply a coordinate in two-, three-, or four-dimensional space (see `vignette("sf1")` for more information) such as (Figure \@ref(fig:sfcs), left panel): \index{sf!point} - `POINT (5 2)` \index{sf!linestring} A linestring is a sequence of points with a straight line connecting the points, for example (Figure \@ref(fig:sfcs), middle panel): - `LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2)` A polygon is a sequence of points that form a closed, non-intersecting ring. Closed means that the first and the last point of a polygon have the same coordinates (Figure \@ref(fig:sfcs), right panel).^[ By definition, a polygon has one exterior boundary (outer ring) and can have zero or more interior boundaries (inner rings), also known as holes. A polygon with a hole would be, for example, `POLYGON ((1 5, 2 2, 4 1, 4 4, 1 5), (2 4, 3 4, 3 3, 2 3, 2 4))` ] \index{sf!hole} - Polygon without a hole: `POLYGON ((1 5, 2 2, 4 1, 4 4, 1 5))` ```{r sfcs, echo=FALSE, fig.cap="Point, linestring and polygon geometries.", fig.asp=0.4} old_par = par(mfrow = c(1, 3), pty = "s", mar = c(0, 3, 1, 0)) plot(st_as_sfc(c("POINT(5 2)")), axes = TRUE, main = "POINT") plot(st_as_sfc("LINESTRING(1 5, 4 4, 4 1, 2 2, 3 2)"), axes = TRUE, main = "LINESTRING") plot(st_as_sfc("POLYGON((1 5, 2 2, 4 1, 4 4, 1 5))"), col="gray", axes = TRUE, main = "POLYGON") par(old_par) ``` ```{r polygon_hole, echo=FALSE, out.width="30%", eval=FALSE} # not printed - enough of these figures already (RL) par(pty = "s") plot(st_as_sfc("POLYGON((1 5, 2 2, 4 1, 4 4, 1 5), (2 4, 3 4, 3 3, 2 3, 2 4))"), col = "gray", axes = TRUE, main = "POLYGON with a hole") ``` So far we have created geometries with only one geometric entity per feature. \index{sf!multi features} Simple feature standard also allows multiple geometries of a single type to exist within a single feature within "multi" version of each geometry type (Figure \@ref(fig:multis)): - Multipoint: `MULTIPOINT (5 2, 1 3, 3 4, 3 2)` - Multilinestring: `MULTILINESTRING ((1 5, 4 4, 4 1, 2 2, 3 2), (1 2, 2 4))` - Multipolygon: `MULTIPOLYGON (((1 5, 2 2, 4 1, 4 4, 1 5), (0 2, 1 2, 1 3, 0 3, 0 2)))` ```{r multis, echo=FALSE, fig.cap="Illustration of multi* geometries.", fig.asp=0.4} old_par = par(mfrow = c(1, 3), pty = "s", mar = c(0, 3, 1, 0)) plot(st_as_sfc("MULTIPOINT (5 2, 1 3, 3 4, 3 2)"), axes = TRUE, main = "MULTIPOINT") plot(st_as_sfc("MULTILINESTRING ((1 5, 4 4, 4 1, 2 2, 3 2), (1 2, 2 4))"), axes = TRUE, main = "MULTILINESTRING") plot(st_as_sfc("MULTIPOLYGON (((1 5, 2 2, 4 1, 4 4, 1 5), (0 2, 1 2, 1 3, 0 3, 0 2)))"), col = "gray", axes = TRUE, main = "MULTIPOLYGON") par(old_par) ``` Finally, a geometry collection can contain any combination of geometries including (multi)points and linestrings (see Figure \@ref(fig:geomcollection)): \index{sf!geometry collection} - Geometry collection: `GEOMETRYCOLLECTION (MULTIPOINT (5 2, 1 3, 3 4, 3 2), LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2))` ```{r geomcollection, echo=FALSE, fig.cap="Illustration of a geometry collection.", fig.asp=0.4} # Plotted - it is referenced in ch5 (st_cast) old_par = par(pty = "s", mar = c(2, 3, 3, 0)) plot(st_as_sfc("GEOMETRYCOLLECTION (MULTIPOINT (5 2, 1 3, 3 4, 3 2), LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2))"), axes = TRUE, main = "GEOMETRYCOLLECTION", col = 1) par(old_par) ``` ### The sf class {#sf} Simple features consist of two main parts: geometries and non-geographic attributes. Figure \@ref(fig:02-sfdiagram) shows how an sf object is created -- geometries come from an `sfc` object, while attributes are taken from a `data.frame` or `tibble`.^[To learn more about building sf geometries from scratch, see the following Sections \@ref(sfg) and \@ref(sfc).] ```{r 02-sfdiagram, fig.cap="Building blocks of sf objects.", echo=FALSE} # source("code/02-sfdiagram.R") knitr::include_graphics("images/02-sfdiagram.png") ``` Non-geographic attributes represent the name of the feature or other attributes such as measured values, groups, and other things. \index{sf!class} To illustrate attributes, we will represent a temperature of 25°C in London on June 21, 2023. This example contains a geometry (coordinates), and three attributes with three different classes (place name, temperature and date).^[ Other attributes might include an urbanity category (city or village), or a remark if the measurement was made using an automatic station. ] Objects of class `sf` represent such data by combining the attributes (`data.frame`) with the simple feature geometry column (`sfc`). They are created with `st_sf()` as illustrated below, which creates the London example described above: ```{r 02-spatial-data-33} lnd_point = st_point(c(0.1, 51.5)) # sfg object lnd_geom = st_sfc(lnd_point, crs = "EPSG:4326") # sfc object lnd_attrib = data.frame( # data.frame object name = "London", temperature = 25, date = as.Date("2023-06-21") ) lnd_sf = st_sf(lnd_attrib, geometry = lnd_geom) # sf object ``` What just happened? First, the coordinates were used to create the simple feature geometry (`sfg`). Second, the geometry was converted into a simple feature geometry column (`sfc`), with a CRS. Third, attributes were stored in a `data.frame`, which was combined with the `sfc` object with `st_sf()`. This results in an `sf` object, as demonstrated below (some output is omitted): ```{r 02-spatial-data-34, eval=FALSE} lnd_sf #> Simple feature collection with 1 features and 3 fields #> ... #> name temperature date geometry #> 1 London 25 2023-06-21 POINT (0.1 51.5) ``` ```{r 02-spatial-data-35} class(lnd_sf) ``` The result shows that `sf` objects actually have two classes, `sf` and `data.frame`. Simple features are simply data frames (square tables), but with spatial attributes stored in a list column, usually called `geometry` or `geom`, as described in Section \@ref(intro-sf). This duality is central to the concept of simple features: most of the time a `sf` can be treated as and behaves like a `data.frame`. Simple features are, in essence, data frames with a spatial extension. ```{r 02-spatial-data-36, eval=FALSE, echo=FALSE} ruan_point = st_point(c(-9, 53)) # sfc object our_geometry = st_sfc(lnd_point, ruan_point, crs = 4326) # data frame object our_attributes = data.frame( name = c("London", "Ruan"), temperature = c(25, 13), date = c(as.Date("2023-06-21"), as.Date("2023-06-22")), category = c("city", "village"), automatic = c(FALSE, TRUE)) # sf object sf_points = st_sf(our_attributes, geometry = our_geometry) ``` ### Simple feature geometries (sfg) {#sfg} The `sfg` class represents the different simple feature geometry types in R: point, linestring, polygon (and their 'multi' equivalents, such as multipoints) or geometry collection. \index{simple feature geometries|see {sf!sfg}} Usually you are spared the tedious task of creating geometries on your own since you can simply import an already existing spatial file. However, there are a set of functions to create simple feature geometry objects (`sfg`) from scratch, if needed. The names of these functions are simple and consistent, as they all start with the `st_` prefix and end with the name of the geometry type in lowercase letters: - A point: `st_point()` - A linestring: `st_linestring()` - A polygon: `st_polygon()` - A multipoint: `st_multipoint()` - A multilinestring: `st_multilinestring()` - A multipolygon: `st_multipolygon()` - A geometry collection: `st_geometrycollection()` `sfg` objects can be created from three base R data types: 1. A numeric vector: a single point 2. A matrix: a set of points, where each row represents a point, a multipoint or linestring 3. A list: a collection of objects such as matrices, multilinestrings or geometry collections The function `st_point()` creates single points from numeric vectors: ```{r 02-spatial-data-18} st_point(c(5, 2)) # XY point st_point(c(5, 2, 3)) # XYZ point st_point(c(5, 2, 1), dim = "XYM") # XYM point st_point(c(5, 2, 3, 1)) # XYZM point ``` The results show that XY (2D coordinates), XYZ (3D coordinates) and XYZM (3D with an additional variable, typically measurement accuracy) point types are created from vectors of lengths 2, 3, and 4, respectively. The XYM type must be specified using the `dim` argument (which is short for dimension). By contrast, use matrices in the case of multipoint (`st_multipoint()`) and linestring (`st_linestring()`) objects: ```{r 02-spatial-data-19} # the rbind function simplifies the creation of matrices ## MULTIPOINT multipoint_matrix = rbind(c(5, 2), c(1, 3), c(3, 4), c(3, 2)) st_multipoint(multipoint_matrix) ## LINESTRING linestring_matrix = rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)) st_linestring(linestring_matrix) ``` Finally, use lists for the creation of multilinestrings, (multi-)polygons and geometry collections: ```{r 02-spatial-data-20} ## POLYGON polygon_list = list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))) st_polygon(polygon_list) ``` ```{r 02-spatial-data-21} ## POLYGON with a hole polygon_border = rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5)) polygon_hole = rbind(c(2, 4), c(3, 4), c(3, 3), c(2, 3), c(2, 4)) polygon_with_hole_list = list(polygon_border, polygon_hole) st_polygon(polygon_with_hole_list) ``` ```{r 02-spatial-data-22} ## MULTILINESTRING multilinestring_list = list(rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)), rbind(c(1, 2), c(2, 4))) st_multilinestring(multilinestring_list) ``` ```{r 02-spatial-data-23} ## MULTIPOLYGON multipolygon_list = list(list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))), list(rbind(c(0, 2), c(1, 2), c(1, 3), c(0, 3), c(0, 2)))) st_multipolygon(multipolygon_list) ``` ```{r 02-spatial-data-24, eval=FALSE} ## GEOMETRYCOLLECTION geometrycollection_list = list(st_multipoint(multipoint_matrix), st_linestring(linestring_matrix)) st_geometrycollection(geometrycollection_list) #> GEOMETRYCOLLECTION (MULTIPOINT (5 2, 1 3, 3 4, 3 2), #> LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2)) ``` ### Simple feature columns (sfc) {#sfc} One `sfg` object contains only a single simple feature geometry. A simple feature geometry column (`sfc`) is a list of `sfg` objects, which is additionally able to contain information about the CRS in use. For instance, to combine two simple features into one object with two features, we can use the `st_sfc()` function. \index{sf!simple feature columns (sfc)} This is important since `sfc` represents the geometry column in **sf** data frames: ```{r 02-spatial-data-25} # sfc POINT point1 = st_point(c(5, 2)) point2 = st_point(c(1, 3)) points_sfc = st_sfc(point1, point2) points_sfc ``` In most cases, an `sfc` object contains objects of the same geometry type. Therefore, when we convert `sfg` objects of type polygon into a simple feature geometry column, we would also end up with an `sfc` object of type polygon, which can be verified with `st_geometry_type()`. Equally, a geometry column of multilinestrings would result in an `sfc` object of type multilinestring: ```{r 02-spatial-data-26} # sfc POLYGON polygon_list1 = list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))) polygon1 = st_polygon(polygon_list1) polygon_list2 = list(rbind(c(0, 2), c(1, 2), c(1, 3), c(0, 3), c(0, 2))) polygon2 = st_polygon(polygon_list2) polygon_sfc = st_sfc(polygon1, polygon2) st_geometry_type(polygon_sfc) ``` ```{r 02-spatial-data-27} # sfc MULTILINESTRING multilinestring_list1 = list(rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)), rbind(c(1, 2), c(2, 4))) multilinestring1 = st_multilinestring((multilinestring_list1)) multilinestring_list2 = list(rbind(c(2, 9), c(7, 9), c(5, 6), c(4, 7), c(2, 7)), rbind(c(1, 7), c(3, 8))) multilinestring2 = st_multilinestring((multilinestring_list2)) multilinestring_sfc = st_sfc(multilinestring1, multilinestring2) st_geometry_type(multilinestring_sfc) ``` It is also possible to create an `sfc` object from `sfg` objects with different geometry types: ```{r 02-spatial-data-28} # sfc GEOMETRY point_multilinestring_sfc = st_sfc(point1, multilinestring1) st_geometry_type(point_multilinestring_sfc) ``` As mentioned before, `sfc` objects can additionally store information on the CRS. The default value is `NA` (*Not Available*), as can be verified with `st_crs()`: ```{r 02-spatial-data-29} st_crs(points_sfc) ``` All geometries in `sfc` objects must have the same CRS. A CRS can be specified with the `crs` argument of `st_sfc()` (or `st_sf()`), which takes a **CRS identifier** provided as a text string, such as `crs = "EPSG:4326"` (see Section \@ref(crs-in-r) for other CRS representations and details on what this means). ```{r 02-spatial-data-30, eval=FALSE} # Set the CRS with an identifier referring to an 'EPSG' CRS code: points_sfc_wgs = st_sfc(point1, point2, crs = "EPSG:4326") st_crs(points_sfc_wgs) # print CRS (only first 4 lines of output shown) #> Coordinate Reference System: #> User input: EPSG:4326 #> wkt: #> GEOGCRS["WGS 84", #> ... ``` ### The sfheaders package {#sfheaders} ```{r sfheaers-setup, echo=FALSE} ## Detatch {sf} to remove 'print' methods ## because I want to show the underlying structure ## ## library(sf) will be called later # unloadNamespace("sf") # errors # pkgload::unload("sf") ``` \index{sfheaders} **sfheaders** is an R package that speeds-up the construction, conversion and manipulation of `sf` objects [@cooley_sfheaders_2020]. It focuses on building `sf` objects from vectors, matrices and data frames, rapidly, and without depending on the **sf** library; and exposing its underlying C++ code through header files (hence the name, **sfheaders**). This approach enables others to extend it using compiled and fast-running code. Every core **sfheaders** function has a corresponding C++ implementation, as described in the [`Cpp` vignette](https://dcooley.github.io/sfheaders/articles/Cpp.html). For most people, the R functions will be more than sufficient to benefit from the computational speed of the package. **sfheaders** was developed separately from **sf**, but aims to be fully compatible, creating valid `sf` objects of the type described in preceding sections. The simplest use case for **sfheaders** is demonstrated in the code chunks below with examples of building `sfg`, `sfc`, and `sf` objects showing: - A vector converted to `sfg_POINT` - A matrix converted to `sfg_LINESTRING` - A data frame converted to `sfg_POLYGON` We will start by creating the simplest possible `sfg` object, a single coordinate pair, assigned to a vector named `v`: ```{r sfheaders-sfg_point} #| eval: false v = c(1, 1) v_sfg_sfh = sfheaders::sfg_point(obj = v) v_sfg_sfh # printing without sf loaded #> [,1] [,2] #> [1,] 1 1 #> attr(,"class") #> [1] "XY" "POINT" "sfg" ``` ```{r} #| echo: false v = c(1, 1) v_sfg_sfh = sfheaders::sfg_point(obj = v) ``` ```{r, eval=FALSE, echo=FALSE} v_sfg_sfh = sf::st_point(v) ``` The example above shows how the `sfg` object `v_sfg_sfh` is printed when **sf** is not loaded, demonstrating its underlying structure. When **sf** is loaded (as is the case here), the result of the above command is indistinguishable from `sf` objects: ```{r} v_sfg_sf = st_point(v) print(v_sfg_sf) == print(v_sfg_sfh) ``` ```{r, echo=FALSE, eval=FALSE} # (although `sfg` objects created with **sfheaders** have a dimension while `sfg` objects created with the **sf** package do not) waldo::compare(v_sfg_sf, v_sfg_sfh) dim(v_sfg_sf) dim(v_sfg_sfh) attr(v_sfg_sfh, "dim") ``` The next examples shows how **sfheaders** creates `sfg` objects from matrices and data frames: ```{r sfheaders-sfg_linestring} # matrices m = matrix(1:8, ncol = 2) sfheaders::sfg_linestring(obj = m) # data frames df = data.frame(x = 1:4, y = 4:1) sfheaders::sfg_polygon(obj = df) ``` Reusing the objects `v`, `m`, and `df` we can also build simple feature columns (`sfc`) as follows (outputs not shown): ```{r sfheaders-sfc_point2, eval=FALSE} sfheaders::sfc_point(obj = v) sfheaders::sfc_linestring(obj = m) sfheaders::sfc_polygon(obj = df) ``` Similarly, `sf` objects can be created as follows: ```{r sfheaders-sfc_point, eval=FALSE} sfheaders::sf_point(obj = v) sfheaders::sf_linestring(obj = m) sfheaders::sf_polygon(obj = df) ``` In each of these examples, the CRS is not defined. If you plan on doing any calculations or geometric operations using **sf** functions, we encourage you to set the CRS (see Chapter \@ref(reproj-geo-data) for details): ```{r sfheaders-crs} df_sf = sfheaders::sf_polygon(obj = df) st_crs(df_sf) = "EPSG:4326" ``` **sfheaders** is also good at 'deconstructing' and 'reconstructing' `sf` objects, meaning converting geometry columns into data frames that contain data on the coordinates of each vertex and geometry feature (and multi-feature) ids. It is fast and reliable at 'casting' geometry columns to different types, a topic covered in Chapter \@ref(geometry-operations). Benchmarks, in the package's [documentation](https://dcooley.github.io/sfheaders/articles/examples.html#performance) and in test code developed for this book, show it is much faster than the `sf` package for such operations. ### Spherical geometry operations with S2 {#s2} Spherical geometry engines are based on the fact that the world is round, while simple mathematical procedures for geocomputation, such as calculating a straight line between two points or the area enclosed by a polygon, assume planar (projected) geometries. Since **sf** version 1.0.0, R supports spherical geometry operations 'out of the box' (and by default), thanks to its interface to Google's S2 spherical geometry engine via the **s2** interface package \index{S2}. S2 is perhaps best known as an example of a Discrete Global Grid System (DGGS). Another example is the [H3](https://h3geo.org/) global hexagonal hierarchical spatial index [@bondaruk_assessing_2020]. Although potentially useful for describing locations anywhere on Earth using character strings, the main benefit of **sf**'s interface to S2 is its provision of drop-in functions for calculations such as distance, buffer, and area calculations, as described in **sf**'s built-in documentation which can be opened with the command [`vignette("sf7")`](https://r-spatial.github.io/sf/articles/sf7.html). **sf** can run in two modes with respect to S2: on and off. By default the S2 geometry engine is turned on, as can be verified with the following command: ```{r} sf_use_s2() ``` An example of the consequences of turning the geometry engine off is shown below, by creating buffers around the `india` object created earlier in the chapter (note the warnings emitted when S2 is turned off) (Figure \@ref(fig:s2example)): ```{r} india_buffer_with_s2 = st_buffer(india, 1) # 1 meter sf_use_s2(FALSE) india_buffer_without_s2 = st_buffer(india, 1) # 1 degree ``` ```{r s2example, echo=FALSE, fig.cap="Example of the consequences of turning off the S2 geometry engine. Both representations of a buffer around India were created with the same command but the purple polygon object was created with S2 switched on, resulting in a buffer of 1 m. The larger light green polygon was created with S2 switched off, resulting in a buffer of 1 degree, which is not accurate.", message=FALSE} library(tmap) tm1 = tm_shape(india_buffer_with_s2) + tm_fill(fill = hcl.colors(4, palette = "purple green")[2], lwd = 0.01) + tm_shape(india) + tm_fill(fill = "gray95") + tm_title("st_buffer() with dist = 1") + tm_title("s2 switched on (default)", position = tm_pos_in("right", "bottom"), size = 1) tm2 = tm_shape(india_buffer_without_s2) + tm_fill(fill = hcl.colors(4, palette = "purple green")[3], lwd = 0.01) + tm_shape(india) + tm_fill(fill = "gray95") + tm_title(" ") + tm_title("s2 switched off", position = tm_pos_in("right", "bottom"), size = 1) tmap_arrange(tm1, tm2, ncol = 2) ``` The right panel of Figure \@ref(fig:s2example) is incorrect, as the buffer of 1 degree does not return the equal distance around the `india` polygon (for more explanation of this issue, see Section \@ref(geom-proj)). Throughout this book, we will assume that S2 is turned on, unless explicitly stated. Turn it on again with the following command. ```{r} sf_use_s2(TRUE) ``` ```{block2 09-gis-2, type="rmdnote"} Although the **sf**'s use of S2 makes sense in many cases, in some cases there are good reasons for turning S2 off for the duration of an R session or even for an entire project. As documented in issue [1771](https://github.com/r-spatial/sf/issues/1771) in **sf**'s GitHub repo, the default behavior can make code that would work with S2 turned off (and with older versions of **sf**) fail. These edge cases include operations on polygons that are not valid according to S2's stricter definition. If you see error messages such as `#> Error in s2_geography_from_wkb ...` it may be worth trying the command that generated the error message again, after turning off S2. To turn off S2 for the entirety of a project, you can create a file called .Rprofile in the root directory (the main folder) of your project containing the command `sf::sf_use_s2(FALSE)`. ``` ## Raster data The spatial raster data model represents the world with the continuous grid of cells (often also called pixels; Figure \@ref(fig:raster-intro-plot):A)\index{raster data model}. This data model often refers to so-called regular grids, in which each cell has the same, constant size -- and we will focus on the regular grids in this book only. However, several other types of grids exist, including rotated, sheared, rectilinear, and curvilinear grids (see chapter 1 of @pebesma_spatial_2023 or chapter 2 of @tennekes_elegant_2022). The raster data model usually consists of a raster header\index{raster!header} and a matrix (with rows and columns) representing equally spaced cells (often also called pixels; Figure \@ref(fig:raster-intro-plot):A).^[ Depending on the file format, the header is part of the actual image data file, e.g., GeoTIFF, or stored in an extra header or world file, e.g., ASCII grid formats. There is also the headerless (flat) binary raster format which should facilitate the import into various software programs.] The raster header\index{raster!header} defines the CRS, the extent and the origin. \index{raster} \index{raster data model} The origin (or starting point) is frequently the coordinate of the lower left corner of the matrix (the **terra** package, however, uses the upper left corner, by default (Figure \@ref(fig:raster-intro-plot):B)). The header defines the extent via the number of columns, the number of rows and the cell size resolution. The resolution can be calculated as follows: $$ \text{resolution} = \frac{\text{xmax} - \text{xmin}}{\text{ncol}}, \frac{\text{ymax} - \text{ymin}}{\text{nrow}} $$ Starting from the origin, we can easily access and modify each single cell by either using the ID of a cell (Figure \@ref(fig:raster-intro-plot):B) or by explicitly specifying the rows and columns. This matrix representation avoids storing explicitly the coordinates for the four corner points (in fact, it only stores one coordinate, namely the origin) of each cell corner as would be the case for rectangular vector polygons. This and map algebra (Section \@ref(map-algebra)) make raster processing much more efficient and faster than vector data processing. In contrast to vector data, the cell of one raster layer can only hold a single value.^[Thus, to store many values for a single location we need to have many raster layers.] The value might be continuous or categorical (Figure \@ref(fig:raster-intro-plot)C). ```{r raster-intro-plot, echo = FALSE, fig.cap = "Raster data types.", fig.scap="Raster data types.", fig.asp=0.5, message=FALSE} source("https://github.com/geocompx/geocompr/raw/main/code/02-raster-intro-plot.R", print.eval = TRUE) ``` Raster maps usually represent continuous phenomena such as elevation, temperature, population density or spectral data. Discrete features such as soil or land-cover classes can also be represented in the raster data model. Both uses of raster datasets are illustrated in Figure \@ref(fig:raster-intro-plot2), which shows how the borders of discrete features may become blurred in raster datasets. Depending on the nature of the application, vector representations of discrete features may be more suitable. ```{r raster-intro-plot2, echo=FALSE, fig.cap="Examples of (A) continuous and (B) categorical rasters.", warning=FALSE, message=FALSE} source("code/02-raster-intro-plot2.R", print.eval = TRUE) # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146617327-45919232-a6a3-4d9d-a158-afa87f47381b.png") ``` ### R packages for working with raster data Over the last two decades, several packages for reading and processing raster datasets have been developed. \index{raster (package)}\index{terra (package)}\index{stars (package)} As outlined in Section \@ref(history-of-r-spatial), chief among them was **raster**, which led to a step change in R's raster capabilities when it was launched in 2010 and the premier package in the space until the development of **terra** and **stars**. Both more recently developed packages provide powerful and performant functions for working with raster datasets, and there is substantial overlap between their possible use cases. In this book, we focus on **terra**, which replaces the older and (in most cases) slower **raster**. Before learning about how **terra**'s class system works, this section describes similarities and differences between **terra** and **stars**; this knowledge will help decide which is most appropriate in different situations. First, **terra** focuses on the most common raster data model (regular grids), while **stars** also allows storing less popular models (including regular, rotated, sheared, rectilinear, and curvilinear grids). While **terra** usually handles one or multi-layered rasters^[It also has an additional class `SpatRasterDataset` for storing many collections of datasets.], the **stars** package provides ways to store raster data cubes -- a raster object with many layers (e.g., bands), for many moments in time (e.g., months), and many attributes (e.g., sensor type A and sensor type B). Importantly, in both packages, all layers or elements of a data cube must have the same spatial dimensions and extent. Second, both packages allow to either read all of the raster data into memory or just to read its metadata -- this is usually done automatically based on the input file size. However, they store raster values very differently. **terra** is based on C++ code and mostly uses C++ pointers. **stars** stores values as lists of arrays for smaller rasters or just a file path for larger ones. Third, **stars** functions are closely related to the vector objects and functions in **sf**, while **terra** uses its own class of objects for vector data, namely `SpatVector`, but also accepts `sf` ones.^[It is also possible to convert between these classes with `vect()` (from `sf` to `SpatVector`) and `st_as_sf()` (from `SpatVector` to `sf`).] Fourth, both packages have a different approach for how various functions work on their objects. The **terra** package mostly relies on a large number of built-in functions, where each function has a specific purpose (e.g., resampling or cropping). On the other hand, **stars** uses some built-in functions (usually with names starting with `st_`), some existing **dplyr** functions (e.g., `filter()` or `slice()`), and also has its own methods for existing R functions (e.g., `split()` or `aggregate()`). Importantly, it is straightforward to convert objects from **terra** to **stars** (using `st_as_stars()`) and the other way round (using `rast()`). We also encourage you to read @pebesma_spatial_2023 for the most comprehensive introduction to the **stars** package. ### Introduction to terra \index{terra (package)} The **terra** package supports raster objects in R. It provides an extensive set of functions to create, read, export, manipulate and process raster datasets. **terra**'s functionality is largely the same as the more mature **raster** package, but there are some differences: **terra** functions are usually more computationally efficient than **raster** equivalents. On the other hand, the **raster** class system is popular and used by many other packages. You can seamlessly translate between the two types of object to ensure backward compatibility with older scripts and packages, for example, with the functions [`raster()`](https://rspatial.github.io/raster/reference/raster.html), [`stack()`](https://rspatial.github.io/raster/reference/stack.html), and `brick()` in the **raster** package (see the previous chapter for more on the evolution of R packages for working with geographic data). In addition to functions for raster data manipulation, **terra** provides many low-level functions that can form a foundation for developing new tools for working with raster datasets. \index{terra (package)} **terra** also lets you work on large raster datasets that are too large to fit into the main memory. In this case, **terra** provides the possibility to divide the raster into smaller chunks, and processes these iteratively instead of loading the whole raster file into RAM. For the illustration of **terra** concepts, we will use datasets from the **spDataLarge** [@R-spDataLarge]. It consists of a few raster objects and one vector object covering an area of Zion National Park (Utah, USA). For example, `srtm.tif` is a digital elevation model of this area (for more details, see its documentation `?srtm`). First, let's create a `SpatRaster` object named `my_rast`: ```{r 02-spatial-data-37, message=FALSE} raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") my_rast = rast(raster_filepath) class(my_rast) ``` Typing the name of the raster into the console, will print out the raster header (dimensions, resolution, extent, CRS) and some additional information (class, data source, summary of the raster values): ```{r 02-spatial-data-38} my_rast ``` Dedicated functions report each component: `dim()` returns the number of rows, columns and layers; `ncell()` the number of cells (pixels); `res()` the spatial resolution; `ext()` its spatial extent; and `crs()` its CRS (raster reprojection is covered in Section \@ref(reproj-ras)). `inMemory()` reports whether the raster data is stored in memory or on disk, and `sources` specifies the file location. ```{block2 terrahelp, type='rmdnote'} `help("terra-package")` returns a full list of all available **terra** functions. ``` ### Basic map-making {#basic-map-raster} Similar to the **sf** package, **terra** also provides `plot()` methods for its own classes. As shown in the following command, the `plot()` function creates a basic raster plot, resulting in Figure \@ref(fig:basic-new-raster-plot). \index{map-making!basic raster} ```{r basic-new-raster-plot, fig.cap="Basic raster plot."} plot(my_rast) ``` There are several other approaches for plotting raster data in R that are outside the scope of this section, including: - `plotRGB()` function from the **terra** package to create a plot based on three layers in a `SpatRaster` object - Packages such as **tmap** to create static and interactive maps of raster and vector objects (see Chapter \@ref(adv-map)) - Functions, for example `levelplot()` from the **rasterVis** package, to create facets, a common technique for visualizing change over time ### Raster classes {#raster-classes} \index{terra (package)} The `SpatRaster` class represents rasters object of **terra**. The easiest way to create a raster object in R is to read-in a raster file from disk or from a server (Section \@ref(raster-data-read)). \index{raster!class} ```{r 02-spatial-data-41} single_raster_file = system.file("raster/srtm.tif", package = "spDataLarge") single_rast = rast(raster_filepath) ``` The **terra** package supports numerous drivers with the help of the GDAL library. Rasters from files are usually not read entirely into RAM, with an exception of their header and a pointer to the file itself. Rasters can also be created from scratch, using the same `rast()` function. This is illustrated in the subsequent code chunk, which results in a new `SpatRaster` object. The resulting raster consists of 36 cells (6 columns and 6 rows specified by `nrows` and `ncols`) centered around the Prime Meridian and the Equator (see `xmin`, `xmax`, `ymin` and `ymax` parameters). Values (`vals`) are assigned to each cell: 1 to cell 1, 2 to cell 2, and so on. Remember: `rast()` fills cells row-wise (unlike `matrix()`) starting at the upper left corner, meaning the top row contains the values 1 to 6, the second 7 to 12, etc. For other ways of creating raster objects, see `?rast`. ```{r 02-spatial-data-42} new_raster = rast(nrows = 6, ncols = 6, xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, vals = 1:36) ``` Given the number of rows and columns as well as the extent (`xmin`, `xmax`, `ymin`, `ymax`), the resolution has to be 0.5. The unit of the resolution is that of the underlying CRS. Here, it is degrees, because the default CRS of raster objects is WGS84. However, one can specify any other CRS with the `crs` argument. The `SpatRaster` class also handles multiple layers, which typically correspond to a single multi-spectral satellite file or a time-series of rasters. ```{r 02-spatial-data-45} multi_raster_file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(multi_raster_file) multi_rast ``` `nlyr()` retrieves the number of layers stored in a `SpatRaster` object: ```{r 02-spatial-data-47} nlyr(multi_rast) ``` For multi-layer raster objects, layers can be selected with the `[[` and `$` operators, for example with commands `multi_rast[["landsat_1"]]` and `multi_rast$landsat_1`. The `terra::subset()` can also be used to select layers. It accepts a layer number or its name as the second argument: ```{r, eval=FALSE} multi_rast3 = subset(multi_rast, 3) multi_rast4 = subset(multi_rast, "landsat_4") ``` The opposite operation, combining several `SpatRaster` objects into one, can be done using the `c` function: ```{r, eval=FALSE} multi_rast34 = c(multi_rast3, multi_rast4) ``` ```{block2 02-spatial-data-2a, type='rmdnote'} Most `SpatRaster` objects do not store raster values, but rather a pointer to the file itself. This has a significant side-effect -- they cannot be directly saved to `".rds"` or `".rda"` files or used in cluster computing. In these cases, there are two main possible solutions: (1) use of the `wrap()` function that creates a special kind of temporary object that can be saved as an R object or used in cluster computing, or (2) save the object as a regular raster with `writeRaster()`. ``` ## Coordinate Reference Systems {#crs-intro} \index{CRS!introduction} Vector and raster spatial data types share concepts intrinsic to spatial data. Perhaps the most fundamental of these is the coordinate reference systems (CRSs), which defines how the spatial elements of the data relate to the surface of the Earth (or other bodies). CRSs are either geographic or projected, as introduced at the beginning of this chapter (see Figure \@ref(fig:vectorplots)). This section explains each type, laying the foundations for Chapter \@ref(reproj-geo-data), which provides a deep dive into setting, transforming and querying CRSs. ### Geographic coordinate reference systems \index{CRS!geographic} Geographic CRSs identify any location on the Earth's surface using two values --- longitude and latitude (Figure \@ref(fig:vector-crs), left panel). *Longitude* is location in the East-West direction in angular distance from the Prime Meridian plane. *Latitude* is angular distance North or South of the equatorial plane. Distances in geographic CRSs are therefore not measured in meters. This has important consequences, as demonstrated in Section \@ref(reproj-geo-data). The surface of the Earth in geographic CRSs is represented by a spherical or ellipsoidal surface. Spherical models assume that the Earth is a perfect sphere of a given radius -- they have the advantage of simplicity but, at the same time, they are inaccurate as the Earth is not exactly a sphere. Ellipsoidal models are slightly more accurate, and are defined by two parameters: the equatorial radius and the polar radius. These are suitable because the Earth is compressed: the equatorial radius is around 11.5 km longer than the polar radius [@maling_coordinate_1992].^[ The degree of compression is often referred to as *flattening*, defined in terms of the equatorial radius ($a$) and polar radius ($b$) as follows: $f = (a - b) / a$. The terms *ellipticity* and *compression* can also be used. Because $f$ is a rather small value, digital ellipsoid models use the 'inverse flattening' ($rf = 1/f$) to define the Earth's compression. Values of $a$ and $rf$ in various ellipsoidal models can be seen by executing `sf_proj_info(type = "ellps")`. ] Ellipsoids are part of a wider component of CRSs: the *datum*. This contains information on what ellipsoid to use and the precise relationship between the coordinates and location on the Earth's surface. There are two types of datum --- geocentric (such as `WGS84`) and local (such as `NAD83`). You can see examples of these two types of datums in Figure \@ref(fig:datum-fig). Black lines represent a *geocentric datum*, whose center is located in the Earth's center of gravity and is not optimized for a specific location. In a *local datum*, shown as a purple dashed line, the ellipsoidal surface is shifted to align with the surface at a particular location. These allow local variations in Earth's surface, for example due to large mountain ranges, to be accounted for in a local CRS. This can be seen in Figure \@ref(fig:datum-fig), where the local datum is fitted to the area of Philippines, but is misaligned with most of the rest of the planet's surface. Both datums in Figure \@ref(fig:datum-fig) are put on top of a geoid --- a model of global mean sea level.^[Note that the geoid in Figure \@ref(fig:datum-fig) is exaggerated by a factor of 10,000 to highlight the irregular shape of the planet.] (ref:datum-fig) Geocentric and local geodetic datums shown on top of a geoid (in false color and the vertical exaggeration by 10,000 scale factor). Image of the geoid is adapted from the work of @essd-11-647-2019. ```{r datum-fig, echo=FALSE, message=FALSE, fig.cap="(ref:datum-fig)", fig.scap="Geocentric and local geodetic datums on a geoid."} knitr::include_graphics("images/02_datum_fig.png") ``` ### Projected coordinate reference systems \index{CRS!projected} All projected CRSs are based on a geographic CRS, described in the previous section, and rely on map projections to convert the three-dimensional surface of the Earth into Easting and Northing (x and y) values in a projected CRS. Projected CRSs are based on Cartesian coordinates on an implicitly flat surface (Figure \@ref(fig:vector-crs), right panel). They have an origin, x and y axes, and a linear unit of measurement such as meters. This transition cannot be done without adding some deformations. Therefore, some properties of the Earth's surface are distorted in this process, such as area, direction, distance, and shape. A projected coordinate reference system can preserve only one or two of those properties. Projections are often named based on a property they preserve: equal-area preserves area, azimuthal preserve direction, equidistant preserve distance, and conformal preserve local shape. There are three main groups of projection types: conic, cylindrical, and planar (azimuthal). In a conic projection, the Earth's surface is projected onto a cone along a single line of tangency or two lines of tangency. Distortions are minimized along the tangency lines and rise with the distance from those lines in this projection. Therefore, it is the best suited for maps of mid-latitude areas. A cylindrical projection maps the surface onto a cylinder. This projection could also be created by touching the Earth's surface along a single line of tangency or two lines of tangency. Cylindrical projections are used most often when mapping the entire world. A planar projection projects data onto a flat surface touching the globe at a point or along a line of tangency. It is typically used in mapping polar regions. `sf_proj_info(type = "proj")` gives a list of the available projections supported by the PROJ library. A quick summary of different projections, their types, properties, and suitability can be found at [www.geo-projections.com](https://www.geo-projections.com/). We will expand on CRSs and explain how to project from one CRS to another in Chapter \@ref(reproj-geo-data). For now, it is sufficient to know: - That coordinate systems are a key component of geographic objects - Which CRS your data is in, and whether it is in geographic (lon/lat) or projected (typically meters), is important and has consequences for how R handles spatial and geometry operations - That CRSs of `sf` objects can be queried with the function `st_crs()` and CRSs of `terra` objects can be queried with the function `crs()` ```{r vector-crs, echo=FALSE, fig.cap="Examples of geographic (WGS 84; left) and projected (NAD83 / UTM zone 12N; right) coordinate systems for a vector data type.", message=FALSE, fig.asp=0.56, fig.scap="Examples of geographic and projected CRSs (vector data)."} # source("https://github.com/geocompx/geocompr/raw/main/code/02-vector-crs.R") knitr::include_graphics("images/02_vector_crs.png") ``` ## Units An important feature of CRSs is that they contain information about spatial units. Clearly, it is vital to know whether a house's measurements are in feet or meters, and the same applies to maps. It is good cartographic practice to add a *scale bar* or some other distance indicator onto maps to demonstrate the relationship between distances on the page or screen and distances on the ground. Likewise, it is important to formally specify the units in which the geometry data or cells are measured to provide context, and to ensure that subsequent calculations are done in context. A novel feature of geometry data in `sf` objects is that they have *native support* for units. This means that distance, area and other geometric calculations in **sf** return values that come with a `units` attribute, defined by the **units** package [@pebesma_measurement_2016]. This is advantageous, preventing confusion caused by different units (most CRSs use meters, some use feet) and providing information on dimensionality. This is demonstrated in the code chunk below, which calculates the area of Luxembourg: \index{units} \index{sf!units} ```{r 02-spatial-data-57} luxembourg = world[world$name_long == "Luxembourg", ] ``` ```{r 02-spatial-data-58} st_area(luxembourg) # requires the s2 package in recent versions of sf ``` The output is in units of square meters (m^2^), showing that the result represents two-dimensional space. This information, stored as an attribute (which interested readers can discover with `attributes(st_area(luxembourg))`), can feed into subsequent calculations that use units, such as population density (which is measured in people per unit area, typically per km^2^). Reporting units prevents confusion. To take the Luxembourg example, if the units remained unspecified, one could incorrectly assume that the units were in hectares. To translate the huge number into a more digestible size, it is tempting to divide the results by a million (the number of square meters in a square kilometer): ```{r 02-spatial-data-59} st_area(luxembourg) / 1000000 ``` However, the result is incorrectly given again as square meters. The solution is to set the correct units with the **units** package: ```{r 02-spatial-data-60} units::set_units(st_area(luxembourg), km^2) ``` Units are of equal importance in the case of raster data. However, so far **sf** is the only spatial package that supports units, meaning that people working on raster data should approach changes in the units of analysis (for example, converting pixel widths from imperial to decimal units) with care. The `my_rast` object (see above) uses a WGS84 projection with decimal degrees as units. Consequently, its resolution is also given in decimal degrees, but you have to know it, since the `res()` function simply returns a numeric vector. ```{r 02-spatial-data-61} res(my_rast) ``` If we used the Universal Transverse Mercator (UTM) projection, the units would change. ```{r 02-spatial-data-62, warning=FALSE, message=FALSE} repr = project(my_rast, "EPSG:26912") res(repr) ``` Again, the `res()` command gives back a numeric vector without any unit, forcing us to know that the unit of the UTM projection is meters. ## Exercises {#ex2} ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_02-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 03-attribute-operations.Rmd ================================================ # Attribute data operations {#attr} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires the following packages to be installed and attached: ```{r 03-attribute-operations-1, message=FALSE} library(sf) # vector data package introduced in Chapter 2 library(terra) # raster data package introduced in Chapter 2 library(dplyr) # tidyverse package for data frame manipulation ``` - It relies on **spData**, which loads datasets used in the code examples of this chapter: ```{r 03-attribute-operations-2, results='hide'} #| message: FALSE #| results: hide library(spData) # spatial data package introduced in Chapter 2 ``` - Also ensure you have installed the **tidyr** package, or the **tidyverse** of which it is a part, if you want to run data 'tidying' operations in Section \@ref(vec-attr-creation). ## Introduction \index{attribute} Attribute data is non-spatial information associated with geographic (geometry) data. A bus stop provides a simple example: its position would typically be represented by latitude and longitude coordinates (geometry data), in addition to its name. The [Elephant & Castle / New Kent Road](https://www.openstreetmap.org/relation/6610626) stop in London, for example has coordinates of $-0.098$ degrees longitude and 51.495 degrees latitude which can be represented as `POINT (-0.098 51.495)` in the `sfc` representation described in Chapter \@ref(spatial-class). Attributes, such as *name*\index{attribute}, of the POINT feature (to use simple features terminology) are the topic of this chapter. ```{r, eval=FALSE, echo=FALSE} # Aim: find a bus stop in central London library(osmdata) london_coords = c(-0.1, 51.5) london_bb = c(-0.11, 51.49, -0.09, 51.51) bb = tmaptools::bb(london_bb) osm_data = opq(bbox = london_bb) |> add_osm_feature(key = "highway", value = "bus_stop") |> osmdata_sf() osm_data_points = osm_data$osm_points osm_data_points[4, ] point_vector = round(sf::st_coordinates(osm_data_points[4, ]), 3) point_df = data.frame(name = "London bus stop", point_vector) point_sf = sf::st_as_sf(point_df, coords = c("X", "Y")) ``` \index{attribute} Another example is the elevation value (attribute) for a specific grid cell in raster data. Unlike the vector data model, the raster data model stores the coordinate of the grid cell indirectly, meaning the distinction between attribute and spatial information is less clear. To illustrate the point, think of a pixel in row 3 and column 4 of a raster matrix. Its spatial location is defined by its index in the matrix: move from the origin four cells in the x direction (typically east and right on maps) and three cells in the y direction (typically south and down). The raster's *resolution* defines the distance for each x- and y-step which is specified in a *header*. The header is a vital component of raster datasets which specifies how pixels relate to spatial coordinates (see also Chapter \@ref(spatial-operations)). This chapter teaches how to manipulate geographic objects based on attributes such as the names of bus stops in a vector dataset and elevations of pixels in a raster dataset. For vector data, this means techniques such as subsetting and aggregation (see Sections \@ref(vector-attribute-subsetting) to \@ref(vector-attribute-aggregation)). Sections \@ref(vector-attribute-joining) and \@ref(vec-attr-creation) demonstrate how to join data onto simple feature objects using a shared ID and how to create new variables, respectively. Each of these operations has a spatial equivalent: the `[` operator in base R, for example, works equally for subsetting objects based on their attribute and spatial objects; you can also join attributes in two geographic datasets using spatial joins. This is good news: skills developed in this chapter are cross-transferable. After a deep dive into various types of *vector* attribute operations in the next section, *raster* attribute data operations are covered. Creation of raster layers containing continuous and categorical attributes and extraction of cell values from one or more layer (raster subsetting) (Section \@ref(raster-subsetting)) are demonstrated. Section \@ref(summarizing-raster-objects) provides an overview of 'global' raster operations which can be used to summarize entire raster datasets. Chapter \@ref(spatial-operations) extends the methods presented here to the spatial world. ## Vector attribute manipulation \index{attribute} Geographic vector datasets are well supported in R thanks to the `sf` class, which extends base R's `data.frame`. Like data frames, `sf` objects have one column per attribute variable (such as 'name') and one row per observation or *feature* (e.g., per bus station). `sf` objects differ from basic data frames because they have a `geometry` column of class `sfc` which can contain a range of geographic entities (single and 'multi' point, line, and polygon features) per row. This was described in Chapter \@ref(spatial-class), which demonstrated how *generic methods* such as `plot()` and `summary()` work with `sf` objects. **sf** also provides generics that allow `sf` objects to behave like regular data frames, as shown by printing the class's methods: ```{r 03-attribute-operations-3, eval=FALSE} methods(class = "sf") # methods for sf objects, first 12 shown ``` ```{r 03-attribute-operations-4} #> [1] [ [[<- $<- aggregate #> [5] as.data.frame cbind coerce filter #> [9] identify initialize merge plot ``` ```{r 03-attribute-operations-5, eval=FALSE, echo=FALSE} # Another way to show sf methods: attributes(methods(class = "sf"))$info |> dplyr::filter(!visible) ``` Many of these (`aggregate()`, `cbind()`, `merge()`, `rbind()` and `[`) are for manipulating data frames. `rbind()`, for example, binds rows of data frames together, one 'on top' of the other. `$<-` creates new columns. A key feature of `sf` objects is that they store spatial and non-spatial data in the same way, as columns in a `data.frame`. ```{block2 03-attribute-operations-6, type = 'rmdnote'} The geometry column of `sf` objects is typically called `geometry` or `geom`, but any name can be used. The following command, for example, creates a geometry column named g: `st_sf(data.frame(n = world$name_long), g = world$geom)` This enables geometries imported from spatial databases to have a variety of names such as `wkb_geometry` and `the_geom`. ``` `sf` objects can also extend the tidyverse classes for data frames, `tbl_df` and `tbl`.\index{tidyverse (package)} Thus **sf** enables the full power of R's data analysis capabilities to be unleashed on geographic data, whether you use base R or tidyverse functions for data analysis. \index{tibble} **sf** objects can also be used with the high-performance data processing package **data.table** although, as documented in the issue [`Rdatatable/data.table#2273`](https://github.com/Rdatatable/data.table/issues/2273), is not fully [compatible](https://github.com/Rdatatable/data.table/issues/5352) with `sf` objects. Before using these capabilities, it is worth recapping how to discover the basic properties of vector data objects. Let's start by using base R functions to learn about the `world` dataset from the **spData** package: ```{r 03-attribute-operations-7} class(world) # it's an sf object and a (tidy) data frame dim(world) # it is a two-dimensional object, with 177 rows and 11 columns ``` \index{attribute!dropping geometries} `world` contains ten non-geographic columns (and one geometry list column) with almost 200 rows representing the world's countries. The function `st_drop_geometry()` keeps only the attributes data of an `sf` object, in other words removing its geometry: ```{r 03-attribute-operations-8} world_df = st_drop_geometry(world) class(world_df) ncol(world_df) ``` Dropping the geometry column before working with attribute data can be useful; data manipulation processes can run faster when they work only on the attribute data and geometry columns are not always needed. For most cases, however, it makes sense to keep the geometry column, explaining why the column is 'sticky' (it remains after most attribute operations unless specifically dropped). Non-spatial data operations on `sf` objects only change an object's geometry when appropriate (e.g., by dissolving borders between adjacent polygons following aggregation). Becoming skilled at geographic attribute data manipulation means becoming skilled at manipulating data frames. For many applications, the tidyverse\index{tidyverse (package)} package **dplyr** [@R-dplyr] offers an effective approach for working with data frames. Tidyverse compatibility is an advantage of **sf** over its predecessor **sp**, but there are some pitfalls to avoid (see the supplementary `tidyverse-pitfalls` vignette at [geocompx.org](https://geocompx.github.io/geocompkg/articles/tidyverse-pitfalls.html) for details). ### Vector attribute subsetting Base R subsetting methods include the operator `[` and the function `subset()`. The key **dplyr** subsetting functions are `filter()` and `slice()` for subsetting rows, and `select()` for subsetting columns. Both approaches preserve the spatial components of attribute data in `sf` objects, while using the operator `$` or the **dplyr** function `pull()` to return a single attribute column as a vector will lose the geometry data, as we will see. \index{attribute!subsetting} This section focuses on subsetting `sf` data frames; for further details on subsetting vectors and non-geographic data frames we recommend reading Section [2.7](https://cran.r-project.org/doc/manuals/r-release/R-intro.html#Index-vectors) of *An Introduction to R* [@rcoreteam_introduction_2021] and chapter [4](https://adv-r.hadley.nz/subsetting.html) of *Advanced R Programming* [@wickham_advanced_2019], respectively. \index{attribute!subsetting} The `[` operator can subset both rows and columns. Indices placed inside square brackets placed directly after a data frame object name specify the elements to keep. The command `object[i, j]` means 'return the rows represented by `i` and the columns represented by `j`', where `i` and `j` typically contain integers or `TRUE`s and `FALSE`s (indices can also be character strings, indicating row or column names). `object[5, 1:3]`, for example, means 'return data containing the 5th row and columns 1 to 3: the result should be a data frame with only 1 row and 3 columns, and a fourth geometry column if it's an `sf` object. Leaving `i` or `j` empty returns all rows or columns, so `world[1:5, ]` returns the first five rows and all 11 columns. The examples below demonstrate subsetting with base R. Guess the number of rows and columns in the `sf` data frames returned by each command and check the results on your own computer (see the end of the chapter for more exercises): ```{r 03-attribute-operations-9, eval=FALSE} world[1:6, ] # subset rows by position world[, 1:3] # subset columns by position world[1:6, 1:3] # subset rows and columns by position world[, c("name_long", "pop")] # columns by name world[, c(T, T, F, F, F, F, F, T, T, F, F)] # by logical indices world[, 888] # an index representing a non-existent column ``` ```{r, eval=FALSE, echo=FALSE} # these fail world[c(1, 5), c(T, T)] world[c(1, 5), c(T, T, F, F, F, F, F, T, T, F, F, F)] ``` A demonstration of the utility of using `logical` vectors for subsetting is shown in the code chunk below. This creates a new object, `small_countries`, containing nations whose surface area is smaller than 10,000 km^2^. ```{r 03-attribute-operations-10} i_small = world$area_km2 < 10000 summary(i_small) # a logical vector small_countries = world[i_small, ] ``` The intermediary `i_small` (short for index representing small countries) is a logical vector that can be used to subset the seven smallest countries in the `world` by surface area. A more concise command, which omits the intermediary object, generates the same result: ```{r 03-attribute-operations-11} small_countries = world[world$area_km2 < 10000, ] ``` The base R function `subset()` provides another way to achieve the same result: ```{r 03-attribute-operations-12, eval=FALSE} small_countries = subset(world, area_km2 < 10000) ``` \index{attribute!subsetting} Base R functions are mature, stable and widely used, making them a rock solid choice, especially in contexts where reproducibility and reliability are key. **dplyr** functions enable 'tidy' workflows which some people (the authors of this book included) find intuitive and productive for interactive data analysis, especially when combined with code editors such as RStudio that enable [auto-completion](https://support.posit.co/hc/en-us/articles/205273297-Code-Completion-in-the-RStudio-IDE) of column names. Key functions for subsetting data frames (including `sf` data frames) with **dplyr** functions are demonstrated below. ```{r, echo=FALSE, eval=FALSE} # Aim: benchmark base vs. dplyr subsetting # Could move elsewhere? i = sample(nrow(world), size = 10) benchmark_subset = bench::mark( world[i, ], world |> slice(i) ) benchmark_subset[c("expression", "itr/sec", "mem_alloc")] # # October 2021 on laptop with CRAN version of dplyr: # # A tibble: 2 × 3 # expression `itr/sec` mem_alloc # # 1 world[i, ] 1744. 5.55KB # 2 world |> slice(i) 671. 4.45KB ``` `select()` selects columns by name or position. For example, you could select only two columns, `name_long` and `pop`, with the following command: ```{r 03-attribute-operations-14} world1 = select(world, name_long, pop) names(world1) ``` Note: as with the equivalent command in base R (`world[, c("name_long", "pop")]`), the sticky `geom` column remains. `select()` also allows selecting a range of columns with the help of the `:` operator: ```{r 03-attribute-operations-15} # all columns between name_long and pop (inclusive) world2 = select(world, name_long:pop) ``` You can remove specific columns with the `-` operator: ```{r 03-attribute-operations-16} # all columns except subregion and area_km2 (inclusive) world3 = select(world, -subregion, -area_km2) ``` Subset and rename columns at the same time with the `new_name = old_name` syntax: ```{r 03-attribute-operations-17} world4 = select(world, name_long, population = pop) ``` It is worth noting that the command above is more concise than base R equivalent, which requires two lines of code: ```{r 03-attribute-operations-18, eval=FALSE} world5 = world[, c("name_long", "pop")] # subset columns by name names(world5)[names(world5) == "pop"] = "population" # rename column manually ``` `select()` also works with 'helper functions' for more advanced subsetting operations, including `contains()`, `starts_with()` and `num_range()` (see the help page with `?select` for details). Most **dplyr** verbs return a data frame, but you can extract a single column as a vector with `pull()`. You can get the same result in base R with the list subsetting operators `$` and `[[`, the three following commands return the same numeric vector: ```{r 03-attribute-operations-21, eval = FALSE} pull(world, pop) world$pop world[["pop"]] ``` ```{r 03-attribute-operations-19, eval=FALSE, echo=FALSE} # create throw-away data frame d = data.frame(pop = 1:10, area = 1:10) # return data frame object when selecting a single column d[, "pop", drop = FALSE] # equivalent to d["pop"] select(d, pop) # return a vector when selecting a single column d[, "pop"] pull(d, pop) ``` ```{r 03-attribute-operations-20, echo=FALSE, eval=FALSE} x1 = d[, "pop", drop = FALSE] # equivalent to d["pop"] x2 = d["pop"] identical(x1, x2) ``` `slice()` is the row-equivalent of `select()`. The following code chunk, for example, selects rows 1 to 6: ```{r 03-attribute-operations-22, eval=FALSE} slice(world, 1:6) ``` `filter()` is **dplyr**'s equivalent of base R's `subset()` function. It keeps only rows matching given criteria, e.g., only countries with an area below a certain threshold, or with a high average of life expectancy, as shown in the following examples: ```{r 03-attribute-operations-23, eval=FALSE} world7 = filter(world, area_km2 < 10000) # countries with a small area world7 = filter(world, lifeExp > 82) # with high life expectancy ``` The standard set of comparison operators can be used in the `filter()` function, as illustrated in Table \@ref(tab:operators). ```{r operators0, echo=FALSE} if (knitr::is_html_output()){ operators = c("`==`", "`!=`", "`>`, `<`", "`>=`, `<=`", "`&`, |, `!`") } else { operators = c("==", "!=", ">, <", ">=, <=", "&, |, !") } ``` ```{r operators, echo=FALSE} operators_exp = c("Equal to", "Not equal to", "Greater/Less than", "Greater/Less than or equal", "Logical operators: And, Or, Not") knitr::kable(tibble(Symbol = operators, Name = operators_exp), caption = "Comparison operators that return Boolean (true/false) values.", caption.short = "Comparison operators.", booktabs = TRUE) ``` ### Chaining commands with pipes \index{pipe operator} Key to workflows using **dplyr** functions is the ['pipe'](https://r4ds.had.co.nz/pipes.html) operator `%>%` (or since R `4.1.0` the native pipe `|>`), which takes its name from the Unix pipe `|` [@grolemund_r_2016]. Pipes enable expressive code: the output of a previous function becomes the first argument of the next function, enabling *chaining*. This is illustrated below, in which only countries from Asia are filtered from the `world` dataset, next the object is subset by columns (`name_long` and `continent`) and the first five rows (result not shown). ```{r 03-attribute-operations-24} world7 = world |> filter(continent == "Asia") |> select(name_long, continent) |> slice(1:5) ``` The above chunk shows how the pipe operator allows commands to be written in a clear order: the above run from top to bottom (line-by-line) and left to right. An alternative to piped operations is nested function calls, which are harder to read: ```{r 03-attribute-operations-25} world8 = slice( select( filter(world, continent == "Asia"), name_long, continent), 1:5) ``` Another alternative is to split the operations into multiple self-contained lines, which is recommended when developing new R packages, an approach which has the advantage of saving intermediate results with distinct names which can be later inspected for debugging purposes (an approach which has disadvantages of being verbose and cluttering the global environment when undertaking interactive analysis): ```{r 03-attribute-operations-25-2} world9_filtered = filter(world, continent == "Asia") world9_selected = select(world9_filtered, continent) world9 = slice(world9_selected, 1:5) ``` Each approach has advantages and disadvantages, the importance of which depend on your programming style and applications. For interactive data analysis, the focus of this chapter, we find piped operations fast and intuitive, especially when combined with [RStudio](https://support.posit.co/hc/en-us/articles/200711853-Keyboard-Shortcuts-in-the-RStudio-IDE)/[VSCode](https://github.com/REditorSupport/vscode-R/wiki/Keyboard-shortcuts) shortcuts for creating pipes and [auto-completing](https://support.posit.co/hc/en-us/articles/205273297-Code-Completion-in-the-RStudio-IDE) variable names. ### Vector attribute aggregation \index{attribute!aggregation} \index{aggregation} Aggregation involves summarizing data with one or more 'grouping variables', typically from columns in the data frame to be aggregated (geographic aggregation is covered in the next chapter). An example of attribute aggregation is calculating the number of people per continent based on country-level data (one row per country). The `world` dataset contains the necessary ingredients: the columns `pop` and `continent`, the population and the grouping variable, respectively. The aim is to find the `sum()` of country populations for each continent, resulting in a smaller data frame (aggregation is a form of data reduction and can be a useful early step when working with large datasets). This can be done with the base R function `aggregate()` as follows: ```{r 03-attribute-operations-26} world_agg1 = aggregate(pop ~ continent, FUN = sum, data = world, na.rm = TRUE) class(world_agg1) ``` The result is a non-spatial data frame with six rows, one per continent, and two columns reporting the name and population of each continent (see Table \@ref(tab:continents) with results for the top three most populous continents). `aggregate()` is a [generic function](https://adv-r.hadley.nz/s3.html#s3-methods) which means that it behaves differently depending on its inputs. **sf** provides the method `aggregate.sf()` which is activated automatically when `x` is an `sf` object and a `by` argument is provided: ```{r 03-attribute-operations-27} world_agg2 = aggregate(world["pop"], by = list(world$continent), FUN = sum, na.rm = TRUE) class(world_agg2) nrow(world_agg2) ``` The resulting `world_agg2` object is a spatial object containing eight features representing the continents of the world (and the open ocean). \index{attribute!aggregation} `group_by() |> summarize()` is the **dplyr** equivalent of `aggregate()`. Grouping variables are defined in the `group_by()` function and to aggregation formula is defined in the `summarize()` function, as shown below: ```{r 03-attribute-operations-28} world_agg3 = world |> group_by(continent) |> summarize(pop = sum(pop, na.rm = TRUE)) ``` The approach may seem more complex, but it has benefits: flexibility, readability, and control over the new column names. This flexibility is illustrated in the command below, which calculates not only the population but also the area and number of countries in each continent: ```{r 03-attribute-operations-29} world_agg4 = world |> group_by(continent) |> summarize(Pop = sum(pop, na.rm = TRUE), Area = sum(area_km2), N = n()) ``` In the previous code chunk `Pop`, `Area` and `N` are column names in the result, and `sum()` and `n()` were the aggregating functions. These aggregating functions return `sf` objects with rows representing continents and geometries containing the multiple polygons representing each land mass and associated islands (this works thanks to the geometric operation 'union', as explained in Section \@ref(geometry-unions)). \index{pipe operator} \index{attribute!subsetting} \index{attribute!aggregation} Let's combine what we have learned so far about **dplyr** functions, by chaining multiple commands to summarize attribute data about countries worldwide by continent. The following command calculates population density (with `mutate()`), arranges continents by the number of countries they contain (with `arrange()`), and keeps only the three most populous continents (with `slice_max()`), the result of which is presented in Table \@ref(tab:continents)): ```{r 03-attribute-operations-30} world_agg5 = world |> st_drop_geometry() |> # drop the geometry for speed select(pop, continent, area_km2) |> # subset the columns of interest group_by(Continent = continent) |> # group by continent and summarize: summarize(Pop = sum(pop, na.rm = TRUE), Area = sum(area_km2), N = n()) |> mutate(Density = round(Pop / Area)) |> # calculate population density slice_max(Pop, n = 3) |> # keep only the top 3 arrange(desc(N)) # arrange in order of n. countries ``` ```{r continents, echo=FALSE} options(scipen = 999) knitr::kable( world_agg5, caption = "The top three most populous continents ordered by number of countries.", caption.short = "Top three most populous continents.", booktabs = TRUE ) ``` ```{block2 03-attribute-operations-31, type='rmdnote'} More details are provided in the help pages (which can be accessed via `?summarize` and `vignette(package = "dplyr")` and Chapter 5 of [R for Data Science](https://r4ds.had.co.nz/transform.html#grouped-summaries-with-summarize). ``` ### Vector attribute joining Combining data from different sources is a common task in data preparation. Joins do this by combining tables based on a shared 'key' variable. **dplyr** has multiple join functions including `left_join()` and `inner_join()` --- see `vignette("two-table")` for a full list. These function names follow conventions used in the database language [SQL](https://r4ds.had.co.nz/relational-data.html) [@grolemund_r_2016, Chapter 13]; using them to join non-spatial datasets to `sf` objects is the focus of this section. **dplyr** join functions work the same on data frames and `sf` objects, the only important difference being the `geometry` list column. The result of data joins can be either an `sf` or `data.frame` object. The most common type of attribute join on spatial data takes an `sf` object as the first argument and adds columns to it from a `data.frame` specified as the second argument. \index{join} \index{attribute!join} To demonstrate joins, we will combine data on coffee production with the `world` dataset. The coffee data is in a data frame called `coffee_data` from the **spData** package (see `?coffee_data` for details). It has three columns: `name_long` names major coffee-producing nations and `coffee_production_2016` and `coffee_production_2017` contain estimated values for coffee production in units of 60-kg bags in each year. A 'left join', which preserves the first dataset, merges `world` with `coffee_data`. ```{r 03-attribute-operations-32, warning=FALSE} world_coffee = left_join(world, coffee_data) class(world_coffee) ``` Because the input datasets share a 'key variable' (`name_long`) the join worked without using the `by` argument (see `?left_join` for details). The result is an `sf` object identical to the original `world` object but with two new variables (with column indices 11 and 12) on coffee production. This can be plotted as a map, as illustrated in Figure \@ref(fig:coffeemap), generated with the `plot()` function below. ```{r coffeemap, fig.cap="World coffee production (thousand 60-kg bags) by country, 2017. Source: International Coffee Organization.", fig.scap="World coffee production by country."} names(world_coffee) plot(world_coffee["coffee_production_2017"]) ``` For joining to work, a 'key variable' must be supplied in both datasets. By default, **dplyr** uses all variables with matching names. In this case, both `coffee_data` and `world` objects contained a variable called `name_long`, explaining the message `Joining with 'by = join_by(name_long)'`. In the majority of cases where variable names are not the same, you have two options: 1. Rename the key variable in one of the objects so they match. 2. Use the `by` argument to specify the joining variables. The latter approach is demonstrated below on a renamed version of `coffee_data`. ```{r 03-attribute-operations-33, warning=FALSE} coffee_renamed = rename(coffee_data, nm = name_long) world_coffee2 = left_join(world, coffee_renamed, by = join_by(name_long == nm)) ``` ```{r 03-attribute-operations-34, eval=FALSE, echo=FALSE} identical(world_coffee, world_coffee2) nrow(world) nrow(world_coffee) ``` Note that the name in the original object is kept, meaning that `world_coffee` and the new object `world_coffee2` are identical. Another feature of the result is that it has the same number of rows as the original dataset. Although there are only 47 rows of data in `coffee_data`, all 177 country records are kept intact in `world_coffee` and `world_coffee2`: rows in the original dataset with no match are assigned `NA` values for the new coffee production variables. What if we only want to keep countries that have a match in the key variable? \index{attribute!join} In that case, an inner join can be used. ```{r 03-attribute-operations-35, warning=FALSE} world_coffee_inner = inner_join(world, coffee_data) nrow(world_coffee_inner) ``` Note that the result of `inner_join()` has only 45 rows compared with 47 in `coffee_data`. What happened to the remaining rows? We can identify the rows that did not match using the `setdiff()` function as follows: ```{r 03-attribute-operations-36} setdiff(coffee_data$name_long, world$name_long) ``` The result shows that `Others` accounts for one row not present in the `world` dataset and that the name of the `Democratic Republic of the Congo` accounts for the other: it has been abbreviated, causing the join to miss it. The following command uses a string matching (*regex*) function from the **stringr** package to confirm what `Congo, Dem. Rep. of` should be. ```{r 03-attribute-operations-37} drc = stringr::str_subset(world$name_long, "Dem*.+Congo") drc ``` ```{r, echo=FALSE, eval=FALSE} world$name_long[grepl(pattern = "Dem*.+Congo", world$name_long)] # base R ``` ```{r 03-attribute-operations-38, eval=FALSE, echo=FALSE} # aim: test names in coffee_data and world objects str_subset(coffee_data$name_long, "Ivo|Congo,") .Last.value %in% str_subset(world$name_long, "Ivo|Dem*.+Congo") ``` To fix this issue, we will create a new version of `coffee_data` and update the name. `inner_join()`ing the updated data frame returns a result with all 46 coffee-producing nations. ```{r 03-attribute-operations-39, warning=FALSE} coffee_data$name_long[grepl("Congo,", coffee_data$name_long)] = drc world_coffee_match = inner_join(world, coffee_data) nrow(world_coffee_match) ``` It is also possible to join in the other direction: starting with a non-spatial dataset and adding variables from a simple features object. This is demonstrated below, which starts with the `coffee_data` object and adds variables from the original `world` dataset. In contrast with the previous joins, the result is *not* another simple feature object, but a data frame in the form of a **tidyverse** tibble: the output of a join tends to match its first argument. ```{r 03-attribute-operations-40, warning=FALSE} coffee_world = left_join(coffee_data, world) class(coffee_world) ``` ```{block2 03-attribute-operations-41, type='rmdnote'} In most cases, the geometry column is only useful in an `sf` object. The geometry column can only be used for creating maps and spatial operations if R 'knows' it is a spatial object, defined by a spatial package such as **sf**. Fortunately, non-spatial data frames with a geometry list column (like `coffee_world`) can be coerced into an `sf` object as follows: `st_as_sf(coffee_world)`. ``` This section covers the majority of joining use cases. For more information, we recommend reading the chapter [Relational data](https://r4ds.had.co.nz/relational-data.html?q=join#relational-data) in @grolemund_r_2016, the [join vignette](https://geocompx.github.io/geocompkg/articles/join.html) in the **geocompkg** package that accompanies this book, and [documentation](https://asardaes.github.io/table.express/articles/joins.html) describing joins with **data.table** and other packages. Additionally, spatial joins are covered in the next chapter (Section \@ref(spatial-joining)). ### Creating attributes and removing spatial information {#vec-attr-creation} \index{attribute!create} Often, we would like to create a new column based on already existing columns. For example, we want to calculate population density for each country. For this we need to divide a population column, here `pop`, by an area column, here `area_km2` with unit area in square kilometers. Using base R, we can type: ```{r 03-attribute-operations-42} world_new = world # do not overwrite our original data world_new$pop_dens = world_new$pop / world_new$area_km2 ``` \index{attribute!create} Alternatively, we can use one of **dplyr** functions: `mutate()` or `transmute()`. `mutate()` adds new columns at the penultimate position in the `sf` object (the last one is reserved for the geometry): ```{r 03-attribute-operations-43, eval=FALSE} world_new2 = world |> mutate(pop_dens = pop / area_km2) ``` The difference between `mutate()` and `transmute()` is that the latter drops all other existing columns (except for the sticky geometry column). \index{attribute!create} `unite()` from the **tidyr** package (which provides many useful functions for reshaping datasets, including `pivot_longer()`) pastes together existing columns. For example, we want to combine the `continent` and `region_un` columns into a new column named `con_reg`. Additionally, we can define a separator (here, a colon `:`) which defines how the values of the input columns should be joined, and if the original columns should be removed (here, `TRUE`). ```{r 03-attribute-operations-45, eval=FALSE} world_unite = world |> tidyr::unite("con_reg", continent:region_un, sep = ":", remove = TRUE) ``` The resulting `sf` object has a new column called `con_reg` representing the continent and region of each country, e.g., `South America:Americas` for Argentina and other South America countries. \index{attribute!create} **tidyr**'s `separate()` function does the opposite of `unite()`: it splits one column into multiple columns using either a regular expression or character positions. ```{r 03-attribute-operations-46, eval=FALSE} world_separate = world_unite |> tidyr::separate(con_reg, c("continent", "region_un"), sep = ":") ``` ```{r 03-attribute-operations-47, echo=FALSE, eval=FALSE} identical(world, world_separate) ``` \index{attribute!create} The **dplyr** function `rename()` and the base R function `setNames()` are useful for renaming columns. The first replaces an old name with a new one. The following command, for example, renames the lengthy `name_long` column to simply `name`: ```{r 03-attribute-operations-48, eval=FALSE} world |> rename(name = name_long) ``` \index{attribute!create} `setNames()` changes all column names at once, and requires a character vector with a name matching each column. This is illustrated below, which outputs the same `world` object, but with very short names: ```{r 03-attribute-operations-49, eval=FALSE, echo=FALSE} abbreviate(names(world), minlength = 1) |> dput() ``` ```{r 03-attribute-operations-50, eval=FALSE} new_names = c("i", "n", "c", "r", "s", "t", "a", "p", "l", "gP", "geom") world_new_names = world |> setNames(new_names) ``` \index{attribute!create} Each of these attribute data operations preserves the geometry of the simple features. Sometimes, it makes sense to remove the geometry, for example to speed up aggregation. Do this with `st_drop_geometry()`, **not** manually with commands such as `select(world, -geom)`, as shown below.^[ `st_geometry(world_st) = NULL` also works to remove the geometry from `world`, but overwrites the original object. ] ```{r 03-attribute-operations-51} world_data = world |> st_drop_geometry() class(world_data) ``` ## Manipulating raster objects In contrast to the vector data model underlying simple features (which represents points, lines and polygons as discrete entities in space), raster data represent continuous surfaces. This section shows how raster objects work by creating them *from scratch*, building on Section \@ref(introduction-to-terra). Because of their unique structure, subsetting and other operations on raster datasets work in a different way, as demonstrated in Section \@ref(raster-subsetting). \index{raster!manipulation} The following code recreates the raster dataset used in Section \@ref(raster-classes), the result of which is illustrated in Figure \@ref(fig:cont-raster). This demonstrates how the `rast()` function works to create an example raster named `elev` (representing elevations). ```{r 03-attribute-operations-52, message=FALSE, eval = FALSE} elev = rast(nrows = 6, ncols = 6, xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, vals = 1:36) ``` The result is a raster object with 6 rows and 6 columns (specified by the `nrow` and `ncol` arguments), and a minimum and maximum spatial extent in x and y direction (`xmin`, `xmax`, `ymin`, `ymax`). The `vals` argument sets the values that each cell contains: numeric data ranging from 1 to 36 in this case. \index{raster!manipulation} \index{raster!categorical} Raster objects can also contain categorical values of class `logical` or `factor` variables in R. The following code creates the raster datasets shown in Figure \@ref(fig:cont-raster): ```{r 03-attribute-operations-53, eval=FALSE} grain_order = c("clay", "silt", "sand") grain_char = sample(grain_order, 36, replace = TRUE) grain_fact = factor(grain_char, levels = grain_order) grain = rast(nrows = 6, ncols = 6, xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, vals = grain_fact) ``` ```{r 03-attribute-operations-54, include=FALSE} elev = rast(system.file("raster/elev.tif", package = "spData")) grain = rast(system.file("raster/grain.tif", package = "spData")) ``` \index{raster!categorical} \index{raster attribute table} The raster object stores the corresponding look-up table or "Raster Attribute Table" (RAT) as a list of data frames, which can be viewed with `cats(grain)` (see `?cats()` for more information). Each element of this list is a layer of the raster. It is also possible to use the function `levels()` for retrieving and adding new or replacing existing factor levels. ```{r 03-attribute-operations-56} grain2 = grain # do not overwrite the original data levels(grain2) = data.frame(value = c(0, 1, 2), wetness = c("wet", "moist", "dry")) levels(grain2) ``` ```{r cont-raster, echo = FALSE, message = FALSE, fig.asp=0.5, fig.cap = "Raster datasets with numeric (left) and categorical values (right).", fig.scap="Raster datasets with numeric and categorical values.", warning=FALSE} # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146617366-7308535b-30f6-4c87-83f7-21702c7d993b.png") source("code/03-cont-raster-plot.R", print.eval = TRUE) ``` ```{block2 coltab, type='rmdnote'} Categorical raster objects can also store information about the colors associated with each value using a color table. The color table is a data frame with three (red, green, blue) or four (alpha) columns, where each row relates to one value. Color tables in **terra** can be viewed or set with the `coltab()` function (see `?coltab`). Importantly, saving a raster object with a color table to a file (e.g., GeoTIFF) will also save the color information. ``` ### Raster subsetting Raster subsetting is done with the base R operator `[`, which accepts a variety of inputs: \index{raster!subsetting} - Row-column indexing - Cell IDs - Coordinates - Another spatial object Here, we only show the first two options since these can be considered non-spatial operations. If we need a spatial object to subset another or the output is a spatial object, we refer to this as spatial subsetting. Therefore, the latter two options will be shown in the next chapter (see Section \@ref(spatial-raster-subsetting)). \index{raster!subsetting} The first two subsetting options are demonstrated in the commands below --- both return the value of the top left pixel in the raster object `elev` (results not shown). ```{r 03-attribute-operations-58, eval = FALSE} # row 1, column 1 elev[1, 1] # cell ID 1 elev[1] ``` Subsetting of multi-layered raster objects will return the cell value(s) for each layer. For example, `two_layers = c(grain, elev); two_layers[1]` returns a data frame with one row and two columns --- one for each layer. To extract all values, you can also use `values()`. Cell values can be modified by overwriting existing values in conjunction with a subsetting operation. The following code chunk, for example, sets the upper left cell of `elev` to 0 (results not shown): ```{r 03-attribute-operations-60, results='hide'} elev[1, 1] = 0 elev[] ``` Leaving the square brackets empty is a shortcut version of `values()` for retrieving all values of a raster. Multiple cells can also be modified in this way: ```{r 03-attribute-operations-61} elev[1, c(1, 2)] = 0 ``` Replacing values of multi-layered rasters can be done with a matrix with as many columns as layers and rows as replaceable cells (results not shown): ```{r 03-attribute-operations-61b, eval=FALSE} two_layers = c(grain, elev) two_layers[1] = cbind(c(1), c(4)) two_layers[] ``` ### Summarizing raster objects **terra** contains functions for extracting descriptive statistics\index{statistics} for entire rasters. Printing a raster object to the console by typing its name returns minimum and maximum values of a raster. \index{raster!summarizing} `summary()` provides common descriptive statistics\index{statistics} -- minimum, maximum, quartiles and number of `NA`s for continuous rasters and the number of cells of each class for categorical rasters. \index{raster!summarizing} Further summary operations such as the standard deviation (see below) or custom summary statistics can be calculated with `global()`. ```{r 03-attribute-operations-62, eval = FALSE} global(elev, sd) ``` ```{block2 03-attribute-operations-63, type='rmdnote'} If you provide the `summary()` and `global()` functions with a multi-layered raster object, they will summarize each layer separately, as can be illustrated by running: `summary(c(elev, grain))`. ``` \index{raster!summarizing} Additionally, the `freq()` function allows to get the frequency table of categorical values. ```{r} freq(grain) ``` Raster value statistics can be visualized in a variety of ways. Specific functions such as `boxplot()`, `density()`, `hist()` and `pairs()` work also with raster objects, as demonstrated in the histogram created with the command below (not shown). ```{r 03-attribute-operations-64, eval=FALSE} hist(elev) ``` \index{raster!values} In case the desired visualization function does not work with raster objects, one can extract the raster data to be plotted with the help of `values()` (Section \@ref(raster-subsetting)). Descriptive raster statistics belong to the so-called global raster operations. These and other typical raster processing operations are part of the map algebra scheme, which are covered in the next chapter (Section \@ref(map-algebra)). ```{block 03-attribute-operations-65, type='rmdnote'} Some function names clash between packages (e.g., functions with the name `extract()` exist in both **terra** and **tidyr** packages). This may lead to unexpected results when loading packages in a different order. In addition to calling functions verbosely with their full namespace (e.g., `tidyr::extract()`) to avoid attaching packages with `library()`, another way to prevent function name clashes is by unloading the offending package with `detach()`. The following command, for example, unloads the **terra** package (this can also be done in the *package* tab which resides by default in the right-bottom pane in RStudio): `detach("package:terra", unload = TRUE, force = TRUE)`. The `force` argument makes sure that the package will be detached even if other packages depend on it. This, however, may lead to a restricted usability of packages depending on the detached package, and it is therefore not recommended. ``` ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_03-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 04-spatial-operations.Rmd ================================================ # Spatial data operations {#spatial-operations} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires the same packages used in Chapter \@ref(attr): ```{r 04-spatial-operations-1, message=FALSE, results='hide'} library(sf) library(terra) library(dplyr) library(spData) ``` ## Introduction Spatial operations, including spatial joins between vector datasets and local and focal operations on raster datasets, are a vital part of geocomputation\index{geocomputation}. This chapter shows how spatial objects can be modified in a multitude of ways based on their location and shape. Many spatial operations have a non-spatial (attribute) equivalent, so concepts such as subsetting and joining datasets demonstrated in the previous chapter are applicable here. This is especially true for *vector* operations: Section \@ref(vector-attribute-manipulation) on vector attribute manipulation provides the basis for understanding its spatial counterpart, namely spatial subsetting (covered in Section \@ref(spatial-subsetting)). Spatial joining (Sections \@ref(spatial-joining), \@ref(non-overlapping-joins) and \@ref(incongruent)) and aggregation (Section \@ref(spatial-aggr)) also have non-spatial counterparts, covered in the previous chapter. Spatial operations differ from non-spatial operations in a number of ways, however: spatial joins, for example, can be done in a number of ways --- including matching entities that intersect with or are within a certain distance of the target dataset --- while the attribution joins discussed in Section \@ref(vector-attribute-joining) in the previous chapter can only be done in one way (except when using fuzzy joins, as described in the documentation of the [**fuzzyjoin**](https://cran.r-project.org/package=fuzzyjoin) package). Different *types* of spatial relationship between objects, including intersects and disjoint, are described in Sections \@ref(topological-relations) and \@ref(DE-9IM-strings). \index{spatial operations} Another unique aspect of spatial objects is distance: all spatial objects are related through space, and distance calculations can be used to explore the strength of this relationship, as described in the context of vector data in Section \@ref(distance-relations). Spatial operations on raster objects include subsetting --- covered in Section \@ref(spatial-raster-subsetting). *Map algebra* covers a range of operations that modify raster cell values, with or without reference to surrounding cell values. The concept of map algebra, vital for many applications, is introduced in Section \@ref(map-algebra); local, focal and zonal map algebra operations are covered in sections \@ref(local-operations), \@ref(focal-operations), and \@ref(zonal-operations), respectively. Global map algebra operations, which generate summary statistics representing an entire raster dataset, and distance calculations on rasters, are discussed in Section \@ref(global-operations-and-distances). Next, the relationships between map algebra and vector operations are discussed in Section \@ref(map-algebra-counterparts-in-vector-processing). In the Section \@ref(merging-rasters),1 the process of merging two raster datasets is discussed and demonstrated with reference to a reproducible example. ```{block2 04-spatial-operations-2, type='rmdnote'} It is important to note that spatial operations that use two spatial objects rely on both objects having the same coordinate reference system, a topic that was introduced in Section \@ref(crs-intro) and which will be covered in more depth in Chapter \@ref(reproj-geo-data). ``` ## Spatial operations on vector data {#spatial-vec} This section provides an overview of spatial operations on vector geographic data represented as simple features in the **sf** package. Section \@ref(spatial-ras) presents spatial operations on raster datasets using classes and functions from the **terra** package. ### Spatial subsetting Spatial subsetting is the process of taking a spatial object and returning a new object containing only features that *relate* in space to another object. Analogous to *attribute subsetting* (covered in Section \@ref(vector-attribute-subsetting)), subsets of `sf` data frames can be created with square bracket (`[`) operator using the syntax `x[y, , op = st_intersects]`, where `x` is an `sf` object from which a subset of rows will be returned, `y` is the 'subsetting object' and `, op = st_intersects` is an optional argument that specifies the topological relation (also known as the binary predicate) used to do the subsetting. The default topological relation used when an `op` argument is not provided is `st_intersects()`: the command `x[y, ]` is identical to `x[y, , op = st_intersects]` shown above but not `x[y, , op = st_disjoint]` (the meaning of these and other topological relations is described in the next section). The `filter()` function from the **tidyverse**\index{tidyverse (package)} can also be used, but this approach is more verbose, as we will see in the examples below. \index{vector!subsetting} \index{spatial!subsetting} To demonstrate spatial subsetting, we will use the `nz` and `nz_height` datasets in the **spData** package, which contain geographic data on the 16 main regions and 101 highest points in New Zealand, respectively (Figure \@ref(fig:nz-subset)), in a projected coordinate reference system. The following code chunk creates an object representing Canterbury, then uses spatial subsetting to return all high points in the region. ```{r 04-spatial-operations-3} canterbury = nz |> filter(Name == "Canterbury") canterbury_height = nz_height[canterbury, ] ``` ```{r nz-subset, echo=FALSE, warning=FALSE, fig.cap="Spatial subsetting, with red triangles representing 101 high points in New Zealand, clustered near the central Canterbuy region (left). The points in Canterbury were created with the `[` subsetting operator (highlighted in gray, right).", fig.scap="Spatial subsetting.", message=FALSE} library(tmap) p_hpnz1 = tm_shape(nz) + tm_polygons(fill = "white") + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.5, col_alpha = 0.75) + tm_title("High points in New Zealand") + tm_layout(bg.color = "lightblue") p_hpnz2 = tm_shape(nz) + tm_polygons(fill = "white") + tm_shape(canterbury) + tm_fill(col = "gray") + tm_shape(canterbury_height) + tm_symbols(shape = 2, col = "red", size = 0.5, col_alpha = 0.75) + tm_title("High points in Canterbury") + tm_layout(bg.color = "lightblue") tmap_arrange(p_hpnz1, p_hpnz2, ncol = 2) ``` Like attribute subsetting, the command `x[y, ]` (equivalent to `nz_height[canterbury, ]`) subsets features of a *target* `x` using the contents of a *source* object `y`. Instead of `y` being a vector of class `logical` or `integer`, however, for spatial subsetting both `x` and `y` must be geographic objects. Specifically, objects used for spatial subsetting in this way must have the class `sf` or `sfc`: both `nz` and `nz_height` are geographic vector data frames and have the class `sf`, and the result of the operation returns another `sf` object representing the features in the target `nz_height` object that intersect with (in this case high points that are located within) the `canterbury` region. Various *topological relations*\index{topological relations} can be used for spatial subsetting which determine the type of spatial relationship that features in the target object must have with the subsetting object to be selected. These include *touches*, *crosses* or *within*, as we will see shortly in Section \@ref(topological-relations). The default setting `st_intersects` is a 'catch all' topological relation that will return features in the target that *touch*, *cross* or are *within* the source 'subsetting' object. Alternative spatial operators can be specified with the `op =` argument, as demonstrated in the following command which returns the opposite of `st_intersects()`, points that do not intersect with Canterbury (see Section \@ref(topological-relations)). ```{r 04-spatial-operations-4, eval=FALSE} nz_height[canterbury, , op = st_disjoint] ``` ```{block2 04-spatial-operations-5, type='rmdnote'} Note the empty argument --- denoted with `, ,` --- in the preceding code chunk is included to highlight `op`, the third argument in `[` for `sf` objects. One can use this to change the subsetting operation in many ways. `nz_height[canterbury, 2, op = st_disjoint]`, for example, returns the same rows but only includes the second attribute column (see `` sf::`[.sf` `` and the `?sf` for details). ``` For many applications, this is all you'll need to know about spatial subsetting for vector data: it just works. If you are impatient to learn about more topological relations, beyond `st_intersects()` and `st_disjoint()`, skip to the next section (\@ref(topological-relations)). If you're interested in the details, including other ways of subsetting, read on. Another way of doing spatial subsetting uses objects returned by topological operators. These objects can be useful in their own right, for example when exploring the graph network of relationships between contiguous regions, but they can also be used for subsetting, as demonstrated in the code chunk below. ```{r 04-spatial-operations-6, out.lines=9} sel_sgbp = st_intersects(x = nz_height, y = canterbury) class(sel_sgbp) sel_sgbp sel_logical = lengths(sel_sgbp) > 0 canterbury_height2 = nz_height[sel_logical, ] ``` The above code chunk creates an object of class `sgbp` (a sparse geometry binary predicate, a list of length `x` in the spatial operation) and then converts it into a logical vector `sel_logical` (containing only `TRUE` and `FALSE` values, something that can also be used by **dplyr**'s filter function). \index{binary predicate|see {topological relations}} The function `lengths()` identifies which features in `nz_height` intersect with *any* objects in `y`. In this case, 1 is the greatest possible value, but for more complex operations one could use the method to subset only features that intersect with, for example, 2 or more features from the source object. ```{block2 04-spatial-operations-7, type='rmdnote'} Note: another way to return a logical output is by setting `sparse = FALSE` (meaning 'return a dense matrix not a sparse one') in operators such as `st_intersects()`. The command `st_intersects(x = nz_height, y = canterbury, sparse = FALSE)[, 1]`, for example, would return an output identical to `sel_logical`. Note: the solution involving `sgbp` objects is more generalizable though, as it works for many-to-many operations and has lower memory requirements. ``` The same result can be also achieved with the **sf** function `st_filter()` which was [created](https://github.com/r-spatial/sf/issues/1148) to increase compatibility between `sf` objects and **dplyr** data manipulation code: ```{r} canterbury_height3 = nz_height |> st_filter(y = canterbury, .predicate = st_intersects) ``` ```{r 04-spatial-operations-7b-old, eval=FALSE, echo=FALSE} # Additional tests of subsetting canterbury_height4 = nz_height |> filter(st_intersects(x = _, y = canterbury, sparse = FALSE)) canterbury_height5 = nz_height |> filter(sel_logical) identical(canterbury_height3, canterbury_height4) identical(canterbury_height3, canterbury_height5) identical(canterbury_height2, canterbury_height4) identical(canterbury_height, canterbury_height4) waldo::compare(canterbury_height2, canterbury_height4) ``` At this point, there are three identical (in all but row names) versions of `canterbury_height`, one created using the `[` operator, one created via an intermediary selection object, and another using **sf**'s convenience function `st_filter()`. The next section explores different types of spatial relation, also known as binary predicates, that can be used to identify whether two features are spatially related or not. ### Topological relations Topological relations\index{topological relations} describe the spatial relationships between objects. "Binary topological relationships", to give them their full name, are logical statements (in that the answer can only be `TRUE` or `FALSE`) about the spatial relationships between two objects defined by ordered sets of points (typically forming points, lines and polygons) in two or more dimensions [@egenhofer_mathematical_1990]. That may sound rather abstract and, indeed, the definition and classification of topological relations is based on mathematical foundations first published in book form in 1966 [@spanier_algebraic_1995], with the field of algebraic topology continuing beyond the year 2000 [@dieck_algebraic_2008]. Despite their mathematical origins, topological relations can be understood intuitively with reference to visualizations of commonly used functions that test for common types of spatial relationships. Figure \@ref(fig:relations) shows a variety of geometry pairs and their associated relations. The third and fourth pairs in Figure \@ref(fig:relations) (from left to right and then down) demonstrate that, for some relations, order is important. While the relations *equals*, *intersects*, *crosses*, *touches* and *overlaps* are symmetrical, meaning that if `function(x, y)` is true, `function(y, x)` will also be true, relations in which the order of the geometries are important such as *contains* and *within* are not. Notice that each geometry pair has a "DE-9IM" string such as FF2F11212, described in the next section. \index{topological relations} ```{r relations, echo=FALSE, fig.cap="Topological relations between vector geometries, inspired by figures 1 and 2 in Egenhofer and Herring (1990). The relations for which the function(x, y) is true are printed for each geometry pair, with x represented in pink and y represented in blue. The nature of the spatial relationship for each pair is described by the Dimensionally Extended 9-Intersection Model string.", fig.show='hold', message=FALSE, fig.asp=0.66, warning=FALSE} # source("https://github.com/geocompx/geocompr/raw/main/code/de_9im.R") source("code/de_9im.R") library(sf) xy2sfc = function(x, y) st_sfc(st_polygon(list(cbind(x, y)))) p1 = xy2sfc(x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0)) p2 = xy2sfc(x = c(0, 1, 1, 0), y = c(0, 0, 0.5, 0)) p3 = xy2sfc(x = c(0, 1, 1, 0), y = c(0, 0, 0.7, 0)) p4 = xy2sfc(x = c(0.7, 0.7, 0.9, 0.7), y = c(0.8, 0.5, 0.5, 0.8)) p5 = xy2sfc(x = c(0.6, 0.7, 1, 0.6), y = c(0.7, 0.5, 0.5, 0.7)) p6 = xy2sfc(x = c(0.1, 1, 1, 0.1), y = c(0, 0, 0.3, 0)) p7 = xy2sfc(x = c(0.05, 0.05, 0.6, 0.5, 0.05), y = c(0.4, 0.97, 0.97, 0.4, 0.4)) # todo: add 3 more with line/point relations? tmap::tmap_arrange(de_9im(p1, p2), de_9im(p1, p3), de_9im(p1, p4), de_9im(p7, p1), de_9im(p1, p5), de_9im(p1, p6), nrow = 2) ``` In `sf`, functions testing for different types of topological relations are called 'binary predicates', as described in the vignette *Manipulating Simple Feature Geometries*, which can be viewed with the command [`vignette("sf3")`](https://r-spatial.github.io/sf/articles/sf3.html), and in the help page [`?geos_binary_pred`](https://r-spatial.github.io/sf/reference/geos_binary_ops.html). To see how topological relations work in practice, let's create a simple reproducible example, building on the relations illustrated in Figure \@ref(fig:relations) and consolidating knowledge of how vector geometries are represented from a previous chapter (Section \@ref(geometry)). Note that to create tabular data representing coordinates (x and y) of the polygon vertices, we use the base R function `cbind()` to create a matrix representing coordinates points, a `POLYGON`, and finally an `sfc` object, as described in Chapter \@ref(spatial-class): ```{r} polygon_matrix = cbind( x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0) ) polygon_sfc = st_sfc(st_polygon(list(polygon_matrix))) ``` We will create additional geometries to demonstrate spatial relations with the following commands which, when plotted on top of the polygon created above, relate in space to one another, as shown in Figure \@ref(fig:relation-objects). Note the use of the function `st_as_sf()` and the argument `coords` to efficiently convert from a data frame containing columns representing coordinates to an `sf` object containing points: ```{r} point_df = data.frame( x = c(0.2, 0.7, 0.4), y = c(0.1, 0.2, 0.8) ) point_sf = st_as_sf(point_df, coords = c("x", "y")) ``` ```{r relation-objects, echo=FALSE, fig.cap="Points, line and polygon objects arranged to illustrate topological relations.", fig.asp=1, out.width="50%", fig.scap="Demonstration of topological relations."} par(pty = "s") plot(polygon_sfc, border = "red", col = "gray", axes = TRUE) plot(point_sf, add = TRUE, lab = 1:4, cex = 2) text(point_df[, 1] + 0.02, point_df[, 2] + 0.04, 1:3, cex = 1.3) ``` A simple query is: which of the points in `point_sf` intersect in some way with polygon `polygon_sfc`? The question can be answered by inspection (points 1 and 3 are touching and within the polygon, respectively). This question can be answered with the spatial predicate `st_intersects()` as follows: ```{r 04-spatial-operations-9, eval=FALSE} st_intersects(point_sf, polygon_sfc) #> Sparse geometry binary predicate... `intersects' #> 1: 1 #> 2: (empty) #> 3: 1 ``` The result should match your intuition: positive (`1`) results are returned for the first and third point, and a negative result (represented by an empty vector) for the second are outside the polygon's border. What may be unexpected is that the result comes in the form of a list of vectors. This *sparse matrix* output only registers a relation if one exists, reducing the memory requirements of topological operations on multi-feature objects. As we saw in the previous section, a *dense matrix* consisting of `TRUE` or `FALSE` values is returned when `sparse = FALSE`. ```{r 04-spatial-operations-10} st_intersects(point_sf, polygon_sfc, sparse = FALSE) ``` In the above output each row represents a feature in the target (argument `x`) object, and each column represents a feature in the selecting object (`y`). In this case, there is only one feature in the `y` object `polygon_sfc` so the result, which can be used for subsetting as we saw in Section \@ref(spatial-subsetting), has only one column. `st_intersects()` returns `TRUE` even in cases where the features just touch: *intersects*\index{intersects} is a 'catch-all' topological operation which identifies many types of spatial relation, as illustrated in Figure \@ref(fig:relations). More restrictive questions include which points lie within the polygon, and which features are on or contain a shared boundary with `y`? These can be answered as follows (results not shown): ```{r 04-spatial-operations-9-2, eval=FALSE} st_within(point_sf, polygon_sfc) st_touches(point_sf, polygon_sfc) ``` Note that although the first point *touches* the boundary polygon, it is not within it; the third point is within the polygon but does not touch any part of its border. The opposite of `st_intersects()` is `st_disjoint()`, which returns only objects that do not spatially relate in any way to the selecting object (note `[, 1]` converts the result into a vector). ```{r 04-spatial-operations-11} st_disjoint(point_sf, polygon_sfc, sparse = FALSE)[, 1] ``` The function `st_is_within_distance()` detects features that *almost touch* the selection object, which has an additional `dist` argument. It can be used to set how close target objects need to be before they are selected. The 'is within distance' binary spatial predicate is demonstrated in the code chunk below, the results of which show that every point is within 0.2 units of the polygon. ```{r 04-spatial-operations-14} st_is_within_distance(point_sf, polygon_sfc, dist = 0.2, sparse = FALSE)[, 1] ``` Note that although point 2 is more than 0.2 units of distance from the nearest vertex of `polygon_sfc`, it is still selected when the distance is set to 0.2. This is because distance is measured to the nearest edge, in this case the part of the polygon that lies directly above point 2 in Figure \@ref(fig:relation-objects). (You can verify the actual distance between point 2 and the polygon is 0.13 with the command `st_distance(point_sf, polygon_sfc)`.) ```{r, eval=FALSE, echo=FALSE} # verify distances to the polygon with reference to paragraph above: st_distance(point_sf, polygon_sfc) # [,1] # [1,] 0.0000000 # [2,] 0.1341641 # [3,] 0.0000000 ``` ```{block2 04-spatial-operations-15, type='rmdnote'} Functions for calculating topological relations use spatial indices to largely speed up spatial query performance. They achieve that using the Sort-Tile-Recursive (STR) algorithm. The `st_join` function, mentioned in the next section, also uses the spatial indexing. You can learn more at https://www.r-spatial.org/r/2017/06/22/spatial-index.html. ``` ```{r 04-spatial-operations-16, eval=FALSE, echo=FALSE} # other tests st_overlaps(point_sf, polygon_sfc, sparse = FALSE) st_covers(point_sf, polygon_sfc, sparse = FALSE) st_covered_by(point_sf, polygon_sfc, sparse = FALSE) ``` ```{r 04-spatial-operations-17, eval=FALSE, echo=FALSE} st_contains(a, p[2, ], sparse = TRUE) ``` ```{r 04-spatial-operations-18, eval=FALSE, echo=FALSE} # starting simpler so commented a1 = st_polygon(list(rbind(c(-1, -1), c(1, -1), c(1, 1), c(-1, -1)))) a2 = st_polygon(list(rbind(c(2, 0), c(2, 2), c(3, 2), c(3, 0), c(2, 0)))) a = st_sfc(a1, a2) b1 = a1 * 0.5 b2 = a2 * 0.4 + c(1, 0.5) b = st_sfc(b1, b2) l1 = st_linestring(x = matrix(c(0, 3, -1, 1), , 2)) l2 = st_linestring(x = matrix(c(-1, -1, -0.5, 1), , 2)) l = st_sfc(l1, l2) p = st_multipoint(x = matrix(c(0.5, 1, -1, 0, 1, 0.5), , 2)) plot(a, border = "red", axes = TRUE) plot(b, border = "green", add = TRUE) plot(l, add = TRUE) plot(p, add = TRUE) ``` ### Distance relations While the topological relations presented in the previous section are binary (a feature either intersects with another or does not) distance relations are continuous\index{distance relations}. The distance between two `sf` objects is calculated with `st_distance()`, which is also used behind the scenes in Section \@ref(non-overlapping-joins) for distance-based joins. This is illustrated in the code chunk below, which finds the distance between the highest point in New Zealand and the geographic centroid of the Canterbury region, created in Section \@ref(spatial-subsetting): \index{vector!distance relations} ```{r 04-spatial-operations-31, warning=FALSE} nz_highest = nz_height |> slice_max(n = 1, order_by = elevation) canterbury_centroid = st_centroid(canterbury) st_distance(nz_highest, canterbury_centroid) ``` There are two potentially surprising things about the result: - It has `units`, telling us the distance is 100,000 meters, not 100,000 inches, or any other measure of distance - It is returned as a matrix, even though the result only contains a single value This second feature hints at another useful feature of `st_distance()`, its ability to return *distance matrices* between all combinations of features in objects `x` and `y`. This is illustrated in the command below, which finds the distances between the first three features in `nz_height` and the Otago and Canterbury regions of New Zealand represented by the object `co`. ```{r 04-spatial-operations-32} co = filter(nz, grepl("Canter|Otag", Name)) st_distance(nz_height[1:3, ], co) ``` Note that the distance between the second and third features in `nz_height` and the second feature in `co` is zero. This demonstrates the fact that distances between points and polygons refer to the distance to *any part of the polygon*. The second and third points in `nz_height` are *in* Otago, which can be verified by plotting them (result not shown): ```{r 04-spatial-operations-33, eval=FALSE} plot(st_geometry(co)[2]) plot(st_geometry(nz_height)[2:3], add = TRUE) ``` ### DE-9IM strings {#DE-9IM-strings} Underlying the binary predicates demonstrated in the previous section is the Dimensionally Extended 9-Intersection Model (DE-9IM)\index{topological relations!DE-9IM}. As the cryptic name suggests, this is not an easy topic to understand, but it is worth knowing about because it underlies many spatial operations and enables the creation of custom spatial predicates. The model was originally labelled "DE + 9IM" by its inventors, referring to the "dimension of the intersections of boundaries, interiors, and exteriors of two features" [@clementini_comparison_1995], but it is now referred to as DE-9IM [@shen_classification_2018]. DE-9IM is applicable to two-dimensional objects (points, lines and polygons) in Euclidean space, meaning that the model (and software implementing it such as GEOS) assumes you are working with data in a projected coordinate reference system, described in Chapter \@ref(reproj-geo-data). ```{r de-9im, echo=FALSE, eval=FALSE} # Todo one day: revive this b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles bsf = sf::st_sf(data.frame(Object = c("a", "b")), geometry = b) b9 = replicate(bsf, n = 9, simplify = FALSE) b9sf = do.call(rbind, b9) domains = c("Interior", "Boundary", "Exterior") b9sf$domain_a = rep(rep(domains, 3), each = 2) b9sf$domain_b = rep(rep(domains, each = 3), each = 2) library(ggplot2) ggplot(b9sf) + geom_sf() + facet_grid(domain_a ~ domain_b) plot(b9sf) tmap_arrange( tm_shape(b) + tm_polygons(alpha = 0.5) + tm_layout(title = "Interior-Interior"), tm_shape(b) + tm_polygons(alpha = 0.5) + tm_layout(title = "Interior-Boundary"), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), tm_shape(b) + tm_polygons(alpha = 0.5), nrow = 3 ) plot(b) text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y")) # add text ``` To demonstrate how DE-9IM strings work, let's take a look at the various ways that the first geometry pair in Figure \@ref(fig:relations) relate. Figure \@ref(fig:de9imgg) illustrates the 9-intersection model (9IM) which shows the intersections between every combination of each object's interior, boundary and exterior: when each component of the first object `x` is arranged as columns, and each component of `y` is arranged as rows, a facetted graphic is created with the intersections between each element highlighted. ```{r de9imgg, echo=FALSE, warning=FALSE, fig.cap="Illustration of how the Dimensionally Extended 9 Intersection Model (DE-9IM) works. Colors not in the legend represent the overlap between different components. The thick lines highlight two-dimensional intersections, e.g., between the boundary of object x and the interior of object y, shown in the middle top facet.", message=FALSE} p1_2 = st_as_sf(c(p1, p3)) ii = st_as_sf(st_intersection(p1, p3)) ii$Object = "Intersection" ii$domain_a = "Interior" ii$domain_b = "Interior" bi = st_sf(x = st_intersection( st_cast(p1, "LINESTRING"), st_difference(p3, st_buffer(st_cast(p3, "LINESTRING"), dist = 0.01)) )) bi = st_buffer(bi, dist = 0.01) bi$Object = "Intersection" bi$domain_a = "Boundary" bi$domain_b = "Interior" ei = st_sf(x = st_difference(p3, p1)) ei$Object = "Intersection" ei$domain_a = "Exterior" ei$domain_b = "Interior" ib = st_sf(x = st_intersection( st_cast(p3, "LINESTRING"), st_difference(p1, st_buffer(st_cast(p1, "LINESTRING"), dist = 0.005)) )) ib = st_buffer(ib, dist = 0.01) ib$Object = "Intersection" ib$domain_a = "Interior" ib$domain_b = "Boundary" bb = st_cast(ii, "POINT") bb_line = st_sf(x = st_sfc(st_linestring(matrix(c(1, 0.5, 1, 0.7), nrow = 2, byrow = TRUE)))) bb_line_buffer = st_buffer(bb_line, dist = 0.01) bb_buffer = st_buffer(bb, dist = 0.01) bb = st_union(bb_buffer, bb_line_buffer) bb$Object = "Intersection" bb$domain_a = "Boundary" bb$domain_b = "Boundary" eb = st_sf(x = st_difference( st_cast(p3, "LINESTRING"), p1 )) eb = st_buffer(eb, dist = 0.01) eb$Object = "Intersection" eb$domain_a = "Exterior" eb$domain_b = "Boundary" ie = st_sf(x = st_difference(p1, p3)) ie$Object = "Intersection" ie$domain_a = "Interior" ie$domain_b = "Exterior" be = st_sf(x = st_difference( st_cast(p1, "LINESTRING"), p3 )) be = st_buffer(be, dist = 0.01) be$Object = "Intersection" be$domain_a = "Boundary" be$domain_b = "Exterior" ee = st_sf(x = st_difference( st_buffer(st_union(p1, p3), 0.02), st_union(p1, p3) )) ee$Object = "Intersection" ee$domain_a = "Exterior" ee$domain_b = "Exterior" b9 = replicate(p1_2, n = 9, simplify = FALSE) b9sf = do.call(rbind, b9) b9sf$Object = rep(c("x", "y"), 9) domains = c("Interior", "Boundary", "Exterior") b9sf$domain_a = rep(rep(domains, 3), each = 2) b9sf$domain_b = rep(rep(domains, each = 3), each = 2) b9sf = rbind(b9sf, ii, bi, ei, ib, bb, eb, ie, be, ee) b9sf$domain_a = ordered(b9sf$domain_a, levels = c("Interior", "Boundary", "Exterior")) b9sf$domain_b = ordered(b9sf$domain_b, levels = c("Interior", "Boundary", "Exterior")) b9sf = b9sf |> mutate(alpha = case_when( Object == "x" ~ 0.1, Object == "y" ~ 0.1, TRUE ~ 0.2 )) library(ggplot2) ggplot(b9sf) + geom_sf(aes(fill = Object, alpha = alpha)) + facet_grid(domain_b ~ domain_a) + scale_fill_manual(values = c("red", "lightblue", "yellow"), position = "top", name = "") + scale_alpha_continuous(range = c(0.3, 0.9)) + guides(alpha = "none") + theme_void() + theme(legend.position = "top") ``` DE-9IM strings are derived from the dimension of each type of relation. In this case, the red intersections in Figure \@ref(fig:de9imgg) have dimensions of 0 (points), 1 (lines), and 2 (polygons), as shown in Table \@ref(tab:de9emtable). ```{r de9emtable, echo=FALSE} # See https://github.com/geocompx/geocompr/issues/699 pattern = st_relate(p1, p3) matrix_de_9im = function(pattern) { string = unlist(strsplit(pattern , "")) matrix_de_9im = matrix(string, nrow = 3, byrow = TRUE) colnames(matrix_de_9im) = c("I", "B", "E") row.names(matrix_de_9im) = c("I", "B", "E") return(matrix_de_9im) } m = matrix_de_9im(pattern) colnames(m) = c("Interior (x)", "Boundary (x)", "Exterior (x)") rownames(m) = c("Interior (y)", "Boundary (y)", "Exterior (y)") knitr::kable(m, caption = "Relations between interiors, boundaries and exteriors of geometries x and y.") ``` Flattening this matrix 'row-wise' (meaning concatenating the first row, then the second, then the third) results in the string `212111212`. Another example will serve to demonstrate the system: the relation shown in Figure \@ref(fig:relations) (the third polygon pair in the third column and 1st row) can be defined in the DE-9IM system as follows: - The intersections between the *interior* of the larger object `x` and the interior, boundary and exterior of `y` have dimensions of 2, 1 and 2, respectively - The intersections between the *boundary* of the larger object `x` and the interior, boundary and exterior of `y` have dimensions of F, F and 1, respectively, where 'F' means 'false', the objects are disjoint - The intersections between the *exterior* of `x` and the interior, boundary and exterior of `y` have dimensions of F, F and 2, respectively: the exterior of the larger object does not touch the interior or boundary of `y`, but the exterior of the smaller and larger objects cover the same area These three components, when concatenated, create the string `212`, `FF1`, and `FF2`. This is the same as the result obtained from the function `st_relate()` (see the source code of this chapter to see how other geometries in Figure \@ref(fig:relations) were created): ```{r} xy2sfc = function(x, y) st_sfc(st_polygon(list(cbind(x, y)))) x = xy2sfc(x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0)) y = xy2sfc(x = c(0.7, 0.7, 0.9, 0.7), y = c(0.8, 0.5, 0.5, 0.8)) st_relate(x, y) ``` Understanding DE-9IM strings allows new binary spatial predicates to be developed. The help page `?st_relate` contains function definitions for 'queen' and 'rook' relations in which polygons share a border or only a point, respectively. 'Queen' relations mean that 'boundary-boundary' relations (the cell in the second column and the second row in Table \@ref(tab:de9emtable), or the 5th element of the DE-9IM string) must not be empty, corresponding to the pattern `F***T****`, while for 'rook' relations, the same element must be 1 (meaning a linear intersection) (see Figure \@ref(fig:queens)). These are implemented as follows: ```{r} st_queen = function(x, y) st_relate(x, y, pattern = "F***T****") st_rook = function(x, y) st_relate(x, y, pattern = "F***1****") ``` Building on the object `x` created previously, we can use the newly created functions to find out which elements in the grid are a 'queen' and 'rook' in relation to the middle square of the grid as follows: ```{r queenscode, fig.show='hide'} grid = st_make_grid(x, n = 3) grid_sf = st_sf(grid) grid_sf$queens = lengths(st_queen(grid, grid[5])) > 0 plot(grid, col = grid_sf$queens) grid_sf$rooks = lengths(st_rook(grid, grid[5])) > 0 plot(grid, col = grid_sf$rooks) ``` ```{r queens, fig.cap="Demonstration of custom binary spatial predicates for finding queen (left) and rook (right) relations to the central square in a grid with 9 geometries.", echo=FALSE, warning=FALSE} st_crs(grid_sf) = "EPSG:2180" tm_shape(grid_sf) + tm_fill(fill = c("queens", "rooks"), fill.scale = tm_scale(values = c("white", "black"))) + tm_shape(grid_sf) + tm_borders(col = "gray", lwd = 2) + tm_layout(frame = FALSE, legend.show = FALSE, panel.labels = c("queen", "rook")) ``` ```{r, echo=FALSE, eval=FALSE} st_lineoverlap = function(x, y) st_relate(x, y, pattern = "T*1******") line1 = st_sfc(st_linestring(cbind( x = c(0, 0.8), y = c(0, 0) ))) line2 = st_sfc(st_linestring(cbind( x = c(0.1, 0.5), y = c(0, 0) ))) line3 = st_sfc(st_linestring(cbind( x = c(0, 0.5), y = c(0, 0.2) ))) st_queen(line1, line2) st_relate(line1, line2) st_relate(line1, line3) st_lineoverlap(line1, line2) st_lineoverlap(line1, line3) de_9im(line1, line2) # test the function rnet = pct::get_pct_rnet(region = "isle-of-wight") osm_net = osmextract::oe_get_network(place = "isle-of-wight", mode = "driving") sel = st_relate(rnet, osm_net, pattern = "T*1******") summary(lengths(sel) > 0) rnet_joined1 = st_join(rnet, osm_net, join = st_lineoverlap) rnet_joined2 = st_join(rnet, osm_net, join = st_relate, pattern = "T*1******") rnet_joined3 = st_join(rnet, osm_net) summary(is.na(rnet_joined1$osm_id)) summary(is.na(rnet_joined2$osm_id)) summary(is.na(rnet_joined3$osm_id)) sel_relates = st_relate(rnet[1, ], osm_net) dim(sel_relates) sel_table = table(sel_relates) sel_table dim(sel_table) sel_restrictive = sel_relates[1, ] == "0F1FF0102" summary(sel_restrictive) nrow(osm_net) mapview::mapview(rnet[1, ]) + mapview::mapview(osm_net[sel_restrictive, ]) rnet_approx = rnet st_precision(rnet_approx) = 100 head(st_coordinates(rnet_approx)) sel_relates = st_relate(rnet_approx[1, ], osm_net) dim(sel_relates) sel_table = table(sel_relates) sel_table ``` ### Spatial joining Joining two non-spatial datasets relies on a shared 'key' variable, as described in Section \@ref(vector-attribute-joining). Spatial data joining applies the same concept, but instead relies on spatial relations, described in the previous section. As with attribute data, joining adds new columns to the target object (the argument `x` in joining functions), from a source object (`y`). \index{join!spatial} \index{spatial!join} The process is illustrated by the following example: imagine you have ten points randomly distributed across the Earth's surface and you ask, for the points that are on land, which countries are they in? Implementing this idea in a [reproducible example](https://github.com/geocompx/geocompr/blob/main/code/04-spatial-join.R) will build your geographic data-handling skills and will showhow spatial joins work. The starting point is to create points that are randomly scattered over the Earth's surface. ```{r 04-spatial-operations-19} set.seed(2018) # set seed for reproducibility (bb = st_bbox(world)) # the world's bounds random_df = data.frame( x = runif(n = 10, min = bb[1], max = bb[3]), y = runif(n = 10, min = bb[2], max = bb[4]) ) random_points = random_df |> st_as_sf(coords = c("x", "y"), crs = "EPSG:4326") # set coordinates and CRS ``` The scenario illustrated in Figure \@ref(fig:spatial-join) shows that the `random_points` object (top left) lacks attribute data, while the `world` (top right) has attributes, including country names shown for a sample of countries in the legend. Spatial joins are implemented with `st_join()`, as illustrated in the code chunk below. The output is the `random_joined` object which is illustrated in Figure \@ref(fig:spatial-join) (bottom left). Before creating the joined dataset, we use spatial subsetting to create `world_random`, which contains only countries that contain random points, to verify that number of country names returned in the joined dataset should be four (Figure \@ref(fig:spatial-join), top right panel). ```{r 04-spatial-operations-20, message=FALSE} world_random = world[random_points, ] nrow(world_random) random_joined = st_join(random_points, world["name_long"]) ``` ```{r spatial-join, echo=FALSE, fig.cap="Illustration of a spatial join. A new attribute variable is added to random points (top left) from source world object (top right) resulting in the data represented in the final panel.", fig.asp=0.5, warning=FALSE, message=FALSE, out.width="100%", fig.scap="Illustration of a spatial join."} source("code/04-spatial-join.R", print.eval = TRUE) ``` By default, `st_join()` performs a left join, meaning that the result is an object containing all rows from `x` including rows with no match in `y` (see Section \@ref(vector-attribute-joining)), but it can also do inner joins by setting the argument `left = FALSE`. Like spatial subsetting, the default topological operator used by `st_join()` is `st_intersects()`, which can be changed by setting the `join` argument (see `?st_join` for details). The example above demonstrates the addition of a column from a polygon layer to a point layer, but the approach works regardless of geometry types. In such cases, for example when `x` contains polygons, each of which matches multiple objects in `y`, spatial joins will result in duplicate features by creating a new row for each match in `y`. ### Distance-based joins {#non-overlapping-joins} Sometimes two geographic datasets do not intersect but still have a strong geographic relationship due to their proximity. The datasets `cycle_hire` and `cycle_hire_osm`, already attached in the **spData** package, provide a good example. Plotting them shows that they are often closely related, but they do not touch, as shown in Figure \@ref(fig:cycle-hire), a base version of which is created with the following code below: \index{join!non-overlapping} ```{r 04-spatial-operations-21, eval=FALSE} plot(st_geometry(cycle_hire), col = "blue") plot(st_geometry(cycle_hire_osm), add = TRUE, pch = 3, col = "red") ``` We can check if any points are the same using `st_intersects()` as shown below: ```{r 04-spatial-operations-22, message=FALSE} any(st_intersects(cycle_hire, cycle_hire_osm, sparse = FALSE)) ``` ```{r 04-spatial-operations-23, echo=FALSE, eval=FALSE} # included to show alternative ways of showing there's no overlap sum(st_geometry(cycle_hire) %in% st_geometry(cycle_hire_osm)) sum(st_coordinates(cycle_hire)[, 1] %in% st_coordinates(cycle_hire_osm)[, 1]) ``` ```{r cycle-hire, fig.cap="The spatial distribution of cycle hire points in London based on official data (blue) and OpenStreetMap data (red).", echo=FALSE, warning=FALSE, fig.scap="The spatial distribution of cycle hire points in London."} if (knitr::is_latex_output()){ knitr::include_graphics("images/cycle-hire-1.png") } else if (knitr::is_html_output()){ # library(tmap) # osm_tiles = tmaptools::read_osm(tmaptools::bb(cycle_hire, ext = 1.3), type = "https://korona.geog.uni-heidelberg.de/tiles/roadsg/x={x}&y={y}&z={z}") # qtm(osm_tiles) + # tm_shape(cycle_hire) + # tm_bubbles(col = "blue", alpha = 0.5, size = 0.2) + # tm_shape(cycle_hire_osm) + # tm_bubbles(col = "red", alpha = 0.5, size = 0.2) + # tm_scale_bar() library(leaflet) leaflet() |> # addProviderTiles(providers$OpenStreetMap.BlackAndWhite) |> addCircles(data = cycle_hire) |> addCircles(data = cycle_hire_osm, col = "red") } ``` Imagine that we need to join the `capacity` variable in `cycle_hire_osm` onto the official 'target' data contained in `cycle_hire`. This is when a non-overlapping join is needed. The simplest method is to use the binary predicate `st_is_within_distance()`, as demonstrated below using a threshold distance of 20 m. One can set the threshold distance in metric units also for unprojected data (e.g., lon/lat CRSs such as WGS84), if the spherical geometry engine (S2) is enabled, as it is in **sf** by default (see Section \@ref(s2)). ```{r 04-spatial-operations-24} sel = st_is_within_distance(cycle_hire, cycle_hire_osm, dist = units::set_units(20, "m")) summary(lengths(sel) > 0) ``` This shows that there are `r sum(lengths(sel) > 0)` points in the target object `cycle_hire` within the threshold distance of `cycle_hire_osm`. How to retrieve the *values* associated with the respective `cycle_hire_osm` points? The solution is again with `st_join()`, but with an additional `dist` argument (set to 20 m below): ```{r 04-spatial-operations-25} z = st_join(cycle_hire, cycle_hire_osm, st_is_within_distance, dist = units::set_units(20, "m")) nrow(cycle_hire) nrow(z) ``` Note that the number of rows in the joined result is greater than the target. This is because some cycle hire stations in `cycle_hire` have multiple matches in `cycle_hire_osm`. To aggregate the values for the overlapping points and return the mean, we can use the aggregation methods learned in Chapter \@ref(attr), resulting in an object with the same number of rows as the target. ```{r 04-spatial-operations-26} z = z |> group_by(id) |> summarize(capacity = mean(capacity)) nrow(z) == nrow(cycle_hire) ``` The capacity of nearby stations can be verified by comparing a plot of the capacity of the source `cycle_hire_osm` data with the results in this new object (plots not shown): ```{r 04-spatial-operations-27, eval=FALSE} plot(cycle_hire_osm["capacity"]) plot(z["capacity"]) ``` The result of this join has used a spatial operation to change the attribute data associated with simple features; the geometry associated with each feature has remained unchanged. ### Spatial aggregation {#spatial-aggr} As with attribute data aggregation, spatial data aggregation *condenses* data: aggregated outputs have fewer rows than non-aggregated inputs. Statistical *aggregating functions*, such as mean average or sum, summarize multiple values \index{statistics} of a variable, and return a single value per *grouping variable*. Section \@ref(vector-attribute-aggregation) demonstrated how `aggregate()` and `group_by() |> summarize()` condense data based on attribute variables, this section shows how the same functions work with spatial objects. \index{aggregation!spatial} Returning to the example of New Zealand, imagine you want to find out the average height of high points in each region: it is the geometry of the source (`y` or `nz` in this case) that defines how values in the target object (`x` or `nz_height`) are grouped. This can be done in a single line of code with base R's `aggregate()` method. ```{r 04-spatial-operations-28} nz_agg = aggregate(x = nz_height, by = nz, FUN = mean) ``` The result of the previous command is an `sf` object with the same geometry as the (spatial) aggregating object (`nz`), which you can verify with the command `identical(st_geometry(nz), st_geometry(nz_agg))`. The result of the previous operation is illustrated in Figure \@ref(fig:spatial-aggregation), which shows the average value of features in `nz_height` within each of New Zealand's 16 regions. The same result can also be generated by piping the output from `st_join()` into the 'tidy' functions `group_by()` and `summarize()` as follows: ```{r spatial-aggregation, echo=FALSE, fig.cap="Average height of the top 101 high points across the regions of New Zealand.", fig.asp=1, message=FALSE, out.width="50%"} library(tmap) tm_shape(nz_agg) + tm_fill("elevation", fill.scale = tm_scale(breaks = seq(27, 30, by = 0.5) * 1e2)) + tm_borders() + tm_layout(scale = 1.8) ``` ```{r 04-spatial-operations-29} nz_agg2 = st_join(x = nz, y = nz_height) |> group_by(Name) |> summarize(elevation = mean(elevation, na.rm = TRUE)) ``` ```{r test-tidy-spatial-join, eval=FALSE, echo=FALSE} plot(nz_agg) plot(nz_agg2) # aggregate looses the name of aggregating objects ``` The resulting `nz_agg` objects have the same geometry as the aggregating object `nz` but with a new column summarizing the values of `x` in each region using the function `mean()`. Other functions could be used instead of `mean()` here, including `median()`, `sd()` and other functions that return a single value per group. Note: one difference between the `aggregate()` and `group_by() |> summarize()` approaches is that the former results in `NA` values for unmatching region names, while the latter preserves region names. The 'tidy' approach is thus more flexible in terms of aggregating functions and the column names of the results. Aggregating operations that also create new geometries are covered in Section \@ref(geometry-unions). ### Joining incongruent layers {#incongruent} Spatial congruence\index{spatial congruence} is an important concept related to spatial aggregation. An *aggregating object* (which we will refer to as `y`) is *congruent* with the target object (`x`) if the two objects have shared borders. Often this is the case for administrative boundary data, whereby larger units --- such as Middle Layer Super Output Areas ([MSOAs](https://www.ons.gov.uk/methodology/geography/ukgeographies/censusgeography)) in the UK or districts in many other European countries --- are composed of many smaller units. *Incongruent* aggregating objects, by contrast, do not share common borders with the target [@qiu_development_2012]. This is problematic for spatial aggregation (and other spatial operations) illustrated in Figure \@ref(fig:areal-example): aggregating the centroid of each sub-zone will not return accurate results. Areal interpolation overcomes this issue by transferring values from one set of areal units to another, using a range of algorithms including simple area weighted approaches and more sophisticated approaches such as 'pycnophylactic' methods [@tobler_smooth_1979]. ```{r areal-example, echo=FALSE, fig.cap="Congruent (left) and incongruent (right) areal units with respect to larger aggregating zones (translucent red borders).", fig.asp=0.33, fig.scap="Congruent and incongruent areal units."} source("code/04-areal-example.R", print.eval = TRUE) ``` The **spData** package contains a dataset named `incongruent` (colored polygons with black borders in the right panel of Figure \@ref(fig:areal-example)) and a dataset named `aggregating_zones` (the two polygons with the translucent red border in the right panel of Figure \@ref(fig:areal-example)). Let us assume that the `value` column of `incongruent` refers to the total regional income in million Euros. How can we transfer the values of the underlying nine spatial polygons into the two polygons of `aggregating_zones`? The simplest useful method for this is *area weighted* spatial interpolation, which transfers values from the `incongruent` object to a new column in `aggregating_zones` in proportion with the area of overlap: the larger the spatial intersection between input and output features, the larger the corresponding value. This is implemented in `st_interpolate_aw()`, as demonstrated in the code chunk below. ```{r 04-spatial-operations-30} iv = incongruent["value"] # keep only the values to be transferred agg_aw = st_interpolate_aw(iv, aggregating_zones, extensive = TRUE) agg_aw$value ``` In our case it is meaningful to sum up the values of the intersections falling into the aggregating zones since total income is a so-called spatially extensive variable (which increases with area), assuming income is evenly distributed across the smaller zones (hence the warning message above). This would be different for spatially [intensive](https://geodacenter.github.io/workbook/3b_rates/lab3b.html#spatially-extensive-and-spatially-intensive-variables) variables such as *average* income or percentages, which do not increase as the area increases. `st_interpolate_aw()` works equally with spatially intensive variables: set the `extensive` parameter to `FALSE` and it will use an average rather than a sum function when doing the aggregation. ## Spatial operations on raster data {#spatial-ras} This section builds on Section \@ref(manipulating-raster-objects), which highlights various basic methods for manipulating raster datasets, to demonstrate more advanced and explicitly spatial raster operations, and it uses the objects `elev` and `grain` manually created in Section \@ref(manipulating-raster-objects). For the reader's convenience, these datasets can be also found in the **spData** package. ```{r} elev = rast(system.file("raster/elev.tif", package = "spData")) grain = rast(system.file("raster/grain.tif", package = "spData")) ``` ### Spatial subsetting {#spatial-raster-subsetting} The previous chapter (Section \@ref(manipulating-raster-objects)) demonstrated how to retrieve values associated with specific cell IDs or row and column combinations. Raster objects can also be extracted by location (coordinates) and other spatial objects. To use coordinates for subsetting, one can 'translate' the coordinates into a cell ID with the **terra** function `cellFromXY()`. An alternative is to use `terra::extract()` (be careful, there is also a function called `extract()` in the **tidyverse**\index{tidyverse (package)}) to extract values. Both methods are demonstrated below to find the value of the cell that covers a point located at coordinates of 0.1, 0.1. \index{raster!subsetting} \index{spatial!subsetting} ```{r 04-spatial-operations-34, eval = FALSE} id = cellFromXY(elev, xy = matrix(c(0.1, 0.1), ncol = 2)) elev[id] # the same as terra::extract(elev, matrix(c(0.1, 0.1), ncol = 2)) ``` Raster objects can also be subset with another raster object, as demonstrated in the code chunk below: ```{r 04-spatial-operations-35, eval=FALSE} clip = rast(xmin = 0.9, xmax = 1.8, ymin = -0.45, ymax = 0.45, resolution = 0.3, vals = rep(1, 9)) elev[clip] # we can also use extract # terra::extract(elev, ext(clip)) ``` This amounts to retrieving the values of the first raster object (in this case `elev`) that fall within the extent of a second raster (here: `clip`). The example above returned the values of specific cells, but in many cases spatial outputs from subsetting operations on raster datasets are needed. This can be done by setting the `drop` argument of the `[` operator to `FALSE`. The code below returns the first two cells of `elev`, i.e., the first two cells of the top row, as a raster object (only the first 2 lines of the output is shown): ```{r 04-spatial-operations-36, eval=FALSE} elev[1:2, drop = FALSE] # spatial subsetting with cell IDs #> class : SpatRaster #> dimensions : 1, 2, 1 (nrow, ncol, nlyr) #> ... ``` ```{r 04-spatial-operations-37, echo=FALSE, eval=FALSE} # aim: illustrate the result of previous spatial subsetting example x = elev[1, 1:2, drop = FALSE] plot(x) ``` Another common use case of spatial subsetting is when a raster with `logical` (or `NA`) values is used to mask another raster with the same extent and resolution, as illustrated in Figure \@ref(fig:raster-subset). In this case, the `[` and `mask()` functions can be used (results not shown). ```{r raster-subset, echo = FALSE, fig.cap = "Original raster (left), raster mask (middle), and output of masking a raster (right).", fig.scap="Subsetting raster values."} knitr::include_graphics("images/04_raster_subset.png") ``` ```{r 04-spatial-operations-38, eval=FALSE} # create raster mask rmask = elev values(rmask) = sample(c(NA, TRUE), 36, replace = TRUE) ``` In the code chunk above, we have created a mask object called `rmask` with values randomly assigned to `NA` and `TRUE`. Next, we want to keep those values of `elev` which are `TRUE` in `rmask`. In other words, we want to mask `elev` with `rmask`. ```{r 04-spatial-operations-38b, eval=FALSE} # spatial subsetting elev[rmask, drop = FALSE] # with [ operator # we can also use mask # mask(elev, rmask) ``` The above approach can be also used to replace some values (e.g., expected to be wrong) with NA. ```{r 04-spatial-operations-38c, eval=FALSE} elev[elev < 20] = NA ``` These operations are in fact Boolean local operations since we compare cell-wise two rasters. The next subsection explores these and related operations in more detail. ### Map algebra \index{map algebra} The term 'map algebra' was coined in the late 1970s to describe a "set of conventions, capabilities, and techniques" for the analysis of geographic raster *and* (although less prominently) vector data [@tomlin_map_1994]. In this context, we define map algebra more narrowly, as operations that modify or summarize raster cell values, with reference to surrounding cells, zones, or statistical functions that apply to every cell. Map algebra operations tend to be fast, because raster datasets only implicitly store coordinates, hence the [old adage](https://geozoneblog.wordpress.com/2013/04/19/raster-vs-vector/) "raster is faster but vector is corrector". The location of cells in raster datasets can be calculated by using its matrix position and the resolution and origin of the dataset (stored in the header). For the processing, however, the geographic position of a cell is barely relevant as long as we make sure that the cell position is still the same after the processing. Additionally, if two or more raster datasets share the same extent, projection and resolution, one could treat them as matrices for the processing. This is the way that map algebra works with the **terra** package. First, the headers of the raster datasets are queried and (in cases where map algebra operations work on more than one dataset) checked to ensure the datasets are compatible. Second, map algebra retains the so-called one-to-one locational correspondence, meaning that cells cannot move. This differs from matrix algebra, in which values change position, for example when multiplying or dividing matrices. Map algebra (or cartographic modeling with raster data) divides raster operations into four sub-classes [@tomlin_geographic_1990], with each working on one or several grids simultaneously: 1. *Local* or per-cell operations 2. *Focal* or neighborhood operations. Most often the output cell value is the result of a 3 x 3 input cell block 3. *Zonal* operations are similar to focal operations, but the surrounding pixel grid on which new values are computed can have irregular sizes and shapes 4. *Global* or per-raster operations. That means the output cell derives its value potentially from one or several entire rasters This typology classifies map algebra operations by the number of cells used for each pixel processing step and the type of the output. For the sake of completeness, we should mention that raster operations can also be classified by discipline such as terrain, hydrological analysis, or image classification. The following sections explain how each type of map algebra operations can be used, with reference to worked examples. ### Local operations \index{map algebra!local operations} **Local** operations comprise all cell-by-cell operations in one or several layers. This includes adding or subtracting values from a raster, squaring and multiplying rasters. Raster algebra also allows logical operations such as finding all raster cells that are greater than a specific value (5 in our example below). The **terra** package supports all these operations and more, as demonstrated below (Figure \@ref(fig:04-local-operations)): ```{r 04-spatial-operations-41, eval = FALSE} elev + elev elev^2 log(elev) elev > 5 ``` ```{r 04-local-operations, echo=FALSE, fig.cap="Examples of different local operations of the elev raster object: adding two rasters, squaring, applying logarithmic transformation, and performing a logical operation."} knitr::include_graphics("images/04-local-operations.png") ``` Another good example of local operations is the classification of intervals of numeric values into groups such as grouping a digital elevation model into low (class 1), middle (class 2) and high elevations (class 3). Using the `classify()` command, we need first to construct a reclassification matrix, where the first column corresponds to the lower and the second column to the upper end of the class. The third column represents the new value for the specified ranges in columns one and two. ```{r 04-spatial-operations-40} rcl = matrix(c(0, 12, 1, 12, 24, 2, 24, 36, 3), ncol = 3, byrow = TRUE) rcl ``` Here, we assign the raster values in the ranges 0--12, 12--24 and 24--36 are *reclassified* to take values 1, 2 and 3, respectively. ```{r 04-spatial-operations-40b, eval = FALSE} recl = classify(elev, rcl = rcl) ``` The `classify()` function can be also used when we want to reduce the number of classes in our categorical rasters. We will perform several additional reclassifications in Chapter \@ref(location). Apart from applying arithmetic operators directly, one can also use the `app()`, `tapp()` and `lapp()` functions. They are more efficient, hence, they are preferable in the presence of large raster datasets. Additionally, they allow you to save an output file directly. The `app()` function applies a function to each cell of a raster and is used to summarize (e.g., calculating the sum) the values of multiple layers into one layer. `tapp()` is an extension of `app()`, allowing us to select a subset of layers (see the `index` argument) for which we want to perform a certain operation. Finally, the `lapp()` function allows us to apply a function to each cell using layers as arguments -- an application of `lapp()` is presented below. The calculation of the normalized difference vegetation index (NDVI) is a well-known local (pixel-by-pixel) raster operation. It returns a raster with values between -1 and 1; positive values indicate the presence of living plants (mostly > 0.2). NDVI is calculated from red and near-infrared (NIR) bands of remotely sensed imagery, typically from satellite systems such as Landsat or Sentinel. Vegetation absorbs light heavily in the visible light spectrum, and especially in the red channel, while reflecting NIR light. Here's the NDVI formula: $$ \begin{split} NDVI&= \frac{\text{NIR} - \text{Red}}{\text{NIR} + \text{Red}}\\ \end{split} $$ Let's calculate NDVI for the multi-spectral satellite file of Zion National Park. ```{r} multi_raster_file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(multi_raster_file) ``` Our raster object has four satellite bands from the Landsat 8 satellite: blue, green, red, and NIR. Importantly, Landsat level-2 products are stored as integers to save disk space, and thus we need to convert them to floating-point numbers before doing any calculations. For that purpose, we need to apply a scaling factor (0.0000275) and add an offset (-0.2) to the original values.^[You can read more about it at https://www.usgs.gov/faqs/how-do-i-use-a-scale-factor-landsat-level-2-science-products.] ```{r} multi_rast = (multi_rast * 0.0000275) - 0.2 ``` The proper values now should be in a range between 0 and 1. This is not the case here, probably due to the presence of clouds and other atmospheric effects, which are stored as negative values. We will replace these negative values with 0 as follows. ```{r} multi_rast[multi_rast < 0] = 0 ``` The next step should be to implement the NDVI formula into an R function: ```{r} ndvi_fun = function(nir, red){ (nir - red) / (nir + red) } ``` This function accepts two numerical arguments, `nir` and `red`, and returns a numerical vector with NDVI values. It can be used as the `fun` argument of `lapp()`. We just need to remember that our function expects two bands (not four from the original raster), and they need to be in the NIR, red order. That is why we subset the input raster with `multi_rast[[c(4, 3)]]` before doing any calculations. ```{r} ndvi_rast = lapp(multi_rast[[c(4, 3)]], fun = ndvi_fun) ``` The result, shown on the right panel in Figure \@ref(fig:04-ndvi), can be compared to the RGB image of the same area (left panel of the same figure). It allows us to see that the largest NDVI values are connected to northern areas of dense forest, while the lowest values are related to the lake in the north and snowy mountain ridges. ```{r 04-ndvi, echo=FALSE, fig.cap="RGB image (left) and NDVI values (right) calculated for the example satellite file of Zion National Park"} knitr::include_graphics("images/04-ndvi.png") ``` Predictive mapping is another interesting application of local raster operations. The response variable corresponds to measured or observed points in space, for example, species richness, the presence of landslides, tree disease or crop yield. Consequently, we can easily retrieve space- or airborne predictor variables from various rasters (elevation, pH, precipitation, temperature, land cover, soil class, etc.). Subsequently, we model our response as a function of our predictors using `lm()`, `glm()`, `gam()` or a machine-learning technique. Spatial predictions on raster objects can therefore be made by applying estimated coefficients to the predictor raster values, and summing the output raster values (see Chapter \@ref(eco)). ### Focal operations \index{map algebra!focal operations} While local functions operate on one cell, though possibly from multiple layers, **focal** operations take into account a central (focal) cell and its neighbors. The neighborhood (also named kernel, filter or moving window) under consideration is typically of size 3-by-3 cells (that is the central cell and its eight surrounding neighbors), but it can take on any other size or (not necessarily rectangular) shape as defined by the user. A focal operation applies an aggregation function to all cells within the specified neighborhood, uses the corresponding output as the new value for the central cell, and moves on to the next central cell (Figure \@ref(fig:focal-example)). Other names for this operation are spatial filtering and convolution [@burrough_principles_2015]. In R, we can use the `focal()` function to perform spatial filtering. We define the shape of the moving window with a `matrix` whose values correspond to weights (see `w` parameter in the code chunk below). Secondly, the `fun` parameter lets us specify the function we wish to apply to this neighborhood. Here, we choose the minimum, but any other summary function, including `sum()`, `mean()`, or `var()` can be used. ```{r 04-spatial-operations-42, eval = FALSE} r_focal = focal(elev, w = matrix(1, nrow = 3, ncol = 3), fun = min) ``` The `min()` function has an additional argument to determine whether to remove NAs in the process (`na.rm = TRUE`) or not (`na.rm = FALSE`, the default). ```{r focal-example, echo = FALSE, fig.cap = "Input raster (left) and resulting output raster (right) due to a focal operation, finding the minimum value in 3-by-3 moving windows.", fig.scap="Illustration of a focal operation."} knitr::include_graphics("images/04_focal_example.png") ``` We can quickly check if the output meets our expectations. In our example, the minimum value has to be always the upper left corner of the moving window (remember, we have created the input raster by row-wise incrementing the cell values by one starting at the upper left corner). In this example, the weighting matrix consists only of 1s, meaning each cell has the same weight on the output, but this can be changed. Focal functions or filters play a dominant role in image processing. Low-pass or smoothing filters use the mean function to remove extremes. In the case of categorical data, we can replace the mean with the mode, which is the most common value. By contrast, high-pass filters accentuate features. The line detection Laplace and Sobel filters might serve as an example here. Check the `focal()` help page for how to use them in R (this will also be used in the exercises at the end of this chapter). Terrain processing, the calculation of topographic characteristics such as slope, aspect and flow directions, relies on focal functions. `terrain()` can be used to calculate these metrics, although some terrain algorithms, including the Zevenbergen and Thorne method to compute slope, are not implemented in this **terra** function. Many other algorithms --- including curvatures, contributing areas and wetness indices --- are implemented in open source desktop geographic information system (GIS) software. Chapter \@ref(gis) shows how to access such GIS functionality from within R. ### Zonal operations \index{map algebra!zonal operations} Just like focal operations, *zonal* operations apply an aggregation function to multiple raster cells. However, a second raster, usually with categorical values, defines the *zonal filters* (or 'zones') in the case of zonal operations, as opposed to a neighborhood window in the case of focal operations presented in the previous section. Consequently, raster cells defining the zonal filter do not necessarily have to be neighbors. Our grain-size raster is a good example, as illustrated in the right panel of Figure \@ref(fig:cont-raster): different grain sizes are spread irregularly throughout the raster. Finally, the result of a zonal operation is a summary table grouped by zone which is why this operation is also known as *zonal statistics* in the GIS world\index{GIS}. This is in contrast to focal operations which return a raster object by default. The following code chunk uses the `zonal()` function to calculate the mean elevation associated with each grain-size class. ```{r 04-spatial-operations-43} z = zonal(elev, grain, fun = "mean") z ``` This returns the statistics\index{statistics} for each category, here the mean altitude for each grain-size class. Note that it is also possible to get a raster with calculated statistics for each zone by setting the `as.raster` argument to `TRUE`. ### Global operations and distances *Global* operations are a special case of zonal operations with the entire raster dataset representing a single zone. The most common global operations are descriptive statistics\index{statistics} for the entire raster dataset such as the minimum or maximum -- we already discussed those in Section \@ref(summarizing-raster-objects). Aside from that, global operations are also useful for the computation of distance and weight rasters. In the first case, one can calculate the distance from each cell to a specific target cell. For example, one might want to compute the distance to the nearest coast (see also `terra::distance()`). We might also want to consider topography, for example to avoid the crossing mountain ranges on the way to the coast. This can be done by weighting distance by elevation so that each additional altitudinal meter 'prolongs' the Euclidean distance (in Exercises E8 and E9 at the end of this chapter you will do exactly that). Visibility and viewshed computations also belong to the family of global operations (in the Exercises of Chapter \@ref(gis), you will compute a viewshed raster). ### Map algebra counterparts in vector processing Many map algebra operations have a counterpart in vector processing [@liu_essential_2009]. Computing a distance raster (global operation) while only considering a maximum distance (logical focal operation) is the equivalent to a vector buffer operation (Section \@ref(clipping)). Reclassifying raster data (either local or zonal function depending on the input) is equivalent to dissolving vector data (Section \@ref(spatial-joining)). Overlaying two rasters (local operation), where one contains `NULL` or `NA` values representing a mask, is similar to vector clipping (Section \@ref(clipping)). Quite similar to spatial clipping is intersecting two layers (Section \@ref(spatial-subsetting)). The difference is that these two layers (vector or raster) simply share an overlapping area (see Figure \@ref(fig:venn-clip) for an example). However, be careful with the wording. Sometimes the same words have slightly different meanings for raster and vector data models. While aggregating polygon geometries means dissolving boundaries, for raster data geometries it means increasing cell sizes and thereby reducing spatial resolution. Zonal operations dissolve the cells of one raster in accordance with the zones (categories) of another raster dataset using an aggregating function. ### Merging rasters \index{raster!merge} Suppose we would like to compute the NDVI (see Section \@ref(local-operations)), and additionally we want to compute terrain attributes from elevation data for observations within a study area. Such computations rely on remotely sensed information. The corresponding imagery is often divided into scenes covering a specific spatial extent, and frequently, a study area covers more than one scene. Then, we would need to merge the scenes covered by our study area. In the easiest case, we can just merge these scenes, that is put them side by side. This is possible, for example, with digital elevation data. In the following code chunk, we first download the Shuttle Radar Topography Mission (SRTM) elevation data for Austria and Switzerland (for the country codes, see the **geodata** function `country_codes()`). In a second step, we merge the two rasters into one. ```{r 04-spatial-operations-44, eval = FALSE} aut = geodata::elevation_30s(country = "AUT", path = tempdir()) ch = geodata::elevation_30s(country = "CHE", path = tempdir()) aut_ch = merge(aut, ch) ``` **terra**'s `merge()` command combines two images, and in case they overlap, it uses the value of the first raster. The merging approach is of little use when the overlapping values do not correspond to each other. This is frequently the case when you want to combine spectral imagery from scenes that were taken on different dates. The `merge()` command will still work, but you will see a clear border in the resulting image. On the other hand, the `mosaic()` command lets you define a function for the overlapping area. For instance, we could compute the mean value --- this might smooth the clear border in the merged result, but it will most likely not make it disappear. For a more detailed introduction to remote sensing with R, see @wegmann_remote_2016. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_04-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 05-geometry-operations.Rmd ================================================ # Geometry operations {#geometry-operations} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter uses the same packages as Chapter \@ref(spatial-operations) but with the addition of **spDataLarge**, which was installed in Chapter \@ref(spatial-class): ```{r 05-geometry-operations-1, message=FALSE} library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ``` ## Introduction So far the book has explained the structure of geographic datasets (Chapter \@ref(spatial-class)), and how to manipulate them based on their non-geographic attributes (Chapter \@ref(attr)) and spatial relations (Chapter \@ref(spatial-operations)). This chapter focuses on manipulating the geographic elements of spatial objects, for example by creating buffers, simplifying and converting vector geometries, and aggregating and resampling raster data. After reading it --- and attempting the Exercises at the end --- you should understand and have control over the geometry column in `sf` objects and the extent and geographic location of pixels represented in rasters in relation to other geographic objects. Section \@ref(geo-vec) covers transforming vector geometries with 'unary' and 'binary' operations. Unary operations work on a single geometry in isolation, including simplification (of lines and polygons), the creation of buffers and centroids, and shifting/scaling/rotating single geometries using 'affine transformations' (Sections \@ref(simplification) to \@ref(affine-transformations)). Binary transformations modify one geometry based on the shape of another, including clipping and geometry unions\index{vector!union}, covered in Sections \@ref(clipping) to \@ref(geometry-unions). Type transformations (from a polygon to a line, for example) are demonstrated in Section \@ref(type-trans). Section \@ref(geo-ras) covers geometric transformations on raster objects. This involves changing the size and number of the underlying pixels, and assigning them new values. It teaches how to change the resolution (also called raster aggregation and disaggregation), the extent and the origin of a raster. These operations are especially useful if one would like to align raster datasets from diverse sources. Aligned raster objects share a one-to-one correspondence between pixels, allowing them to be processed using map algebra operations, described in Section \@ref(map-algebra). The interaction between raster and vector objects is covered in Chapter \@ref(raster-vector). It presents how raster values can be 'masked' and 'extracted' by vector geometries. Importantly it also shows how to 'polygonize' rasters and 'rasterize' vector datasets, making the two data models more interchangeable. ## Geometric operations on vector data {#geo-vec} This section is about operations that in some way change the geometry of vector (`sf`) objects. It is more advanced than the spatial data operations presented in the previous chapter (Section \@ref(spatial-vec)), because here we drill down into the geometry: the functions discussed in this section work on objects of class `sfc` in addition to objects of class `sf`. ### Simplification \index{vector!simplification} Simplification is a process for generalization of vector objects (lines and polygons) usually for use in smaller scale maps. Another reason for simplifying objects is to reduce the amount of memory, disk space and network bandwidth they consume: it may be wise to simplify complex geometries before publishing them as interactive maps. The **sf** package provides `st_simplify()`, which uses the Douglas-Peucker algorithm to reduce the vertex count. `st_simplify()` uses the `dTolerance` to control the level of generalization in map units [see @douglas_algorithms_1973 for details]. Figure \@ref(fig:seine-simp) illustrates simplification of a `LINESTRING` geometry representing the River Seine and tributaries. The simplified geometry was created by the following command: ```{r 05-geometry-operations-2} seine_simp = st_simplify(seine, dTolerance = 2000) # 2000 m ``` ```{r seine-simp, echo=FALSE, fig.cap="Comparison of the original and simplified geometry of the seine object.", warning=FALSE, fig.scap="Simplification in action.", message=FALSE, fig.asp=0.5} library(tmap) p_simp1 = tm_shape(seine) + tm_lines() + tm_title("Original data") p_simp2 = tm_shape(seine_simp) + tm_lines() + tm_title("st_simplify") tmap_arrange(p_simp1, p_simp2, ncol = 2) ``` The resulting `seine_simp` object is a copy of the original `seine` but with fewer vertices. This is apparent, with the result being visually simpler (Figure \@ref(fig:seine-simp), right) and consuming less memory than the original object, as verified below: ```{r 05-geometry-operations-3} object.size(seine) object.size(seine_simp) ``` \index{vector!simplification} Simplification is also applicable for polygons. This is illustrated using `us_states`, representing the contiguous United States. ```{r 05-geometry-operations-5} us_states_simp1 = st_simplify(us_states, dTolerance = 100000) # 100 km ``` A limitation with `st_simplify()` is that it simplifies objects on a per-geometry basis. This means the 'topology' is lost, resulting in overlapping and 'holey' areal units illustrated in Figure \@ref(fig:us-simp) (right top panel). `ms_simplify()` from **rmapshaper** provides an alternative. By default it uses the Visvalingam algorithm, which overcomes some limitations of the Douglas-Peucker algorithm [@visvalingam_line_1993]. The following code chunk uses this function to simplify `us_states`. The result has only 1% of the vertices of the input (set using the argument `keep`), but its number of objects remains intact because we set `keep_shapes = TRUE`:^[ Simplification of multipolygon objects can remove small internal polygons, even if the `keep_shapes` argument is set to TRUE. To prevent this, you need to set `explode = TRUE`. This option converts all multipolygons into separate polygons before its simplification. ] ```{r 05-geometry-operations-6, warning=FALSE, message=FALSE} # proportion of points to retain (0-1; default 0.05) us_states_simp2 = rmapshaper::ms_simplify(us_states, keep = 0.01, keep_shapes = TRUE) ``` \index{vector!simplification} An alternative process to simplification is smoothing the boundaries of polygon and linestring geometries, which is implemented in the **smoothr** package\index{smoothr (package)}. Smoothing interpolates the edges of geometries and does not necessarily lead to fewer vertices, but can be especially useful when working with geometries that arise from spatially vectorizing a raster (a topic covered in Chapter \@ref(raster-vector)). **smoothr** implements three techniques for smoothing: a Gaussian kernel regression, Chaikin's corner cutting algorithm, and spline interpolation, which are all described in the package vignette and [website](https://strimas.com/smoothr/). Note that similar to `st_simplify()`, the smoothing algorithms don't preserve 'topology'. The workhorse function of **smoothr** is `smooth()`, where the `method` argument specifies what smoothing technique to use. Below is an example of using Gaussian kernel regression to smooth the borders of US states by using `method=ksmooth`. The `smoothness` argument controls the bandwidth of the Gaussian that is used to smooth the geometry and has a default value of 1. ```{r 05-geometry-operations-6b, warning=FALSE} us_states_simp3 = smoothr::smooth(us_states, method = "ksmooth", smoothness = 6) ``` Finally, the visual comparison of the original dataset with the simplified and smoothed versions is shown in Figure \@ref(fig:us-simp). Differences can be observed between the outputs of the Douglas-Peucker (`st_simplify`), Visvalingam (`ms_simplify`), and Gaussian kernel regression (`smooth(method=ksmooth`) algorithms. ```{r us-simp, echo=FALSE, fig.cap="Polygon simplification in action, comparing the original geometry of the contiguous United States with simplified versions, generated with functions from sf (top-right), rmapshaper (bottom-left), and smoothr (bottom-right) packages.", warning=FALSE, fig.scap="Polygon simplification in action."} library(tmap) p_ussimp1 = tm_shape(us_states) + tm_polygons() + tm_title("Original data") p_ussimp2 = tm_shape(us_states_simp1) + tm_polygons() + tm_title("st_simplify") p_ussimp3 = tm_shape(us_states_simp2) + tm_polygons() + tm_title("ms_simplify") p_ussimp4 = tm_shape(us_states_simp3) + tm_polygons() + tm_title('smooth(method = "ksmooth")') tmap_arrange(p_ussimp1, p_ussimp2, p_ussimp3, p_ussimp4, ncol = 2, nrow = 2) ``` ### Centroids \index{vector!centroids} Centroid operations identify the center of geographic objects. Like statistical measures of central tendency (including mean and median definitions of 'average'), there are many ways to define the geographic center of an object. All of them create single point representations of more complex vector objects. The most commonly used centroid operation is the *geographic centroid*. This type of centroid operation (often referred to as 'the centroid') represents the center of mass in a spatial object (think of balancing a plate on your finger). Geographic centroids have many uses, for example to create a simple point representation of complex geometries, or to estimate distances between polygons. They can be calculated with the **sf** function `st_centroid()` as demonstrated in the code below, which generates the geographic centroids of regions in New Zealand and tributaries to the River Seine, illustrated with black points in Figure \@ref(fig:centr). ```{r 05-geometry-operations-7, warning=FALSE} nz_centroid = st_centroid(nz) seine_centroid = st_centroid(seine) ``` Sometimes the geographic centroid falls outside the boundaries of their parent objects (think of a doughnut). In such cases *point on surface* operations can be used to guarantee the point will be in the parent object (e.g., for labeling irregular multipolygon objects such as island states), as illustrated by the red points in Figure \@ref(fig:centr). Notice that these red points always lie on their parent objects. They were created with `st_point_on_surface()` as follows:^[ A description of how `st_point_on_surface()` works is provided at https://gis.stackexchange.com/a/76563/20955. ] ```{r 05-geometry-operations-8, warning=FALSE} nz_pos = st_point_on_surface(nz) seine_pos = st_point_on_surface(seine) ``` ```{r centr, warning=FALSE, echo=FALSE, fig.cap="Centroids (black points) and 'points on surface' (red points) of New Zealand's regions (left) and the Seine (right) datasets.", fig.scap="Centroid vs. point on surface operations."} p_centr1 = tm_shape(nz) + tm_polygons(col = "gray80", fill = "gray90") + tm_shape(nz_centroid) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_shape(nz_pos) + tm_symbols(shape = 1, col = "red", size = 0.5) + tm_layout(scale = 1.6) p_centr2 = tm_shape(seine) + tm_lines(col = "gray80") + tm_shape(seine_centroid) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_shape(seine_pos) + tm_symbols(shape = 1, col = "red", size = 0.5) + tm_add_legend(type = "symbols", shape = 1, col = c("black", "red"), labels = c("Centroid", "Point on surface")) + tm_layout(scale = 1.6) tmap_arrange(p_centr1, p_centr2, ncol = 2) ``` Other types of centroids exist, including the *Chebyshev center* and the *visual center*. We will not explore these here, but it is possible to calculate them using R, as we'll see in Chapter \@ref(algorithms). ### Buffers \index{vector!buffers} Buffers are polygons representing the area within a given distance of a geometric feature: regardless of whether the input is a point, line or polygon, the output is a polygon. Unlike simplification (which is often used for visualization and reducing file size), buffering tends to be used for geographic data analysis. How many points are within a given distance of this line? Which demographic groups are within travel distance of this new shop? These kinds of questions can be answered and visualized by creating buffers around the geographic entities of interest. Figure \@ref(fig:buffs) illustrates buffers of different sizes (5 and 50 km) surrounding the River Seine and tributaries. These buffers were created with commands below, which show that the command `st_buffer()` requires at least two arguments: an input geometry and a distance, provided in the units of the CRS (in this case meters). ```{r 05-geometry-operations-9} seine_buff_5km = st_buffer(seine, dist = 5000) seine_buff_50km = st_buffer(seine, dist = 50000) ``` ```{r buffs, echo=FALSE, fig.cap="Buffers around the Seine dataset of 5 km (left) and 50 km (right). Note the colors, which reflect the fact that one buffer is created per geometry feature.", fig.show='hold', out.width="100%", fig.scap="Buffers around the seine dataset."} p_buffs1 = tm_shape(seine_buff_5km) + tm_polygons(fill = "name") + tm_shape(seine) + tm_lines() + tm_title("5 km buffer") + tm_layout(legend.show = FALSE) p_buffs2 = tm_shape(seine_buff_50km) + tm_polygons(fill = "name") + tm_shape(seine) + tm_lines() + tm_title("50 km buffer") + tm_layout(legend.show = FALSE) tmap_arrange(p_buffs1, p_buffs2, ncol = 2) ``` ```{block2 05-geometry-operations-10, type = "rmdnote"} The `st_buffer()` has a few additional arguments. The most important ones are: - `nQuadSegs` (when the GEOS\index{GEOS} engine is used), which means 'number of segments per quadrant' and is set by default to 30 (meaning circles created by buffers are composed of $4 \times 30 = 120$ lines). Unusual cases where it may be useful include when the memory consumed by the output of a buffer operation is a major concern (in which case it should be reduced) or when very high precision is needed (in which case it should be increased) - `max_cells` (when the S2\index{S2} engine is used), the larger the value, the more smooth the buffer will be, but the calculations will take longer - `endCapStyle` and `joinStyle` (when the GEOS engine is used), which control the appearance of the buffer's edges - `singleSide` (when the GEOS engine is used), which controls whether the buffer is created on one or both sides of the input geometry ``` ```{r nQuadSegs, eval=FALSE, echo=FALSE} # Demonstrate nQuadSegs seine_buff_simple = st_buffer(seine, dist = 50000, nQuadSegs = 3) plot(seine_buff_simple, key.pos = NULL, main = "50 km buffer") plot(seine, key.pos = NULL, lwd = 3, pal = rainbow, add = TRUE) seine_points = st_cast(seine[1, ], "POINT") buff_single = st_buffer(seine_points[1, ], 50000, 2) buff_points = st_cast(buff_single, "POINT") plot(st_geometry(buff_single), add = TRUE) ``` ```{r buffargs, eval=FALSE, echo=FALSE} seine_wgs = st_transform(seine, "EPSG:4326") plot(st_buffer(seine, 5000)) plot(st_buffer(seine_wgs, 5000)) plot(st_buffer(seine, 5000, nQuadSegs = 1)) plot(st_buffer(seine_wgs, 5000, nQuadSegs = 1)) # no effect plot(st_buffer(seine, 5000, max_cells = 10)) # no effect plot(st_buffer(seine_wgs, 5000, max_cells = 100)) plot(st_buffer(seine, 5000, endCapStyle = "FLAT", joinStyle = "MITRE")) plot(st_buffer(seine_wgs, 5000, endCapStyle = "FLAT", joinStyle = "MITRE")) # no effect plot(st_buffer(seine, 5000, singleSide = TRUE)) plot(st_buffer(seine_wgs, 5000, singleSide = TRUE)) # no effect ``` ### Affine transformations \index{vector!affine transformation} Affine transformation is any transformation that preserves lines and parallelism. However, angles or length are not necessarily preserved. Affine transformations include, among others, shifting (translation), scaling and rotation. Additionally, it is possible to use any combination of these. Affine transformations are an essential part of geocomputation. For example, shifting is needed for labels placement, scaling is used in non-contiguous area cartograms (see Section \@ref(other-mapping-packages)), and many affine transformations are applied when reprojecting or improving the geometry that was created based on a distorted or wrongly projected map. The **sf** package implements affine transformation for objects of classes `sfg` and `sfc`. ```{r 05-geometry-operations-11} nz_sfc = st_geometry(nz) ``` Shifting moves every point by the same distance in map units. It could be done by adding a numerical vector to a vector object. For example, the code below shifts all y-coordinates by 100,000 meters to the north, but leaves the x-coordinates untouched (Figure \@ref(fig:affine-trans), left panel). ```{r 05-geometry-operations-12} nz_shift = nz_sfc + c(0, 100000) ``` Scaling enlarges or shrinks objects by a factor. It can be applied either globally or locally. Global scaling increases or decreases all coordinates values in relation to the origin coordinates, while keeping all geometries topological relations intact. It can be done by subtraction or multiplication of a `sfg` or `sfc` object. ```{r 05-geometry-operations-13, echo=FALSE,eval=FALSE} nz_scale0 = nz_sfc * 0.5 ``` Local scaling treats geometries independently and requires points around which geometries are going to be scaled, e.g., centroids. In the example below, each geometry is shrunk by a factor of two around the centroids (Figure \@ref(fig:affine-trans), middle panel). To achieve that, each object is firstly shifted in a way that its center has coordinates of `0, 0` (`(nz_sfc - nz_centroid_sfc)`). Next, the sizes of the geometries are reduced by half (`* 0.5`). Finally, each object's centroid is moved back to the input data coordinates (`+ nz_centroid_sfc`). ```{r 05-geometry-operations-14} nz_centroid_sfc = st_centroid(nz_sfc) nz_scale = (nz_sfc - nz_centroid_sfc) * 0.5 + nz_centroid_sfc ``` Rotation of two-dimensional coordinates requires a rotation matrix: $$ R = \begin{bmatrix} \cos \theta & -\sin \theta \\ \sin \theta & \cos \theta \\ \end{bmatrix} $$ It rotates points in a clockwise direction. The rotation matrix can be implemented in R as: ```{r 05-geometry-operations-15} rotation = function(a){ r = a * pi / 180 #degrees to radians matrix(c(cos(r), sin(r), -sin(r), cos(r)), nrow = 2, ncol = 2) } ``` The `rotation` function accepts one argument `a` --- a rotation angle in degrees. Rotation could be done around selected points, such as centroids (Figure \@ref(fig:affine-trans), right panel). See `vignette("sf3")` for more examples. ```{r 05-geometry-operations-16} nz_rotate = (nz_sfc - nz_centroid_sfc) * rotation(30) + nz_centroid_sfc ``` ```{r affine-trans, echo=FALSE, fig.cap="Affine transformations: shift, scale and rotate.", warning=FALSE, eval=TRUE, fig.scap="Affine transformations."} st_crs(nz_shift) = st_crs(nz_sfc) st_crs(nz_scale) = st_crs(nz_sfc) st_crs(nz_rotate) = st_crs(nz_sfc) p_at1 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_shift) + tm_polygons(fill = "red") + tm_title("Shift") p_at2 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_scale) + tm_polygons(fill = "red") + tm_title("Scale") p_at3 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_rotate) + tm_polygons(fill = "red") + tm_title("Rotate") tmap_arrange(p_at1, p_at2, p_at3, ncol = 3) ``` ```{r 05-geometry-operations-17, echo=FALSE,eval=FALSE} nz_scale_rotate = (nz_sfc - nz_centroid_sfc) * 0.25 * rotation(90) + nz_centroid_sfc ``` ```{r 05-geometry-operations-18, echo=FALSE,eval=FALSE} shearing = function(hx, hy){ matrix(c(1, hy, hx, 1), nrow = 2, ncol = 2) } nz_shear = (nz_sfc - nz_centroid_sfc) * shearing(1.1, 0) + nz_centroid_sfc ``` ```{r 05-geometry-operations-19, echo=FALSE,eval=FALSE} plot(nz_sfc) plot(nz_shear, add = TRUE, col = "red") ``` Finally, the newly created geometries can replace the old ones with the `st_set_geometry()` function: ```{r 05-geometry-operations-20} nz_scale_sf = st_set_geometry(nz, nz_scale) ``` ### Clipping {#clipping} \index{vector!clipping} \index{spatial!subsetting} Spatial clipping is a form of spatial subsetting that involves changes to the `geometry` columns of at least some of the affected features. Clipping can only apply to features more complex than points: lines, polygons and their 'multi' equivalents. To illustrate the concept, we will start with a simple example: two overlapping circles with a center point one unit away from each other and a radius of one (Figure \@ref(fig:points)). ```{r points, fig.cap="Overlapping circles.", fig.asp=0.4, crop = TRUE, echo=-1} op = par(mar = rep(0, 4)) b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles plot(b, border = "gray") text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y"), cex = 3) # add text ``` ```{r, echo=FALSE} par(op) ``` Imagine you want to select not one circle or the other, but the space covered by both `x` *and* `y`. This can be done using the function `st_intersection()`\index{vector!intersection}, illustrated using objects named `x` and `y` which represent the left- and right-hand circles (Figure \@ref(fig:circle-intersection)). ```{r circle-intersection, fig.cap="Overlapping circles with a gray color indicating intersection between them.", fig.asp=0.4, fig.scap="Overlapping circles showing intersection types.", crop = TRUE, echo=-1} op = par(mar = rep(0, 4)) x = b[1] y = b[2] x_and_y = st_intersection(x, y) plot(b, border = "gray") plot(x_and_y, col = "lightgray", border = "gray", add = TRUE) # intersecting area ``` ```{r, echo=FALSE} par(op) ``` The subsequent code chunk demonstrates how this works for all combinations of the 'Venn' diagram representing `x` and `y`, inspired by [Figure 5.1](https://r4ds.had.co.nz/transform.html#logical-operators) of the book *R for Data Science* [@grolemund_r_2016]. ```{r venn-clip, echo=FALSE, fig.cap="Spatial equivalents of logical operators.", warning=FALSE} source("code/05-venn-clip.R") ``` ### Subsetting and clipping \index{vector!clipping} \index{spatial!subsetting} Clipping objects can change their geometry, but it can also subset objects, returning only features that intersect (or partly intersect) with a clipping/subsetting object. To illustrate this point, we will subset points that cover the bounding box of the circles `x` and `y` in Figure \@ref(fig:venn-clip). Some points will be inside just one circle, some will be inside both and some will be inside neither. `st_sample()` is used below to generate a *simple random* distribution of points within the extent of circles `x` and `y`, resulting in output illustrated in Figure \@ref(fig:venn-subset), raising the question: how to subset the points to only return the point that intersects with *both* `x` and `y`? ```{r venn-subset, fig.cap="Randomly distributed points within the bounding box enclosing circles x and y. The point that intersects with both objects x and y is highlighted.", fig.height=6, fig.width=9, fig.scap="Randomly distributed points within the bounding box. Note that only one point intersects with both x and y, highlighted with a red circle.", echo=FALSE, echo=-1} op = par(mar = rep(0, 4)) bb = st_bbox(st_union(x, y)) box = st_as_sfc(bb) set.seed(2024) p = st_sample(x = box, size = 10) p_xy1 = p[x_and_y] plot(box, border = "gray", lty = 2) plot(x, add = TRUE, border = "gray") plot(y, add = TRUE, border = "gray") plot(p, add = TRUE, cex = 3.5) plot(p_xy1, cex = 5, col = "red", add = TRUE) text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y"), cex = 3) ``` ```{r, echo=FALSE} par(op) ``` ```{r venn-subset-to-show, eval=FALSE} bb = st_bbox(st_union(x, y)) box = st_as_sfc(bb) set.seed(2024) p = st_sample(x = box, size = 10) x_and_y = st_intersection(x, y) ``` The code chunk below demonstrates three ways to achieve the same result. We can use the intersection\index{vector!intersection} of `x` and `y` (represented by `x_and_y` in the previous code chunk) as a subsetting object directly, as shown in the first line in the code chunk below. We can also find the *intersection* between the input points represented by `p` and the subsetting/clipping object `x_and_y`, as demonstrated in the second line in the code chunk below. This second approach will return features that partly intersect with `x_and_y` but with modified geometries for spatially extensive features that cross the border of the subsetting object. The third approach is to create a subsetting object using the binary spatial predicate `st_intersects()`, introduced in the previous chapter. The results are identical (except superficial differences in attribute names), but the implementation differs substantially: ```{r 05-geometry-operations-21} # way #1 p_xy1 = p[x_and_y] # way #2 p_xy2 = st_intersection(p, x_and_y) # way #3 sel_p_xy = st_intersects(p, x, sparse = FALSE)[, 1] & st_intersects(p, y, sparse = FALSE)[, 1] p_xy3 = p[sel_p_xy] ``` ```{r 05-geometry-operations-22, echo=FALSE, eval=FALSE} # test if objects are identical: identical(p_xy1, p_xy2) identical(p_xy2, p_xy3) identical(p_xy1, p_xy3) waldo::compare(p_xy1, p_xy2) # the same except attribute names waldo::compare(p_xy2, p_xy3) # the same except attribute names # An alternative way to sample from the bb bb = st_bbox(st_union(x, y)) pmulti = st_multipoint(pmat) box = st_convex_hull(pmulti) ``` Although the example above is rather contrived and provided for educational rather than applied purposes, and we encourage the reader to reproduce the results to deepen understanding for handling geographic vector objects in R, it raises an important question: which implementation to use? Generally, more concise implementations should be favored, meaning the first approach above. We will return to the question of choosing between different implementations of the same technique or algorithm in Chapter \@ref(algorithms). ### Geometry unions \index{vector!union} \index{aggregation!spatial} As we saw in Section \@ref(vector-attribute-aggregation), spatial aggregation can silently dissolve the geometries of touching polygons in the same group. This is demonstrated in the code chunk below in which 48 US states and the District of Columbia (`us_states`) are aggregated into four regions using base and **dplyr**\index{dplyr (package)} functions (see results in Figure \@ref(fig:us-regions)): ```{r 05-geometry-operations-23} regions = aggregate(x = us_states[, "total_pop_15"], by = list(us_states$REGION), FUN = sum, na.rm = TRUE) regions2 = us_states |> group_by(REGION) |> summarize(pop = sum(total_pop_15, na.rm = TRUE)) ``` ```{r 05-geometry-operations-24, echo=FALSE} # st_join(buff, africa[, "pop"]) |> # summarize(pop = sum(pop, na.rm = TRUE)) # summarize(africa[buff, "pop"], pop = sum(pop, na.rm = TRUE)) ``` ```{r us-regions, fig.cap="Spatial aggregation on contiguous polygons, illustrated by aggregating the population of US states into regions, with population represented by color. Note the operation automatically dissolves boundaries between states.", echo=FALSE, warning=FALSE, out.width="100%", fig.scap="Spatial aggregation on contiguous polygons."} source("code/05-us-regions.R", print.eval = TRUE) ``` What is going on in terms of the geometries? Behind the scenes, both `aggregate()` and `summarize()` combine the geometries and dissolve the boundaries between them using `st_union()`. This is demonstrated in the code chunk below which creates a united western US: ```{r 05-geometry-operations-25} us_west = us_states[us_states$REGION == "West", ] us_west_union = st_union(us_west) ``` The function can take two geometries and unite them, as demonstrated in the code chunk below which creates a united western block incorporating Texas (challenge: reproduce and plot the result): ```{r 05-geometry-operations-26, message=FALSE} texas = us_states[us_states$NAME == "Texas", ] texas_union = st_union(us_west_union, texas) ``` ```{r 05-geometry-operations-27, echo=FALSE, eval=FALSE} plot(texas_union) # aim: experiment with st_union us_south2 = st_union(us_west[1, ], us_west[6, ]) plot(us_southhwest) ``` ### Type transformations {#type-trans} \index{vector!geometry casting} Geometry casting is a powerful operation that enables transformation of the geometry type. It is implemented in the `st_cast()` function from the **sf** package. Importantly, `st_cast()` behaves differently on single simple feature geometry (`sfg`) objects, simple feature geometry column (`sfc`) and simple features objects. Let's create a multipoint to illustrate how geometry casting works on simple feature geometry (`sfg`) objects: ```{r 05-geometry-operations-28} multipoint = st_multipoint(matrix(c(1, 3, 5, 1, 3, 1), ncol = 2)) ``` In this case, `st_cast()` can be useful to transform the new object into a linestring or a polygon (Figure \@ref(fig:single-cast)). ```{r 05-geometry-operations-29} linestring = st_cast(multipoint, "LINESTRING") polyg = st_cast(multipoint, "POLYGON") ``` ```{r single-cast, echo = FALSE, fig.cap="Examples of a linestring and a polygon casted from a multipoint geometry.", warning=FALSE, fig.asp=0.3, fig.scap="Examples of casting operations."} p_sc1 = tm_shape(st_sfc(multipoint, crs = "+proj=merc")) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_title("MULTIPOINT") + tm_layout(inner.margins = c(0.15, 0.05, 0.15, 0.05)) p_sc2 = tm_shape(st_sfc(linestring, crs = "+proj=merc")) + tm_lines() + tm_title("LINESTRING") + tm_layout(inner.margins = c(0.15, 0.05, 0.15, 0.05)) p_sc3 = tm_shape(st_sfc(polyg, crs = "+proj=merc")) + tm_polygons(col = "black") + tm_title("POLYGON") + tm_layout(inner.margins = c(0.15, 0.05, 0.15, 0.05)) tmap_arrange(p_sc1, p_sc2, p_sc3, ncol = 3) ``` Conversion from multipoint to linestring is a common operation that creates a line object from ordered point observations, such as GPS measurements or geotagged media. This, in turn, allows us to perform spatial operations such as the calculation of the length of the path traveled. Conversion from multipoint or linestring to polygon is often used to calculate an area, for example from the set of GPS measurements taken around a lake or from the corners of a building lot. The transformation process can be also reversed using `st_cast()`: ```{r 05-geometry-operations-30} multipoint_2 = st_cast(linestring, "MULTIPOINT") multipoint_3 = st_cast(polyg, "MULTIPOINT") all.equal(multipoint, multipoint_2) all.equal(multipoint, multipoint_3) ``` ```{block2 05-geometry-operations-31, type='rmdnote'} For single simple feature geometries (`sfg`), `st_cast()` also provides geometry casting from non-multi-types to multi-types (e.g., `POINT` to `MULTIPOINT`) and from multi-types to non-multi-types. However, when casting from multi-types to non-multi-types, only the first element of the old object would remain in the output object. ``` ```{r 05-geometry-operations-32, include=FALSE} cast_all = function(xg) { lapply(c("MULTIPOLYGON", "MULTILINESTRING", "MULTIPOINT", "POLYGON", "LINESTRING", "POINT"), function(x) st_cast(xg, x)) } t = cast_all(multipoint) t2 = cast_all(polyg) ``` Geometry casting of simple features geometry column (`sfc`) and simple features objects works the same as for `sfg` in most of the cases. One important difference is the conversion between multi-types to non-multi-types. As a result of this process, multi-objects of `sfc` or `sf` are split into many non-multi-objects. Let's say we have the following `sf` objects: - `POI` - POINT type (with one point by definition) - `MPOI` - MULTIPOINT type with four points - `LIN` - LINESTRING type with one linestring containing five points - `MLIN` - MULTILINESTRING type with two linestrings (one with five points and one with two points) - `POL` - POLYGON type with one polygon (created using five points) - `MPOL` - MULTIPOLYGON type consisting of two polygons (both consisting of five points) - `GC` - GEOMETRYCOLLECTION type with two geometries, a MULTIPOINT (four points) and a LINESTRING (five points) Table \@ref(tab:sfs-st-cast) shows possible geometry type transformations on the simple feature objects listed above. Single simple feature geometries (represented by the first column in the table) can be transformed into multiple geometry types, represented by the columns in Table \@ref(tab:sfs-st-cast). Some transformations are not possible: you cannot convert a single point into a multilinestring or a polygon, for example, explaining why the cells `[1, 4:5]` in the table contain NA. Some transformations split single features input into multiple sub-features, 'expanding' `sf` objects (adding new rows with duplicate attribute values). When a multipoint geometry consisting of five pairs of coordinates is tranformed into a 'POINT' geometry, for example, the output will contain five features. ```{r sfs-st-cast, echo=FALSE} sfs_st_cast = read.csv("extdata/sfs-st-cast.csv") abbreviate_geomtypes = function(geomtypes) { geomtypes_new = gsub(pattern = "POINT", replacement = "POI", x = geomtypes) geomtypes_new = gsub(pattern = "POLYGON", replacement = "POL", x = geomtypes_new) geomtypes_new = gsub(pattern = "LINESTRING", replacement = "LIN", x = geomtypes_new) geomtypes_new = gsub(pattern = "MULTI", replacement = "M", x = geomtypes_new) geomtypes_new = gsub(pattern = "GEOMETRYCOLLECTION", replacement = "GC", x = geomtypes_new) geomtypes_new } sfs_st_cast$input_geom = abbreviate_geomtypes(sfs_st_cast$input_geom) names(sfs_st_cast) = abbreviate_geomtypes(names(sfs_st_cast)) names(sfs_st_cast)[1] = "" knitr::kable(sfs_st_cast, caption = paste("Geometry casting on simple feature geometries", "(see Section 2.1) with input type by row and", "output type by column."), caption.short = "Geometry casting on simple feature geometries.", booktabs = TRUE) |> kableExtra::add_footnote("Values in parentheses represent the number of features; NA means the operation is not available", notation = "none") ``` Let's try to apply geometry type transformations on a new object, `multilinestring_sf`, as an example (on the left in Figure \@ref(fig:line-cast)): ```{r 05-geometry-operations-33} multilinestring_list = list(matrix(c(1, 4, 5, 3), ncol = 2), matrix(c(4, 4, 4, 1), ncol = 2), matrix(c(2, 4, 2, 2), ncol = 2)) multilinestring = st_multilinestring(multilinestring_list) multilinestring_sf = st_sf(geom = st_sfc(multilinestring)) multilinestring_sf ``` You can imagine it as a road or river network. The new object has only one row that defines all the lines. This restricts the number of operations that can be done, for example it prevents adding names to each line segment or calculating lengths of single lines. The `st_cast()` function can be used in this situation, as it separates one mutlilinestring into three linestrings. ```{r 05-geometry-operations-34} linestring_sf2 = st_cast(multilinestring_sf, "LINESTRING") linestring_sf2 ``` ```{r line-cast, echo=FALSE, fig.cap="Examples of type casting between multilinestring (left) and linestring (right).", warning=FALSE, fig.scap="Examples of type casting.", message=FALSE} p_lc1 = tm_shape(multilinestring_sf) + tm_lines(lwd = 3) + tm_title("MULTILINESTRING") linestring_sf2$name = c("Riddle Rd", "Marshall Ave", "Foulke St") p_lc2 = tm_shape(linestring_sf2) + tm_lines(lwd = 3, col = "name", col.scale = tm_scale(values = "Set2")) + tm_title("LINESTRING") + tm_layout(legend.show = FALSE) tmap_arrange(p_lc1, p_lc2, ncol = 2) ``` The newly created object allows for attributes creation (see more in Section \@ref(vec-attr-creation)) and length measurements: ```{r 05-geometry-operations-35} linestring_sf2$name = c("Riddle Rd", "Marshall Ave", "Foulke St") linestring_sf2$length = st_length(linestring_sf2) linestring_sf2 ``` ## Geometric operations on raster data {#geo-ras} \index{raster!manipulation} Geometric raster operations include the shifting, flipping, mirroring, scaling, rotation or warping of images. These operations are necessary for a variety of applications including georeferencing, used to allow images to be overlaid on an accurate map with a known CRS [@liu_essential_2009]. A variety of georeferencing techniques exist, including: - Georectification based on known [ground control points](https://www.qgistutorials.com/en/docs/3/georeferencing_basics.html) - Orthorectification, which also accounts for local topography - Image [registration](https://en.wikipedia.org/wiki/Image_registration) is used to combine images of the same thing, but shot from different sensors by aligning one image with another (in terms of coordinate system and resolution) R is rather unsuitable for the first two points since these often require manual intervention which is why they are usually done with the help of dedicated GIS software (see also Chapter \@ref(gis)). On the other hand, aligning several images is possible in R and this section shows among others how to do so. This often includes changing the extent, the resolution and the origin of an image. A matching projection is of course also required but is already covered in Section \@ref(reproj-ras). In any case, there are other reasons to perform a geometric operation on a single raster image. For instance, in Chapter \@ref(location) we define metropolitan areas in Germany as 20 km^2^ pixels with more than 500,000 inhabitants. The original inhabitant raster, however, has a resolution of 1 km^2^ which is why we will decrease (aggregate) the resolution by a factor of 20 (see Section \@ref(define-metropolitan-areas)). Another reason for aggregating a raster is simply to decrease run-time or save disk space. Of course, this approach is only recommended if the task at hand allows a coarser resolution of raster data. ### Geometric intersections \index{raster!intersection} In Section \@ref(spatial-raster-subsetting) we have shown how to extract values from a raster overlaid by other spatial objects. To retrieve a spatial output, we can use almost the same subsetting syntax. The only difference is that we have to make clear that we would like to keep the matrix structure by setting the `drop` argument to `FALSE`. This will return a raster object containing the cells whose midpoints overlap with `clip`. ```{r 05-geometry-operations-36} elev = rast(system.file("raster/elev.tif", package = "spData")) clip = rast(xmin = 0.9, xmax = 1.8, ymin = -0.45, ymax = 0.45, resolution = 0.3, vals = rep(1, 9)) elev[clip, drop = FALSE] ``` For the same operation, we can also use the `intersect()` and `crop()` command. ### Extent and origin \index{raster!merging} When merging or performing map algebra on rasters, their resolution, projection, origin and/or extent have to match. Otherwise, how should we add the values of one raster with a resolution of 0.2 decimal degrees to a second raster with a resolution of 1 decimal degree? The same problem arises when we would like to merge satellite imagery from different sensors with different projections and resolutions. We can deal with such mismatches by aligning the rasters. In the simplest case, two images only differ with regard to their extent. The following code adds one row and two columns to each side of the raster while setting all new values to `NA` (Figure \@ref(fig:extend-example)). ```{r extend-example0} elev = rast(system.file("raster/elev.tif", package = "spData")) elev_2 = extend(elev, c(1, 2)) ``` ```{r extend-example, fig.cap = "Original raster (left) and the same raster (right) extended by one row on the top and bottom and two columns on the left and right.", fig.scap="Extending rasters.", echo=FALSE, fig.asp=0.5} source("code/05-extend-example.R", print.eval = TRUE) ``` Performing an algebraic operation on two objects with differing extents in R, the **terra** package returns an error. ```{r 05-geometry-operations-37, error=TRUE} elev_3 = elev + elev_2 ``` However, we can align the extent of two rasters with `extend()`. Instead of telling the function how many rows or columns should be added (as done before), we allow it to figure it out by using another raster object. Here, we extend the `elev` object to the extent of `elev_2`. The values of the newly added rows and columns are set to `NA`. ```{r 05-geometry-operations-38} elev_4 = extend(elev, elev_2) ``` \index{raster!origin} The origin of a raster is the cell corner closest to the coordinates (0, 0). The `origin()` function returns the coordinates of the origin. In the example below a cell corner exists with coordinates (0, 0), but that is not necessarily the case. ```{r 05-geometry-operations-39} origin(elev_4) ``` If two rasters have different origins, their cells do not overlap completely which would make map algebra impossible. To change the origin, use `origin()`.^[ If the origins of two raster datasets are just marginally apart, it sometimes is sufficient to simply increase the `tolerance` argument of `terra::terraOptions()`. ] Figure \@ref(fig:origin-example) reveals the effect of changing the origin in this way. ```{r} # change the origin origin(elev_4) = c(0.25, 0.25) ``` ```{r origin-example, fig.cap="Rasters with identical values but different origins.", echo=FALSE} elev_poly = st_as_sf(as.polygons(elev, dissolve = FALSE)) elev4_poly = st_as_sf(as.polygons(elev_4, dissolve = FALSE)) tm_shape(elev4_poly) + tm_grid() + tm_polygons(fill = "elev", lwd = 0.5) + tm_shape(elev_poly) + tm_polygons(fill = "elev") + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = c(0.1, 0.12, 0, 0)) ``` Note that changing the resolution (next section) frequently also changes the origin. ### Aggregation and disaggregation \index{raster!aggregation} \index{raster!disaggregation} Raster datasets can also differ with regard to their resolution. To match resolutions, one can either decrease (`aggregate()`) or increase (`disagg()`) the resolution of one raster.^[ Here we refer to spatial resolution. In remote sensing the spectral (spectral bands), temporal (observations through time of the same area) and radiometric (color depth) resolution are also important. Check out the `tapp()` example in the documentation for getting an idea on how to do temporal raster aggregation. ] As an example, we here change the spatial resolution of `dem` (found in the **spDataLarge** package) by a factor of 5 (Figure \@ref(fig:aggregate-example)). Additionally, the output cell value is going to correspond to the mean of the input cells (note that one could use other functions as well, such as `median()`, `sum()`, etc.): ```{r 05-geometry-operations-40} dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) dem_agg = aggregate(dem, fact = 5, fun = mean) ``` ```{r aggregate-example, fig.cap = "Original raster (left) and aggregated raster (right).", echo=FALSE} p_ar1 = tm_shape(dem) + tm_raster(col.scale = tm_scale_continuous()) + tm_title("A. Original") + tm_layout(frame = FALSE, legend.show = FALSE) p_ar2 = tm_shape(dem_agg) + tm_raster(col.scale = tm_scale_continuous()) + tm_title("B. Aggregated") + tm_layout(frame = FALSE, legend.show = FALSE) tmap_arrange(p_ar1, p_ar2, ncol = 2) ``` Table \@ref(tab:aggdf) compares the properties of the original and aggregated raster. Notice that "decreasing" the resolution with `aggregate()` increases the resolution from $(30.85, 30.85)$ to $(154.25, 154.25)$. This is done by decreasing the number of rows (`nrow`) and columns (`ncol`) (see Section \@ref(raster-data)). The extent was slightly adjusted to accommodate the new grid size. ```{r aggdf} #| echo: false agg_df = data.frame( Object = c("dem", "dem_agg"), Resolution = c("(30.85, 30.85)", "(154.25, 154.25)"), Dimensions = c("117 * 117", "24 * 24"), Extent = c("794599.1, 798208.6, 8931775, 8935384", "794599.1, 798301.1, 8931682, 8935384") ) knitr::kable(agg_df, caption = "Properties of the original and aggregated raster.", booktabs = TRUE) ``` \index{raster!disaggregation} The `disagg()` function increases the resolution of raster objects. It comes with two methods on how to compute the values of the newly created cells: the default method (`method = "near"`) simply gives all output cells the value of the input cell, and hence duplicates values, which translates into a 'blocky' output. The `bilinear` method uses the four nearest pixel centers of the input image (orange colored points in Figure \@ref(fig:bilinear)) to compute an average weighted by distance (arrows in Figure \@ref(fig:bilinear)). The value of the output cell is represented by a square in the upper left corner in Figure \@ref(fig:bilinear). ```{r 05-geometry-operations-41} dem_disagg = disagg(dem_agg, fact = 5, method = "bilinear") identical(dem, dem_disagg) ``` ```{r bilinear, echo = FALSE, fig.width=8, fig.height=10, fig.cap="The distance-weighted average of the four closest input cells determine the output when using the bilinear method for disaggregation.", fig.scap="Bilinear disaggregation in action.", warning=FALSE} source("code/05-bilinear.R", print.eval = TRUE) # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146619205-3c0c2e3f-9e8b-4fda-b014-9c342a4befbb.png") ``` ```{r} #| echo: false #| eval: false identical(dem, dem_disagg) compareGeom(dem, dem_disagg) all.equal(dem, dem_disagg) ``` Comparing the values of `dem` and `dem_disagg` tells us that they are not identical (you can also use `compareGeom()` or `all.equal()`). However, this was hardly to be expected, since disaggregating is a simple interpolation technique. It is important to keep in mind that disaggregating results in a finer resolution; the corresponding values, however, are only as accurate as their lower resolution source. ### Resampling \index{raster!resampling} The above methods of aggregation and disaggregation are only suitable when we want to change the resolution of our raster by the aggregation/disaggregation factor. However, what to do when we have two or more rasters with different resolutions and origins? This is the role of resampling -- a process of computing values for new pixel locations. In short, this process takes the values of our original raster and recalculates new values for a target raster with custom resolution and origin (Figure \@ref(fig:resampl0)). ```{r resampl0, echo=FALSE, fig.cap="Resampling of an original (input) raster into a target raster with custom resolution and origin.", fig.asp = 0.4} target_rast = rast(xmin = 794650, xmax = 798250, ymin = 8931750, ymax = 8935350, resolution = 150, crs = "EPSG:32717") target_rast_p = st_as_sf(as.polygons(target_rast)) dem_resampl1 = resample(dem, target_rast, method = "near") tm1 = tm_shape(dem) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("Original raster") + tm_layout(frame = FALSE, legend.show = FALSE) tm2 = tm_shape(dem) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("Target raster") + tm_layout(frame = FALSE, legend.show = FALSE) + tm_shape(target_rast_p, is.main = TRUE) + tm_borders() tm3 = tm_shape(dem) + tm_raster(col_alpha = 0) + tm_shape(dem_resampl1) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("Resampled raster") + tm_layout(frame = FALSE, legend.show = FALSE) tmap_arrange(tm1, tm2, tm3, nrow = 1) ``` \index{raster!resampling} There are several methods for estimating values for a raster with different resolutions/origins, as shown in Figure \@ref(fig:resampl). The main resampling methods include: - Nearest neighbor: assigns the value of the nearest cell of the original raster to the cell of the target one. This is a fast simple technique that is usually suitable for resampling categorical rasters. - Bilinear interpolation: assigns a weighted average of the four nearest cells from the original raster to the cell of the target one (Figure \@ref(fig:bilinear)). This is the fastest method that is appropriate for continuous rasters. - Cubic interpolation: uses values of the 16 nearest cells of the original raster to determine the output cell value, applying third-order polynomial functions. Used for continuous rasters and results in a smoother surface compared to bilinear interpolation but is computationally more demanding. - Cubic spline interpolation: also uses values of the 16 nearest cells of the original raster to determine the output cell value, but applies cubic splines (piece-wise third-order polynomial functions). Used for continuous rasters. - Lanczos windowed sinc resampling: uses values of the 36 nearest cells of the original raster to determine the output cell value. Used for continuous rasters.^[ More detailed explanation of this method can be found at https://gis.stackexchange.com/a/14361/20955. ] The above explanation highlights that only *nearest neighbor* resampling is suitable for categorical rasters, while all methods can be used (with different outcomes) for continuous rasters. Please note also that the methods gain both in complexity and processing time from top to bottom. Moreover, resampling can be done using statistics (e.g., minimum or mode) of all contributing cells. To apply resampling, the **terra** package provides a `resample()` function. It accepts an input raster (`x`), a raster with target spatial properties (`y`), and a resampling method (`method`). We need a raster with target spatial properties to see how the `resample()` function works. For this example, we create `target_rast`, but you would often use an already existing raster object. ```{r 05-geometry-operations-42} target_rast = rast(xmin = 794650, xmax = 798250, ymin = 8931750, ymax = 8935350, resolution = 300, crs = "EPSG:32717") ``` Next, we need to provide our two raster objects as the first two arguments and one of the resampling methods described above. ```{r 05-geometry-operations-42b} dem_resampl = resample(dem, y = target_rast, method = "bilinear") ``` Figure \@ref(fig:resampl) shows a comparison of different resampling methods on the `dem` object. ```{r resampl, echo=FALSE, fig.cap="Visual comparison of the original raster and five different resampling methods."} dem_resampl1 = resample(dem, target_rast, method = "near") dem_resampl2 = resample(dem, target_rast, method = "bilinear") dem_resampl3 = resample(dem, target_rast, method = "cubic") dem_resampl4 = resample(dem, target_rast, method = "cubicspline") dem_resampl5 = resample(dem, target_rast, method = "lanczos") library(tmap) tm1 = tm_shape(dem) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("Original raster") + tm_layout(frame = FALSE, legend.show = FALSE) tm2 = tm_shape(dem_resampl1) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("near") + tm_layout(frame = FALSE, legend.show = FALSE) tm3 = tm_shape(dem_resampl2) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("bilinear") + tm_layout(frame = FALSE, legend.show = FALSE) tm4 = tm_shape(dem_resampl3) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("cubic") + tm_layout(frame = FALSE, legend.show = FALSE) tm5 = tm_shape(dem_resampl4) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("cubicspline") + tm_layout(frame = FALSE, legend.show = FALSE) tm6 = tm_shape(dem_resampl5) + tm_raster(col.scale = tm_scale(breaks = seq(200, 1100, by = 150))) + tm_title("lanczos") + tm_layout(frame = FALSE, legend.show = FALSE) tmap_arrange(tm1, tm2, tm3, tm4, tm5, tm6) ``` The `resample()` function also has some additional resampling methods, including `sum`, `min`, `q1`, `med`, `q3`, `max`, `average`, `mode`, and `rms`. All of them calculate a given statistic based on the values of all non-NA contributing grid cells. For example, `sum` is useful when each raster cell represents a spatially extensive variable (e.g., number of people). As an effect of using `sum`, the resampled raster should have the same total number of people as the original one. \index{raster!resampling} As you will see in Section \@ref(reproj-ras), raster reprojection is a special case of resampling when our target raster has a different CRS than the original raster. \index{GDAL} ```{block2 type='rmdnote'} Most geometry operations in **terra** are user-friendly, rather fast, and work on large raster objects. However, there could be some cases when **terra** is not the most performant either for extensive rasters or many raster files, and some alternatives should be considered. The most established alternatives come with the GDAL library. It contains several utility functions, including: - `gdalinfo` - lists various information about a raster file, including its resolution, CRS, bounding box, and more - `gdal_translate` - converts raster data between different file formats - `gdal_rasterize` - converts vector data into raster files - `gdalwarp` - allows for raster mosaicing, resampling, cropping, and reprojecting All of the above functions are written in C++, but can be called in R using `sf::gdal_utils()`, the **gdalUtilities** package, or via system commands (see Section \@ref(gdal)). Importantly, all of these functions expect a raster file path as an input and often return their output as a raster file (for example, `gdalUtilities::gdal_translate("my_file.tif", "new_file.tif", t_srs = "EPSG:4326")`). This is very different from the usual **terra** approach, which expects `SpatRaster` objects as inputs. ``` ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_05-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 06-raster-vector.Rmd ================================================ # Raster-vector interactions {#raster-vector} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {.unnumbered} - This chapter requires the following packages: ```{r 06-raster-vector-1, message=FALSE} library(sf) library(terra) library(dplyr) ``` ## Introduction \index{raster-vector interactions} This chapter focuses on interactions between raster and vector geographic data models, introduced in Chapter \@ref(spatial-class). It includes several main techniques: raster cropping and masking using vector objects (Section \@ref(raster-cropping)), extracting raster values using different types of vector data (Section \@ref(raster-extraction)), and raster-vector conversion (Sections \@ref(rasterization) and \@ref(spatial-vectorization)). The above concepts are demonstrated using data from previous chapters to understand their potential real-world applications. ## Raster cropping \index{raster!cropping} Many geographic data projects involve integrating data from many different sources, such as remote sensing images (rasters) and administrative boundaries (vectors). Often the extent of input raster datasets is larger than the area of interest. In this case, raster **cropping** and **masking** are useful for unifying the spatial extent of input data. Both operations reduce object memory use and associated computational resources for subsequent analysis steps and may be a necessary preprocessing step when creating attractive maps involving raster data. We will use two objects to illustrate raster cropping: - A `SpatRaster` object `srtm` representing elevation (meters above sea level) in southwestern Utah - A vector (`sf`) object `zion` representing Zion National Park Both target and cropping objects must have the same projection. The following code chunk therefore not only reads the datasets from the **spDataLarge** package installed in Chapter \@ref(spatial-class), but it also 'reprojects' `zion` (a topic covered in Chapter \@ref(reproj-geo-data)): ```{r 06-raster-vector-2, results='hide'} srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) zion = st_transform(zion, st_crs(srtm)) ``` We use `crop()` from the **terra** package to crop the `srtm` raster. The function reduces the rectangular extent of the object passed to its first argument based on the extent of the object passed to its second argument. This functionality is demonstrated in the command below, which generates Figure \@ref(fig:cropmask)(B). ```{r 06-raster-vector-3 } srtm_cropped = crop(srtm, zion) ``` \index{raster!masking} Related to `crop()` is the **terra** function `mask()`, which sets values outside of the bounds of the object passed to its second argument to `NA`. The following command therefore masks every cell outside of Zion National Park boundaries (Figure \@ref(fig:cropmask)(C)). ```{r 06-raster-vector-4 } srtm_masked = mask(srtm, zion) ``` Importantly, we want to use both `crop()` and `mask()` together in most cases. This combination of functions would (a) limit the raster's extent to our area of interest and then (b) replace all of the values outside of the area to NA.^[These two operations can be combined into a single step with `terra::crop(srtm, zion, mask = TRUE)`, but we prefer to keep them separate for clarity.] ```{r 06-raster-vector-5} srtm_cropped = crop(srtm, zion) srtm_final = mask(srtm_cropped, zion) ``` Changing the settings of `mask()` yields different results. Setting `inverse = TRUE` will mask everything *inside* the bounds of the park (see `?mask` for details) (Figure \@ref(fig:cropmask)(D)), while setting `updatevalue = 0` will set all pixels outside the national park to 0. ```{r 06-raster-vector-6 } srtm_inv_masked = mask(srtm, zion, inverse = TRUE) ``` ```{r cropmask, echo = FALSE, fig.cap="Raster cropping and raster masking.", fig.asp=0.36, fig.width = 10, warning=FALSE} #| message: FALSE #| results: hide library(tmap) library(rcartocolor) terrain_colors = carto_pal(7, "Geyser") pz1 = tm_shape(srtm) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors)) + tm_shape(zion) + tm_borders(lwd = 2) + tm_title("A. Original") + tm_layout(legend.show = FALSE) pz2 = tm_shape(srtm_cropped) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors)) + tm_shape(zion) + tm_borders(lwd = 2) + tm_title("B. Crop") + tm_layout(legend.show = FALSE) pz3 = tm_shape(srtm_masked) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors)) + tm_shape(zion) + tm_borders(lwd = 2) + tm_title("C. Mask") + tm_layout(legend.show = FALSE) pz4 = tm_shape(srtm_inv_masked) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors)) + tm_shape(zion) + tm_borders(lwd = 2) + tm_title("D. Inverse mask") + tm_layout(legend.show = FALSE) tmap_arrange(pz1, pz2, pz3, pz4, ncol = 4, asp = NA) ``` ## Raster extraction \index{raster!extraction} Raster extraction is the process of identifying and returning the values associated with a 'target' raster at specific locations, based on a (typically vector) geographic 'selector' object. The results depend on the type of selector used (points, lines or polygons) and arguments passed to the `terra::extract()` function. The reverse of raster extraction --- assigning raster cell values based on vector objects --- is rasterization, described in Section \@ref(rasterization). \index{raster!extraction points} The basic example is of extracting the value of a raster cell at specific **points**. For this purpose, we will use `zion_points`, which contain a sample of 30 locations within Zion National Park (Figure \@ref(fig:pointextr)). The following command extracts elevation values from `srtm` and creates a data frame with points' IDs (one value per vector's row) and related `srtm` values for each point. Now, we can add the resulting object to our `zion_points` dataset with the `cbind()` function: ```{r 06-raster-vector-8 } data("zion_points", package = "spDataLarge") elevation = terra::extract(srtm, zion_points) zion_points = cbind(zion_points, elevation) ``` ```{r 06-raster-vector-9, echo=FALSE, eval=FALSE} library(dplyr) zion_points2 = zion_points zion_points2$a = 1 zion_points2 = zion_points2 |> group_by(a) |> summarise() elevation = terra::extract(srtm, zion_points2) zion_points = cbind(zion_points, elevation) ``` ```{r pointextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Locations of points used for raster extraction.", fig.asp=0.67} source("code/06-pointextr.R", print.eval = TRUE) ``` \index{raster!extraction lines} Raster extraction also works with **line** selectors. Then, it extracts one value for each raster cell touched by a line. However, the line extraction approach is not recommended to obtain values along the transects, as it is hard to get the correct distance between each pair of extracted raster values. In this case, a better approach is to split the line into many points and then extract the values for these points. To demonstrate this, the code below creates `zion_transect`, a straight line going from northwest to southeast of Zion National Park, illustrated in Figure \@ref(fig:lineextr)(A) (see Section \@ref(vector-data) for a recap on the vector data model): ```{r 06-raster-vector-11} zion_transect = cbind(c(-113.2, -112.9), c(37.45, 37.2)) |> st_linestring() |> st_sfc(crs = crs(srtm)) |> st_sf(geometry = _) ``` ```{r 06-raster-vector-12, eval=FALSE, echo=FALSE} # Aim: show how extraction works with non-straight lines by # using this alternative line object: zion_transect2 = cbind(c(-113.2, -112.9, -113.2), c(36.45, 37.2, 37.5)) |> st_linestring() |> st_sfc(crs = crs(srtm)) |> st_sf() zion_transect = rbind(zion_transect, zion_transect2) ``` The utility of extracting heights from a linear selector is illustrated by imagining that you are planning a hike. The method demonstrated below provides an 'elevation profile' of the route (the line does not need to be straight), useful for estimating how long it will take due to long climbs. The first step is to add a unique `id` for each transect. Next, with the `st_segmentize()` function we can add points along our line(s) with a provided density (`dfMaxLength`) and convert them into points with `st_cast()`. ```{r 06-raster-vector-13, warning=FALSE} zion_transect$id = 1:nrow(zion_transect) zion_transect = st_segmentize(zion_transect, dfMaxLength = 250) zion_transect = st_cast(zion_transect, "POINT") ``` Now, we have a large set of points, and we want to derive a distance between the first point in our transects and each of the subsequent points. In this case, we only have one transect, but the code, in principle, should work on any number of transects: ```{r 06-raster-vector-14} zion_transect = zion_transect |> group_by(id) |> mutate(dist = st_distance(geometry)[, 1]) ``` Finally, we can extract elevation values for each point in our transects and combine this information with our main object. ```{r 06-raster-vector-15} zion_elev = terra::extract(srtm, zion_transect) zion_transect = cbind(zion_transect, zion_elev) ``` The resulting `zion_transect` can be used to create elevation profiles, as illustrated in Figure \@ref(fig:lineextr)(B). ```{r lineextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Location of a line used for (A) raster extraction and (B) the elevation along this line.", fig.scap="Line-based raster extraction."} library(tmap) library(grid) library(ggplot2) zion_transect_line = cbind(c(-113.2, -112.9), c(37.45, 37.2)) |> st_linestring() |> st_sfc(crs = crs(srtm)) |> st_sf() zion_transect_points = st_cast(zion_transect, "POINT")[c(1, nrow(zion_transect)), ] zion_transect_points$name = c("start", "end") rast_poly_line = tm_shape(srtm) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors), col.legend = tm_legend("Elevation (m)")) + tm_shape(zion) + tm_borders(lwd = 2) + tm_shape(zion_transect_line) + tm_lines(col = "black", lwd = 4) + tm_shape(zion_transect_points) + tm_text("name", text.scale = tm_scale(bg.color = "white", bg.alpha = 0.75, auto.placement = TRUE)) + tm_layout(legend.frame = TRUE, legend.position = c("RIGHT", "TOP"), legend.bg.color = "white") plot_transect = ggplot(zion_transect, aes(as.numeric(dist), srtm)) + geom_line() + labs(x = "Distance (m)", y = "Elevation (m a.s.l.)") + theme_bw() + # facet_wrap(~id) + theme(plot.margin = unit(c(5.5, 15.5, 5.5, 5.5), "pt")) grid.newpage() pushViewport(viewport(layout = grid.layout(2, 2, heights = unit(c(0.25, 5), "null")))) grid.text("A. Line extraction", vp = viewport(layout.pos.row = 1, layout.pos.col = 1)) grid.text("B. Elevation along the line", vp = viewport(layout.pos.row = 1, layout.pos.col = 2)) print(rast_poly_line, vp = viewport(layout.pos.row = 2, layout.pos.col = 1)) print(plot_transect, vp = viewport(layout.pos.row = 2, layout.pos.col = 2)) ``` \index{raster!extraction polygons} The final type of geographic vector object for raster extraction is **polygons**. Like lines, polygons tend to return many raster values per polygon. This is demonstrated in the command below, which results in a data frame with column names `ID` (the row number of the polygon) and `srtm` (associated elevation values): ```{r 06-raster-vector-17, eval=FALSE, echo=FALSE} # aim: create zion_many to test multi-polygon results n = 3 zion_many = st_sample(x = zion, size = n) |> st_buffer(dist = 500) |> st_sf(data.frame(v = 1:n), geometry = _) plot(zion_many) # for continuous data: zion_srtm_values1 = terra::extract(x = srtm, y = zion_many, fun = min) zion_srtm_values2 = terra::extract(x = srtm, y = zion_many, fun = mean) zion_srtm_values3 = terra::extract(x = srtm, y = zion_many, fun = max) # for categories nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) zion_many2 = st_transform(zion_many, st_crs(nlcd)) zion_nlcd = terra::extract(nlcd, zion_many2) count(zion_nlcd, levels) ``` ```{r 06-raster-vector-18 } zion_srtm_values = terra::extract(x = srtm, y = zion) ``` Such results can be used to generate summary statistics for raster values per polygon, for example to characterize a single region or to compare many regions. This is shown in the code below, which creates the object `zion_srtm_df` containing summary statistics for elevation values in Zion National Park (see Figure \@ref(fig:polyextr)(A)): ```{r 06-raster-vector-19 } group_by(zion_srtm_values, ID) |> summarize(across(srtm, list(min = min, mean = mean, max = max))) ``` The preceding code chunk used **dplyr**\index{dplyr (package)} to provide summary statistics for cell values per polygon ID, as described in Chapter \@ref(attr). The results provide useful summaries, for example that the maximum height in the park is around 2,661 meters above sea level (other summary statistics, such as standard deviation, can also be calculated in this way). Because there is only one polygon in the example, a data frame with a single row is returned; however, the method works when multiple selector polygons are used. A similar approach works for counting occurrences of categorical raster values within polygons. This is illustrated with a land cover dataset (`nlcd`) from the **spDataLarge** package in Figure \@ref(fig:polyextr)(B), and demonstrated in the code below: ```{r 06-raster-vector-20, warning=FALSE, message=FALSE} nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) zion2 = st_transform(zion, st_crs(nlcd)) zion_nlcd = terra::extract(nlcd, zion2) zion_nlcd |> group_by(ID, levels) |> count() ``` ```{r polyextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Area used for (A) continuous and (B) categorical raster extraction.", fig.width=7.5} rast_poly_srtm = tm_shape(srtm) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors), col.legend = tm_legend("Elevation (m)")) + tm_shape(zion) + tm_polygons(lwd = 2, fill_alpha = 0.3) + tm_title("A. Continuous data extraction") + tm_layout(legend.frame = TRUE, legend.position = c("LEFT", "BOTTOM"), legend.bg.color = "white") rast_poly_nlcd = tm_shape(nlcd) + tm_raster(col.scale = tm_scale_categorical(levels.drop = TRUE), col.legend = tm_legend(title = "Land cover")) + # tm_raster(drop.levels = TRUE, title = "Land cover", legend.show = TRUE) + tm_shape(zion) + tm_polygons(lwd = 2, fill_alpha = 0.3) + tm_title("B. Categorical data extraction") + tm_layout(legend.frame = TRUE, legend.position = c("LEFT", "BOTTOM"), legend.bg.color = "white") tmap_arrange(rast_poly_srtm, rast_poly_nlcd, ncol = 2) ``` \index{raster!extraction fractions} Although the **terra** package offers rapid extraction of raster values within polygons, `extract()` can still be a bottleneck when processing large polygon datasets. The **exactextractr** package offers a [significantly faster alternative](https://github.com/geocompx/geocompr/issues/813) for extracting pixel values through the `exact_extract()` function. The `exact_extract()` function also computes, by default, the fraction of each raster cell overlapped by the polygon, which is more precise (see note below for details). ```{block2 06-raster-vector-22, type='rmdnote'} Polygons usually have irregular shapes, and, therefore, a polygon can overlap only some parts of a raster's cells. To get more detailed results, the `terra::extract()` function has an argument called `exact`. With `exact = TRUE`, we get one more column `fraction` in the output data frame, which represents a fraction of each cell that is covered by the polygon. This could be useful to calculate, for example, a weighted mean for continuous rasters or more precise coverage for categorical rasters. By default, it is `FALSE`, as this operation requires more computations. The `exactextractr::exact_extract()` function always computes the coverage fraction of the polygon in each cell. ``` ```{r 06-raster-vector-23, include=FALSE} zion_srtm_values = terra::extract(x = srtm, y = zion, exact = FALSE) ``` ## Rasterization {#rasterization} \index{rasterization} Rasterization is the conversion of vector objects into their representation in raster objects. Usually, the output raster is then used for quantitative analysis (e.g., analysis of terrain) or modeling. As we saw in Chapter \@ref(spatial-class), the raster data model has some characteristics that make it conducive to certain methods. Furthermore, the process of rasterization can help simplify datasets because the resulting values all have the same spatial resolution: rasterization can be seen as a special type of geographic data aggregation. The **terra** package contains the function `rasterize()` for doing this work. Its first two arguments are, `x`, vector object to be rasterized and, `y`, a 'template raster' object defining the extent, resolution and CRS of the output. The geographic resolution of the input raster has a major impact on the results: if it is too low (cell size is too large), the result may miss the full geographic variability of the vector data; if it is too high, computational times may be excessive. There are no simple rules to follow when deciding an appropriate geographic resolution, which is heavily dependent on the intended use of the results. Often the target resolution is imposed on the user, for example when the output of rasterization needs to be aligned to some other existing raster. \index{rasterization!points} To demonstrate rasterization in action, we will use a template raster that has the same extent and CRS as the input vector data `cycle_hire_osm_projected` (a dataset on cycle hire points in London is illustrated in Figure \@ref(fig:vector-rasterization1)(A)) and spatial resolution of 1000 meters: ```{r 06-raster-vector-24 } cycle_hire_osm = spData::cycle_hire_osm cycle_hire_osm_projected = st_transform(cycle_hire_osm, "EPSG:27700") raster_template = rast(ext(cycle_hire_osm_projected), resolution = 1000, crs = crs(cycle_hire_osm_projected)) ``` Rasterization is a very flexible operation: the results depend not only on the nature of the template raster, but also on the type of input vector (e.g., points, polygons) and a variety of arguments taken by the `rasterize()` function. To illustrate this flexibility, we will try three different approaches to rasterization. First, we create a raster representing the presence or absence of cycle hire points (known as presence/absence rasters). In this case `rasterize()` requires no argument in addition to `x` and `y`, the aforementioned vector and raster objects (results illustrated Figure \@ref(fig:vector-rasterization1)(B)). ```{r 06-raster-vector-25 } ch_raster1 = rasterize(cycle_hire_osm_projected, raster_template) ``` The `fun` argument specifies summary statistics used to convert multiple observations in close proximity into associate cells in the raster object. By default `fun = "last"` is used, but other options such as `fun = "length"` can be used, in this case to count the number of cycle hire points in each grid cell (the results of this operation are illustrated in Figure \@ref(fig:vector-rasterization1)(C)). ```{r 06-raster-vector-26} ch_raster2 = rasterize(cycle_hire_osm_projected, raster_template, fun = "length") ``` The new output, `ch_raster2`, shows the number of cycle hire points in each grid cell. The cycle hire locations have different numbers of bicycles described by the `capacity` variable, raising the question, what's the capacity in each grid cell? To calculate that we must `sum` the field (`"capacity"`), resulting in output illustrated in Figure \@ref(fig:vector-rasterization1)(D), calculated with the following command (other summary functions such as `mean` could be used). ```{r 06-raster-vector-27 } ch_raster3 = rasterize(cycle_hire_osm_projected, raster_template, field = "capacity", fun = sum, na.rm = TRUE) ``` ```{r vector-rasterization1, echo=FALSE, fig.cap="Examples of point rasterization.", warning=FALSE, message=FALSE} source("code/06-vector-rasterization1.R", print.eval = TRUE) ``` \index{rasterization!lines} \index{rasterization!polygons} Another dataset based on California's polygons and borders (created below) illustrates rasterization of lines. After casting the polygon objects into a multilinestring, a template raster is created with a resolution of a 0.5 degree: ```{r 06-raster-vector-29 } california = dplyr::filter(us_states, NAME == "California") california_borders = st_cast(california, "MULTILINESTRING") raster_template2 = rast(ext(california), resolution = 0.5, crs = st_crs(california)$wkt) ``` When considering line or polygon rasterization, one useful additional argument is `touches`. By default it is `FALSE`, but when changed to `TRUE`, all cells that are touched by a line or polygon border get a value. Line rasterization with `touches = TRUE` is demonstrated in the code below (Figure \@ref(fig:vector-rasterization2)(A)). ```{r 06-raster-vector-30} california_raster1 = rasterize(california_borders, raster_template2, touches = TRUE) ``` Compare it to a polygon rasterization, with `touches = FALSE` by default, which selects only raster cells whose centroids are inside the selector polygon, as illustrated in Figure \@ref(fig:vector-rasterization2)(B). ```{r 06-raster-vector-31} california_raster2 = rasterize(california, raster_template2) ``` ```{r vector-rasterization2, echo=FALSE, fig.cap="Examples of line and polygon rasterizations.", warning=FALSE, message=FALSE} source("code/06-vector-rasterization2.R", print.eval = TRUE) ``` ## Spatial vectorization \index{spatial vectorization} Spatial vectorization is the counterpart of rasterization (Section \@ref(rasterization)), but in the opposite direction. It involves converting spatially continuous raster data into spatially discrete vector data such as points, lines or polygons. ```{block2 06-raster-vector-33, type="rmdnote"} Be careful with the wording! In R, vectorization usually refers to the possibility of replacing `for`-loops and alike by doing things like `1:10 / 2` (see also @wickham_advanced_2019). ``` \index{spatial vectorization!points} The simplest form of vectorization is to convert the centroids of raster cells into points. `as.points()` does exactly this for all non-`NA` raster grid cells (Figure \@ref(fig:raster-vectorization1)). Note, here we also used `st_as_sf()` to convert the resulting object to the `sf` class. ```{r 06-raster-vector-34 } elev = rast(system.file("raster/elev.tif", package = "spData")) elev_point = as.points(elev) |> st_as_sf() ``` ```{r raster-vectorization1, echo=FALSE, fig.cap="Raster and point representation of the elev object.", warning=FALSE} source("code/06-raster-vectorization1.R", print.eval = TRUE) ``` \index{spatial vectorization!contours} Another common type of spatial vectorization is the creation of contour lines representing lines of continuous height or temperatures (isotherms), for example. We will use a real-world digital elevation model (DEM) because the artificial raster `elev` produces parallel lines (task for the reader: verify this and explain why this happens). Contour lines can be created with the **terra** function `as.contour()`, which is itself a wrapper around the built-in R function `filled.contour()`, as demonstrated below (not shown): ```{r 06-raster-vector-36, eval=FALSE} dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) cl = as.contour(dem) |> st_as_sf() plot(dem, axes = FALSE) plot(cl, add = TRUE) ``` Contours can also be added to existing plots with functions such as `contour()`, `rasterVis::contourplot()`. As illustrated in Figure \@ref(fig:contour-tmap), isolines can be labeled. \index{hillshade} ```{r contour-tmap, echo=FALSE, message=FALSE, fig.cap="Digital elevation model with hillshading, showing the southern flank of Mt. Mongón overlaid with contour lines.", fig.scap="DEM with hillshading.", warning=FALSE, fig.asp=0.56, fig.width=3.5} # hs = shade(slope = terrain(dem, "slope", unit = "radians"), # aspect = terrain(dem, "aspect", unit = "radians")) # plot(hs, col = gray(0:100 / 100), legend = FALSE) # # overlay with DEM # plot(dem, col = terrain.colors(25), alpha = 0.5, legend = FALSE, add = TRUE) # # add contour lines # contour(dem, col = "white", add = TRUE) knitr::include_graphics("images/06-contour-tmap.png") ``` \index{spatial vectorization!polygons} The final type of vectorization involves conversion of rasters to polygons. This can be done with `terra::as.polygons()`, which converts each raster cell into a polygon consisting of five coordinates, all of which are stored in memory (explaining why rasters are often fast compared with vectors!). This is illustrated below by converting the `grain` object into polygons and subsequently dissolving borders between polygons with the same attribute values (also see the `dissolve` argument in `as.polygons()`). ```{r 06-raster-vector-39 } grain = rast(system.file("raster/grain.tif", package = "spData")) grain_poly = as.polygons(grain) |> st_as_sf() ``` ```{r 06-raster-vector-40, echo=FALSE, fig.cap="Vectorization of (A) raster into (B) polygons (dissolve = FALSE) and aggregated polygons (dissolve = TRUE).", warning=FALSE, message=FALSE, fig.asp=0.4, fig.scap="Vectorization."} source("code/06-raster-vectorization2.R", print.eval = TRUE) ``` The aggregated polygons of the `grain` dataset have rectilinear boundaries which arise from being defined by connecting rectangular pixels. The **smoothr** package described in Chapter \@ref(geometry-operations) can be used to smooth the edges of the polygons. As smoothing removes sharp edges in the polygon boundaries, the smoothed polygons will not have the same exact spatial coverage as the original pixels. Caution should therefore be taken when using the smoothed polygons for further analysis. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_06-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 07-reproj.Rmd ================================================ # Reprojecting geographic data {#reproj-geo-data} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires the following packages: ```{r 06-reproj-1, message=FALSE, warning=FALSE} library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ``` ## Introduction {#reproj-intro} Section \@ref(crs-intro) introduced coordinate reference systems (CRSs), with a focus on the two major types: *geographic* ('lon/lat', with units in degrees longitude and latitude) and *projected* (typically with units of meters from a datum) coordinate systems. This chapter builds on that knowledge and goes further. It demonstrates how to set and *transform* geographic data from one CRS to another and, furthermore, highlights specific issues that can arise due to ignoring CRSs that you should be aware of, especially if your data is stored with lon/lat coordinates. \index{CRS!geographic} \index{CRS!projected} In many projects there is no need to worry about, let alone convert between, different CRSs. Nonetheless, it is important to know if your data is in a projected or geographic CRS, and the consequences of this for geometry operations. If you know this information, CRSs should *just work* behind the scenes: people often suddenly need to learn about CRSs when things go wrong. Having a clearly defined CRS that all project data is in, and understanding how and why to use different CRSs, can ensure that things don't go wrong. Furthermore, learning about coordinate systems will deepen your knowledge of geographic datasets and how to use them effectively. This chapter teaches the fundamentals of CRSs, demonstrates the consequences of using different CRSs (including what can go wrong), and how to 'reproject' datasets from one coordinate system to another. In the next section, we introduce CRSs in R, followed by Section \@ref(crs-setting) which shows how to get and set CRSs associated with spatial objects. Section \@ref(geom-proj) demonstrates the importance of knowing what CRS your data is in with reference to a worked example of creating buffers. We tackle questions of when to reproject and which CRS to use in Section \@ref(whenproject) and Section \@ref(which-crs), respectively. Finally, we cover reprojecting vector and raster objects in Sections \@ref(reproj-vec-geom) and \@ref(reproj-ras) and modifying map projections in Section \@ref(mapproj). ## Coordinate reference systems {#crs-in-r} \index{CRS!EPSG} \index{CRS!WKT} \index{CRS!proj-string} Most modern geographic tools that require CRS conversions, including core R-spatial packages and desktop GIS software such as QGIS, interface with [PROJ](https://proj.org), an open source C++ library that "transforms coordinates from one coordinate reference system (CRS) to another". CRSs can be described in many ways, including the following: 1. Simple yet potentially ambiguous statements such as "it's in lon/lat coordinates" 2. Formalized yet now outdated 'proj4 strings' (also known as 'proj-string') such as `+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs` 3. With an identifying 'authority:code' text string such as `EPSG:4326` Each refers to the same thing: the 'WGS84' coordinate system that forms the basis of Global Positioning System (GPS) coordinates and many other datasets. But which one is correct? \index{CRS!EPSG} The short answer is that the third way to identify CRSs is preferable: `EPSG:4326` is understood by **sf** (and by extension **stars**) and **terra** packages covered in this book, plus many other software projects for working with geographic data including [QGIS](https://docs.qgis.org/3.16/en/docs/user_manual/working_with_projections/working_with_projections.html) and [PROJ](https://proj.org/development/quickstart.html). `EPSG:4326` is future-proof. Furthermore, although it is machine readable, "EPSG:4326" is short, easy to remember and highly 'findable' online (searching for EPSG:4326 yields a dedicated page on the website [epsg.io](https://epsg.io/4326), for example). The more concise identifier `4326` is understood by **sf**, but **we recommend the more explicit `AUTHORITY:CODE` representation to prevent ambiguity and to provide context**. \index{CRS!WKT} The longer answer is that none of the three descriptions are sufficient, and more detail is needed for unambiguous CRS handling and transformations: due to the complexity of CRSs, it is not possible to capture all relevant information about them in such short text strings. For this reason, the Open Geospatial Consortium (OGC, which also developed the simple features specification that the **sf** package implements) developed an open standard format for describing CRSs that is called WKT (Well-Known Text). This is detailed in a [100+ page document](https://portal.opengeospatial.org/files/18-010r7) that "defines the structure and content of a text string implementation of the abstract model for coordinate reference systems described in ISO 19111:2019" [@opengeospatialconsortium_wellknown_2019]. The WKT representation of the WGS84 CRS, which has the **identifier** `EPSG:4326` is as follows: ```{r} st_crs("EPSG:4326") ``` \index{CRS!SRID} The output of the command shows how the CRS identifier (also known as a Spatial Reference Identifier or [SRID](https://postgis.net/workshops/postgis-intro/projection.html)) works: it is simply a look-up, providing a unique identifier associated with a more complete WKT representation of the CRS. This raises the question: what happens if there is a mismatch between the identifier and the longer WKT representation of a CRS? On this point @opengeospatialconsortium_wellknown_2019 is clear, the verbose WKT representation takes precedence over the [identifier](https://docs.ogc.org/is/18-010r7/18-010r7.html#37): > Should any attributes or values given in the cited identifier be in conflict with attributes or values given explicitly in the WKT description, the WKT values shall prevail. \index{CRS!SRID} The convention of referring to CRSs identifiers in the form `AUTHORITY:CODE`, which is also used by geographic software written in other [languages](https://jorisvandenbossche.github.io/blog/2020/02/11/geopandas-pyproj-crs/), allows a wide range of formally defined coordinate systems to be referred to.^[ Several other ways of referring to unique CRSs can be used, with five identifier types (EPSG code, PostGIS SRID, INTERNAL SRID, proj-string, and WKT strings) accepted by [QGIS](https://docs.qgis.org/3.16/en/docs/pyqgis_developer_cookbook/crs.html?highlight=srid) and other identifier types such as a more verbose variant of the `EPSG:4326` identifier, `urn:ogc:def:crs:EPSG::4326` [@opengeospatialconsortium_wellknown_2019]. ] The most commonly used authority in CRS identifiers is *EPSG*\index{CRS!EPSG}, an acronym for the European Petroleum Survey Group which published a standardized list of CRSs (the EPSG was [taken over](http://wiki.gis.com/wiki/index.php/European_Petroleum_Survey_Group) by the [Geomatics Committee of the International Association of Oil & Gas Producers](https://epsg.org/home.html) in 2005). Other authorities can be used in CRS identifiers. `ESRI:54030`, for example, refers to ESRI's implementation of the Robinson projection, which has the following WKT string (only first eight lines shown): ```{r, out.lines=8} st_crs("ESRI:54030") ``` ```{r, eval=FALSE, echo=FALSE} sf::st_crs("urn:ogc:def:crs:EPSG::4326") ``` \index{CRS!WKT} WKT strings are exhaustive, detailed, and precise, allowing for unambiguous CRSs storage and transformations. They contain all relevant information about any given CRS, including its datum and ellipsoid, prime meridian, projection, and units.^[ Before the emergence of WKT CRS definitions, proj-string was the standard way to specify coordinate operations and store CRSs. These string representations, built on a key=value form (e.g, `+proj=longlat +datum=WGS84 +no_defs`), have already been, or should in the future be, superseded by WKT representations in most cases. ] \index{CRS!proj-string} Recent PROJ versions (6+) still allow use of proj-strings to define coordinate operations, but some proj-string keys (`+nadgrids`, `+towgs84`, `+k`, `+init=epsg:`) are either no longer supported or are discouraged. Additionally, only three datums (i.e., WGS84, NAD83, and NAD27) can be directly set in proj-string. Longer explanations of the evolution of CRS definitions and the PROJ library can be found in @bivand_progress_2021, chapter 2 of @pebesma_spatial_2023, and a [blog post by Floris Vanderhaeghe, available at inbo.github.io/tutorials/tutorials/spatial_crs_coding/](https://inbo.github.io/tutorials/tutorials/spatial_crs_coding/). Also, as outlined in the [PROJ documentation](https://proj.org/development/reference/cpp/cpp_general.html) there are different versions of the WKT CRS format including WKT1 and two variants of WKT2, the latter of which (WKT2, 2018 specification) corresponds to the ISO 19111:2019 [@opengeospatialconsortium_wellknown_2019]. ## Querying and setting coordinate systems {#crs-setting} \index{vector!CRS} Let's look at how CRSs are stored in R spatial objects and how they can be queried and set. First, we will look at getting and setting CRSs in **vector** geographic data objects, starting with the following example: ```{r 02-spatial-data-52, message=FALSE, results='hide'} vector_filepath = system.file("shapes/world.gpkg", package = "spData") new_vector = read_sf(vector_filepath) ``` Our new object, `new_vector`, is a data frame of class `sf` that represents countries worldwide (see the help page `?spData::world` for details). The CRS can be retrieved with the **sf** function `st_crs()`. ```{r 02-spatial-data-53, eval=FALSE} st_crs(new_vector) # get CRS #> Coordinate Reference System: #> User input: WGS 84 #> wkt: #> ... ``` ```{r, echo=FALSE, eval=FALSE} # Aim: capture crs for comparison with updated CRS new_vector_crs = st_crs(new_vector) ``` \index{vector!CRS} The output is a list containing two main components: 1. `User input` (in this case `WGS 84`, a synonym for `EPSG:4326` which in this case was taken from the input file), corresponding to CRS identifiers described above 2. `wkt`, containing the full WKT string with all relevant information about the CRS The `input` element is flexible, and depending on the input file or user input, can contain the `AUTHORITY:CODE` representation (e.g., `EPSG:4326`), the CRS's name (e.g., `WGS 84`), or even the proj-string definition. The `wkt` element stores the WKT representation, which is used when saving the object to a file or doing any coordinate operations. Above, we can see that the `new_vector` object has the WGS84 ellipsoid, uses the Greenwich prime meridian, and the latitude and longitude axis order. In this case, we also have some additional elements, such as `USAGE` explaining the area suitable for the use of this CRS, and `ID` pointing to the CRS's identifier: `EPSG:4326`. \index{vector!CRS} The `st_crs` function also has one helpful feature -- we can retrieve some additional information about the used CRS. For example, try to run: - `st_crs(new_vector)$IsGeographic` to check if the CRS is geographic or not - `st_crs(new_vector)$units_gdal` to find out the CRS units - `st_crs(new_vector)$srid` to extract its 'SRID' identifier (when available) - `st_crs(new_vector)$proj4string` to extract the proj-string representation In cases when a CRS is missing or the wrong CRS is set, the `st_set_crs()` function can be used (in this case the WKT string remains unchanged because the CRS was already set correctly when the file was read-in): ```{r 02-spatial-data-54} new_vector = st_set_crs(new_vector, "EPSG:4326") # set CRS ``` ```{r, echo=FALSE, eval=FALSE} waldo::compare(new_vector_crs, st_crs(new_vector)) # `old$input`: "WGS 84" # `new$input`: "EPSG:4326" ``` \index{raster!CRS} Getting and setting CRSs works in a similar way for raster geographic data objects. The `crs()` function in the `terra` package accesses CRS information from a `SpatRaster` object (note the use of the `cat()` function to print it nicely). ```{r 02-spatial-data-55, out.lines=6} raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") my_rast = rast(raster_filepath) cat(crs(my_rast)) # get CRS ``` The output is the WKT string representation of CRS. The same function, `crs()`, can be also used to set a CRS for raster objects. ```{r 02-spatial-data-56} crs(my_rast) = "EPSG:26912" # set CRS ``` Here, we can use either the identifier (recommended in most cases) or complete WKT string representation. Alternative methods to set `crs` include proj-string strings or CRSs extracted from other existing objects with `crs()`, although these approaches may be less future-proof. Importantly, the `st_crs()` and `crs()` functions do not alter coordinates' values or geometries. Their role is only to set a metadata information about the object CRS. In some cases the CRS of a geographic object is unknown, as is the case in the `london` dataset created in the code chunk below, building on the example of London introduced in Section \@ref(vector-data): ```{r 06-reproj-2} london = data.frame(lon = -0.1, lat = 51.5) |> st_as_sf(coords = c("lon", "lat")) st_is_longlat(london) ``` The output `NA` shows that **sf** does not know what the CRS is and is unwilling to guess (`NA` literally means 'not available'). Unless a CRS is manually specified or is loaded from a source that has CRS metadata, **sf** does not make any explicit assumptions about which coordinate systems, other than to say "I don't know". This behavior makes sense given the diversity of available CRSs but differs from some approaches, such as the GeoJSON file format specification, which makes the simplifying assumption that all coordinates have a lon/lat CRS: `EPSG:4326`. Datasets without a specified CRS can cause problems: all geographic coordinates have a coordinate reference system, and software can only make good decisions around plotting and geometry operations if it knows what type of CRS it is working with. Thus, again, it is important to always check the CRS of a dataset and to set it if it is missing. ```{r 06-reproj-3} london_geo = st_set_crs(london, "EPSG:4326") st_is_longlat(london_geo) ``` ## Geometry operations on projected and unprojected data {#geom-proj} Since **sf** version 1.0.0, R's ability to work with geographic vector datasets that have lon/lat CRSs has improved substantially, thanks to its integration with the S2 *spherical geometry engine* introduced in Section \@ref(s2). As shown in Figure \@ref(fig:s2geos), **sf** uses either GEOS\index{GEOS} or the S2\index{S2} depending on the type of CRS and whether S2 has been disabled (it is enabled by default).^[The `st_area()` function is an exception, as it uses the **lwgeom**'s `st_geod_area()` function to calculate areas for data with geographic CRSs when `sf_use_s2()` is disabled.] GEOS is always used for projected data and data with no CRS; for geographic data, S2 is used by default but can be disabled with `sf::sf_use_s2(FALSE)`. ```{r s2geos, fig.cap="Behavior of the geometry operations in the sf package depending on the input data's CRS.", echo=FALSE} 'digraph G3 { layout=dot rankdir=TB node [shape = rectangle]; rec1 [label = "Spatial data" shape = oval]; rec2 [label = "Geographic CRS\n " shape = cds]; rec3 [label = "Projected CRS\nor CRS is missing" shape = cds] rec4 [label = "S2 enabled\n(default)" shape = diamond] rec5 [label = "S2 disabled\n " shape = diamond] rec6 [label = "sf uses s2library for \ngeometry operations" center = true]; rec7 [label = "sf uses GEOS for \ngeometry operations" center = true]; rec8 [label = "Result" shape = oval weight=100]; rec9 [label = "Result" shape = oval weight=100]; rec1 -> rec2; rec1 -> rec3; rec2 -> rec4; rec2 -> rec5; rec3 -> rec7; rec4 -> rec6; rec5 -> rec7; rec6 -> rec8; rec7 -> rec9; }' -> s2geos # # exported manually; the code below returns a low res version of png # tmp = DiagrammeR::grViz(s2geos) # htmlwidgets::saveWidget(widget = tmp, file = "images/07-s2geos.html") # # tmp # tmp = DiagrammeRsvg::export_svg(tmp) # library(htmltools) # html_print(HTML(tmp)) # tmp = charToRaw(tmp) # # rsvg::rsvg_png(tmp, "images/07-s2geos.png") # webshot::webshot(url = "images/07-s2geos.html", file = "images/07-s2geos.png", vwidth = 800, vheight = 600) # download.file( # "https://user-images.githubusercontent.com/1825120/188572856-7946ae32-98de-444c-9f48-b1d7afcf9345.png", # destfile = "images/07-s2geos.png" # ) # browseURL("images/07-s2geos.png") knitr::include_graphics("images/07-s2geos.png") ``` To demonstrate the importance of CRSs, we will create a buffer of 100 km around the `london` object from the previous section. We will also create a deliberately faulty buffer with a 'distance' of 1 degree, which is roughly equivalent to 100 km (1 degree is about 111 km at the equator). Before diving into the code, it may be worth skipping briefly ahead to peek at Figure \@ref(fig:crs-buf) to get a visual handle on the outputs that you should be able to reproduce by following the code chunks below. The first stage is to create three buffers around the `london` and `london_geo` objects created above with boundary distances of 1 degree and 100 km (or 100,000 m, which can be expressed as `1e5` in scientific notation) from central London: ```{r 06-reproj-4-1} london_buff_no_crs = st_buffer(london, dist = 1) # incorrect: no CRS london_buff_s2 = st_buffer(london_geo, dist = 100000) # silent use of s2 london_buff_s2_100_cells = st_buffer(london_geo, dist = 100000, max_cells = 100) ``` In the first line above, **sf** assumes that the input is projected and generates a result that has a buffer in units of degrees, which is problematic, as we will see. In the second line, **sf** silently uses the spherical geometry engine S2, introduced in Chapter \@ref(spatial-class), to calculate the extent of the buffer using the default value of `max_cells = 1000` --- set to `100` in line three --- the consequences which will become apparent shortly. To highlight the impact of **sf**'s use of the S2\index{S2} geometry engine for unprojected (geographic) coordinate systems, we will temporarily disable it with the command `sf_use_s2()` (which is on, `TRUE`, by default), in the code chunk below. Like `london_buff_no_crs`, the new `london_geo` object is a geographic abomination: it has units of degrees, which makes no sense in the vast majority of cases: ```{r 06-reproj-4-2} sf::sf_use_s2(FALSE) london_buff_lonlat = st_buffer(london_geo, dist = 1) # incorrect result sf::sf_use_s2(TRUE) ``` The warning message above hints at issues with performing planar geometry operations on lon/lat data. When spherical geometry operations are turned off, with the command `sf::sf_use_s2(FALSE)`, buffers (and other geometric operations) may result in worthless outputs because they use units of latitude and longitude, a poor substitute for proper units of distances such as meters. ```{block2 06-reproj-5, type="rmdnote"} The distance between two lines of longitude, called meridians\index{meridians}, is around 111 km at the equator (execute `geosphere::distGeo(c(0, 0), c(1, 0))` to find the precise distance). This shrinks to zero at the poles. At the latitude of London, for example, meridians are less than 70 km apart (challenge: execute code that verifies this). Lines of latitude, by contrast, are equidistant from each other irrespective of latitude: they are always around 111 km apart, including at the equator and near the poles (see Figures \@ref(fig:crs-buf) to \@ref(fig:wintriproj)). ``` Do not interpret the warning about the geographic (`longitude/latitude`) CRS as "the CRS should not be set": it almost always should be! It is better understood as a suggestion to *reproject* the data onto a projected CRS. This suggestion does not always need to be heeded: performing spatial and geometric operations makes little or no difference in some cases (e.g., spatial subsetting). But for operations involving distances such as buffering, the only way to ensure a good result (without using spherical geometry engines) is to create a projected copy of the data and run the operation on that. This is done in the code chunk below. ```{r 06-reproj-6} london_proj = data.frame(x = 530000, y = 180000) |> st_as_sf(coords = c("x", "y"), crs = "EPSG:27700") ``` The result is a new object that is identical to `london`, but created using a suitable CRS (the British National Grid, which has an EPSG code of 27700 in this case) that has units of meters. We can verify that the CRS has changed using `st_crs()` as follows (some of the output has been replaced by `...,`): ```{r 06-reproj-7, out.lines=8} st_crs(london_proj) ``` Notable components of this CRS description include the EPSG code (`EPSG: 27700`) and the detailed `wkt` string (only the first five lines of which are shown).^[ For a short description of the most relevant projection parameters and related concepts, see the fourth lecture by Jochen Albrecht hosted at http://www.geography.hunter.cuny.edu/~jochen/GTECH361/lectures/ and information at https://proj.org/usage/projections.html. ] The fact that the units of the CRS, described in the LENGTHUNIT field, are meters (rather than degrees) tells us that this is a projected CRS: `st_is_longlat(london_proj)` now returns `FALSE` and geometry operations on `london_proj` will work without a warning. Buffer operations on the `london_proj` will use GEOS, and results will be returned with proper units of distance. The following line of code creates a buffer around *projected* data of exactly 100 km: ```{r 06-reproj-8} london_buff_projected = st_buffer(london_proj, 100000) ``` The geometries of the three `london_buff*` objects created in the preceding code that *have* a specified CRS (`london_buff_s2`, `london_buff_lonlat` and `london_buff_projected`) are illustrated in Figure \@ref(fig:crs-buf). ```{r crs-buf-old, include=FALSE, eval=FALSE} #| message: FALSE #| warning: FALSE #| results: hide uk = rnaturalearth::ne_countries(scale = 50, returnclass = "sf") |> filter(grepl(pattern = "United Kingdom|Ire", x = name_long)) plot(london_buff_s2, graticule = st_crs(4326), axes = TRUE, reset = FALSE, lwd = 2) plot(london_buff_s2_100_cells, lwd = 9, add = TRUE) plot(st_geometry(uk), add = TRUE, border = "gray", lwd = 3) uk_proj = uk |> st_transform("EPSG:27700") plot(london_buff_projected, graticule = st_crs("EPSG:27700"), axes = TRUE, reset = FALSE, lwd = 2) plot(london_proj, add = TRUE) plot(st_geometry(uk_proj), add = TRUE, border = "gray", lwd = 3) plot(london_buff_lonlat, graticule = st_crs("EPSG:27700"), axes = TRUE, reset = FALSE, lwd = 2) plot(london_proj, add = TRUE) plot(st_geometry(uk), add = TRUE, border = "gray", lwd = 3) ``` ```{r crs-buf, fig.cap="Buffers around London showing results created with the S2 spherical geometry engine on lon/lat data (left), projected data (middle) and lon/lat data without using spherical geometry (right). The left plot illustrates the result of buffering unprojected data with sf, which calls Google's S2 spherical geometry engine by default with max cells set to 1000 (thin line). The thick, blocky line illustrates the result of the same operation with max cells set to 100.", fig.scap="Buffers around London with a geographic and projected CRS.", echo=FALSE, fig.asp=0.39, fig.width = 8, warning = FALSE, message=FALSE} uk = rnaturalearth::ne_countries(scale = 50, returnclass = "sf") |> filter(grepl(pattern = "United Kingdom|Ire", x = name_long)) library(tmap) tm1 = tm_shape(london_buff_s2, bbox = st_bbox(london_buff_s2_100_cells)) + tm_graticules(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(london_buff_s2_100_cells) + tm_borders(col = "black", lwd = 1.5) + tm_shape(uk) + tm_polygons(lty = 3, fill_alpha = 0.2, fill = "#567D46") + tm_shape(london_proj) + tm_symbols() tm2 = tm_shape(london_buff_projected, bbox = st_bbox(london_buff_s2_100_cells)) + tm_grid(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(uk) + tm_polygons(lty = 3, fill_alpha = 0.2, fill = "#567D46") + tm_shape(london_proj) + tm_symbols() tm3 = tm_shape(london_buff_lonlat, bbox = st_bbox(london_buff_s2_100_cells)) + tm_graticules(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(uk) + tm_polygons(lty = 3, fill_alpha = 0.2, fill = "#567D46") + tm_shape(london_proj) + tm_symbols() tmap_arrange(tm1, tm2, tm3, nrow = 1) ``` It is clear from Figure \@ref(fig:crs-buf) that buffers based on `s2` and properly projected CRSs are not 'squashed', meaning that every part of the buffer boundary is equidistant to London. The results that are generated from lon/lat CRSs when `s2` is *not* used, either because the input lacks a CRS or because `sf_use_s2()` is turned off, are heavily distorted, with the result elongated in the north-south axis, highlighting the dangers of using algorithms that assume projected data on lon/lat inputs (as GEOS does). The results generated using S2\index{S2} are also distorted, however, although less dramatically. Both buffer boundaries in Figure \@ref(fig:crs-buf) (left) are jagged, although this may only be apparent or relevant for the thick boundary representing a buffer created with the `s2` argument `max_cells` set to 100. The lesson is that results obtained from lon/lat data via S2 will be different from results obtained from using projected data. The difference between S2\index{S2} derived buffers and GEOS\index{GEOS} derived buffers on projected data reduce as the value of `max_cells` increases: the 'right' value for this argument may depend on many factors and the default value 1000 is often a reasonable default. When choosing `max_cells` values, speed of computation should be balanced against resolution of results. In situations where smooth curved boundaries are advantageous, transforming to a projected CRS before buffering (or performing other geometry operations) may be appropriate. The importance of CRSs (primarily whether they are projected or geographic) and the impacts of **sf**'s default setting to use S2 for buffers on lon/lat data is clear from the example above. The subsequent sections go into more depth, exploring which CRS to use when projected CRSs *are* needed and the details of reprojecting vector and raster objects. ## When to reproject? {#whenproject} \index{CRS!reprojection} The previous section showed how to set the CRS manually, with `st_set_crs(london, "EPSG:4326")`. In real-world applications, however, CRSs are usually set automatically when data is read-in. In many projects the main CRS-related task is to *transform* objects, from one CRS into another. But when should data be transformed? And into which CRS? There are no clear-cut answers to these questions and CRS selection always involves trade-offs [@maling_coordinate_1992]. However, there are some general principles provided in this section that can help you decide. First it's worth considering *when to transform*. In some cases transformation to a geographic CRS is essential, such as when publishing data online with the **leaflet** package. Another case is when two objects with different CRSs must be compared or combined, as shown when we try to find the distance between two `sf` objects with different CRSs: ```{r 06-reproj-9, eval=FALSE} st_distance(london_geo, london_proj) # > Error: st_crs(x) == st_crs(y) is not TRUE ``` To make the `london` and `london_proj` objects geographically comparable, one of them must be transformed into the CRS of the other. But which CRS to use? The answer depends on context: many projects, especially those involving web mapping, require outputs in EPSG:4326, in which case it is worth transforming the projected object. If, however, the project requires planar geometry operations rather than spherical geometry operations engine (e.g., to create buffers with smooth edges), it may be worth transforming data with a geographic CRS into an equivalent object with a projected CRS, such as the British National Grid (EPSG:27700). That is the subject of Section \@ref(reproj-vec-geom). ## Which CRS to use? {#which-crs} \index{CRS!reprojection} \index{projection!World Geodetic System} The question of *which CRS to use* is tricky, and there is rarely a 'right' answer: "There exist no all-purpose projections, all involve distortion when far from the center of the specified frame" [@bivand_applied_2013]. Additionally, you should not be attached just to one projection for every task. It is possible to use one projection for some part of the analysis, another projection for a different part, and even some other for visualization. Always try to pick the CRS that serves your goal best! When selecting **geographic CRSs**\index{CRS!geographic}, the answer is often [WGS84](https://en.wikipedia.org/wiki/World_Geodetic_System#A_new_World_Geodetic_System:_WGS_84). It is used not only for web mapping, but also because GPS datasets and thousands of raster and vector datasets are provided in this CRS by default. WGS84 is the most common CRS in the world, so it is worth knowing its EPSG code: 4326.^[ Instead of `"EPSG:4326"`, you may also use `"OGC:CRS84"`. The former assumes that latitude is always ordered before longitude, while the latter is the standard representation used by GeoJSON, with coordinates ordered longitude before latitude.] This 'magic number' can be used to convert objects with unusual projected CRSs into something that is widely understood. What about when a **projected CRS**\index{CRS!projected} is required? In some cases, it is not something that we are free to decide: "often the choice of projection is made by a public mapping agency" [@bivand_applied_2013]. This means that when working with local data sources, it is likely preferable to work with the CRS in which the data was provided, to ensure compatibility, even if the official CRS is not the most accurate. The example of London was easy to answer because (a) the British National Grid (with its associated EPSG code 27700) is well known and (b) the original dataset (`london`) already had that CRS. \index{UTM} A commonly used default is Universal Transverse Mercator ([UTM](https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system)), a set of CRSs that divides the Earth into 60 longitudinal wedges and 20 latitudinal segments. Almost every place on Earth has a UTM code, such as "60H" which refers to northern New Zealand where R was invented. UTM EPSG codes run sequentially from 32601 to 32660 for northern hemisphere locations and from 32701 to 32760 for southern hemisphere locations. To show how the system works, let's create a function, `lonlat2UTM()` to calculate the EPSG code associated with any point on the planet as [follows](https://stackoverflow.com/a/9188972/): ```{r 06-reproj-13} lonlat2UTM = function(lonlat) { utm = (floor((lonlat[1] + 180) / 6) %% 60) + 1 if (lonlat[2] > 0) { utm + 32600 } else{ utm + 32700 } } ``` The following command uses this function to identify the UTM zone and associated EPSG code for Auckland and London: ```{r 06-reproj-14, echo=FALSE, eval=FALSE} stplanr::geo_code("Auckland") ``` ```{r 06-reproj-15} lonlat2UTM(c(174.7, -36.9)) lonlat2UTM(st_coordinates(london)) ``` The transverse Mercator projection used by UTM CRSs is conformal but distorts areas and distances with increasing severity with distance from the center of the UTM zone. Documentation from the GIS software Manifold therefore suggests restricting the longitudinal extent of projects using UTM zones to 6 degrees from the central meridian ([manifold.net](https://manifold.net/doc/mfd9/universal_transverse_mercator_projection.htm)). Therefore, we recommend using UTM only when your focus is on preserving angles for a relatively small area! Currently, we also have tools helping us to select a proper CRS, which includes the **crsuggest** package (@R-crsuggest). The main function in this package, `suggest_crs()`, takes a spatial object with geographic CRS and returns a list of possible projected CRSs that could be used for the given area.^[This package also allows to figure out the true CRS of the data without any CRS information attached.] Another helpful tool is the webpage https://jjimenezshaw.github.io/crs-explorer/ that lists CRSs based on selected location and type. Important note: while these tools are helpful in many situations, you need to be aware of the properties of the recommended CRS before you apply it. \index{CRS!custom} In cases where an appropriate CRS is not immediately clear, the choice of CRS should depend on the properties that are most important to preserve in the subsequent maps and analysis. CRSs are either equal-area, equidistant, conformal (with shapes remaining unchanged), or some combination of compromises of those (Section \@ref(projected-coordinate-reference-systems)). Custom CRSs with local parameters can be created for a region of interest and multiple CRSs can be used in projects when no single CRS suits all tasks. 'Geodesic calculations' can provide a fall-back if no CRSs are appropriate (see [proj.org/geodesic.html](https://proj.org/geodesic.html)). Regardless of the projected CRS used, the results may not be accurate for geometries covering hundreds of kilometers. \index{CRS!custom} When deciding on a custom CRS, we recommend the following:^[ Many thanks to an anonymous reviewer whose comments formed the basis of this advice. ] \index{projection!Lambert azimuthal equal-area} \index{projection!Azimuthal equidistant} \index{projection!Lambert conformal conic} \index{projection!Stereographic} \index{projection!Universal Transverse Mercator} - A Lambert azimuthal equal-area ([LAEA](https://en.wikipedia.org/wiki/Lambert_azimuthal_equal-area_projection)) projection for a custom local projection (set latitude and longitude of origin to the center of the study area), which is an equal-area projection at all locations but distorts shapes beyond thousands of kilometers - Azimuthal equidistant ([AEQD](https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection)) projections for a specifically accurate straight-line distance between a point and the center point of the local projection - Lambert conformal conic ([LCC](https://en.wikipedia.org/wiki/Lambert_conformal_conic_projection)) projections for regions covering thousands of kilometers, with the cone set to keep distance and area properties reasonable between the secant lines - Stereographic ([STERE](https://en.wikipedia.org/wiki/Stereographic_projection)) projections for polar regions, but taking care not to rely on area and distance calculations thousands of kilometers from the center One possible approach to automatically select a projected CRS specific to a local dataset is to create an [AEQD](https://en.wikipedia.org/wiki/Azimuthal_equidistant_projection) projection for the center-point of the study area. This involves creating a custom CRS (with no EPSG code) with units of meters based on the center point of a dataset. Note that this approach should be used with caution: no other datasets will be compatible with the custom CRS created, and results may not be accurate when used on extensive datasets covering hundreds of kilometers. The principles outlined in this section apply equally to vector and raster datasets. Some features of CRS transformation, however, are unique to each geographic data model. We will cover the particularities of vector data transformation in Section \@ref(reproj-vec-geom) and those of raster transformation in Section \@ref(reproj-ras). Next, Section \@ref(mapproj), shows how to create custom map projections. ## Reprojecting vector geometries {#reproj-vec-geom} \index{CRS!reprojection} \index{vector!reprojection} Chapter \@ref(spatial-class) demonstrated how vector geometries are made up of points, and how points form the basis of more complex objects such as lines and polygons. Reprojecting vectors thus consists of transforming the coordinates of these points, which form the vertices of lines and polygons. Section \@ref(whenproject) contains an example in which at least one `sf` object must be transformed into an equivalent object with a different CRS to calculate the distance between two objects. ```{r 06-reproj-10} london2 = st_transform(london_geo, "EPSG:27700") ``` Now that a transformed version of `london` has been created, using the **sf** function `st_transform()`, the distance between the two representations of London can be found.^[ An alternative to `st_transform()` is `st_transform_proj()` from the **lwgeom**, which enables transformations and which bypasses GDAL and can support projections not supported by GDAL. However, at the time of writing (2024) we could not find any projections supported by `st_transform_proj()` but not supported by `st_transform()`. ] It may come as a surprise that `london` and `london2` are over 2 km apart!^[ The difference in location between the two points is not due to imperfections in the transforming operation (which is in fact very accurate) but the low precision of the manually-created coordinates that created `london` and `london_proj`. Also surprising may be that the result is provided in a matrix with units of meters. This is because `st_distance()` can provide distances between many features and because the CRS has units of meters. Use `as.numeric()` to coerce the result into a regular number. ] ```{r 06-reproj-11} st_distance(london2, london_proj) ``` Functions for querying and reprojecting CRSs are demonstrated below with reference to `cycle_hire_osm`, an `sf` object from **spData** that represents 'docking stations' where you can hire bicycles in London. The CRS of `sf` objects can be queried, and as we learned in Section \@ref(reproj-intro), set with the function `st_crs()`. The output is printed as multiple lines of text containing information about the coordinate system: ```{r, out.lines=6} st_crs(cycle_hire_osm) ``` As we saw in Section \@ref(crs-setting), the main CRS components, `User input` and `wkt`, are printed as a single entity. The output of `st_crs()` is in fact a named list of class `crs` with two elements, single character strings named `input` and `wkt`, as shown in the output of the following code chunk: ```{r 06-reproj-16} crs_lnd = st_crs(london_geo) class(crs_lnd) names(crs_lnd) ``` Additional elements can be retrieved with the `$` operator, including `Name`, `proj4string` and `epsg` (see [`?st_crs`](https://r-spatial.github.io/sf/reference/st_crs.html) and the CRS and tranformation tutorial on the GDAL [website](https://gdal.org/tutorials/osr_api_tut.html#querying-coordinate-reference-system) for details): ```{r} crs_lnd$Name crs_lnd$proj4string crs_lnd$epsg ``` As mentioned in Section \@ref(crs-in-r), WKT representation, stored in the `$wkt` element of the `crs_lnd` object is the ultimate source of truth. This means that the outputs of the previous code chunk are queries from the `wkt` representation provided by PROJ, rather than inherent attributes of the object and its CRS. Both `wkt` and `User Input` elements of the CRS are changed when the object's CRS is transformed. In the code chunk below, we create a new version of `cycle_hire_osm` with a projected CRS (only the first 4 lines of the CRS output are shown for brevity). ```{r 06-reproj-18, eval=FALSE} cycle_hire_osm_projected = st_transform(cycle_hire_osm, "EPSG:27700") st_crs(cycle_hire_osm_projected) #> Coordinate Reference System: #> User input: EPSG:27700 #> wkt: #> PROJCRS["OSGB36 / British National Grid", #> ... ``` The resulting object has a new CRS with an EPSG code 27700. But how do we find out more details about this EPSG code, or any code? One option is to search for it online, another is to look at the properties of the CRS object: ```{r 06-reproj-19, linewidth=80} crs_lnd_new = st_crs("EPSG:27700") crs_lnd_new$Name crs_lnd_new$proj4string crs_lnd_new$epsg ``` The result shows that the EPSG code 27700 represents the British National Grid, which could have been found by searching online for "[EPSG 27700](https://www.google.com/search?q=CRS+27700)". ```{block2 06-reproj-21, type='rmdnote'} Printing a spatial object in the console automatically returns its coordinate reference system. To access and modify it explicitly, use the `st_crs` function, for example, `st_crs(cycle_hire_osm)`. ``` ## Reprojecting raster geometries {#reproj-ras} \index{raster!reprojection} \index{raster!warping} \index{raster!transformation} \index{raster!resampling} The projection concepts described in the previous section apply to rasters. However, there are important differences in reprojection of vectors and rasters: transforming a vector object involves changing the coordinates of every vertex, but this does not apply to raster data. Rasters are composed of rectangular cells of the same size (expressed by map units, such as degrees or meters), so it is usually impracticable to transform coordinates of pixels separately. Thus, raster reprojection involves creating a new raster object, often with a different number of columns and rows than the original. The attributes must subsequently be re-estimated, allowing the new pixels to be 'filled' with appropriate values. In other words, raster reprojection can be thought of as two separate spatial operations: a vector reprojection of the raster extent to another CRS (Section \@ref(reproj-vec-geom)), and computation of new pixel values through resampling (Section \@ref(resampling)). Thus in most cases when both raster and vector data are used, it is better to avoid reprojecting rasters and to reproject vectors instead. ```{block2 06-reproj-35a, type='rmdnote'} Reprojection of the regular rasters is also known as warping. Additionally, there is a second similar operation called "transformation". Instead of resampling all of the values, it leaves all values intact but recomputes new coordinates for every raster cell, changing the grid geometry. For example, it could convert the input raster (a regular grid) into a curvilinear grid. \index{stars (package)} The transformation operation can be performed in R using [the **stars** package](https://r-spatial.github.io/stars/articles/stars5.html). ``` ```{r, include=FALSE} #test the above idea library(terra) library(sf) con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) con_raster_ea = project(con_raster, "EPSG:32612", method = "bilinear") con_poly = st_as_sf(as.polygons(con_raster>0)) con_poly_ea = st_transform(con_poly, "EPSG:32612") plot(con_raster) plot(con_poly, col = NA, add = TRUE, lwd = 4) plot(con_raster_ea) plot(con_poly_ea, col = NA, add = TRUE, lwd = 4) ``` The raster reprojection process is done with `project()` from the **terra** package. Like the `st_transform()` function demonstrated in the previous section, `project()` takes a spatial object (a raster dataset in this case) and some CRS representation as the second argument. On a side note, the second argument can also be an existing raster object with a different CRS. Let's take a look at two examples of raster transformation: using categorical and continuous data. Land cover data are usually represented by categorical maps. The `nlcd.tif` file provides information for a small area in Utah, USA obtained from [National Land Cover Database 2011](https://www.mrlc.gov/data/nlcd-2011-land-cover-conus) in the NAD83 / UTM zone 12N CRS, as shown in the output of the code chunk below (only first line of output shown). ```{r 06-reproj-29, results='hide'} cat_raster = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) crs(cat_raster) #> PROJCRS["NAD83 / UTM zone 12N", #> ... ``` In this region, eight land cover classes were distinguished (a full list of NLCD2011 land cover classes can be found at [mrlc.gov](https://www.mrlc.gov/data/legends/national-land-cover-database-2011-nlcd2011-legend)): ```{r 06-reproj-30} unique(cat_raster) ``` When reprojecting categorical rasters, the estimated values must be the same as those of the original. This could be done using the nearest neighbor method (`near`), which sets each new cell value to the value of the nearest cell (center) of the input raster. An example is reprojecting `cat_raster` to WGS84, a geographic CRS well suited for web mapping. The first step is to obtain the definition of this CRS. The second step is to reproject the raster with the `project()` function which, in the case of categorical data, uses the nearest neighbor method (`near`). ```{r 06-reproj-31} cat_raster_wgs84 = project(cat_raster, "EPSG:4326", method = "near") ``` Many properties of the new object differ from the previous one, including the number of columns and rows (and therefore number of cells), resolution (transformed from meters into degrees), and extent, as illustrated in Table \@ref(tab:catraster) (note that the number of categories increases from 8 to 9 because of the addition of `NA` values, not because a new category has been created --- the land cover classes are preserved). ```{r catraster, echo=FALSE} tibble( CRS = c("NAD83", "WGS84"), nrow = c(nrow(cat_raster), nrow(cat_raster_wgs84)), ncol = c(ncol(cat_raster), ncol(cat_raster_wgs84)), ncell = c(ncell(cat_raster), ncell(cat_raster_wgs84)), resolution = c(mean(res(cat_raster)), mean(res(cat_raster_wgs84), na.rm = TRUE)), unique_categories = c(length(unique(values(cat_raster))), length(unique(values(cat_raster_wgs84))))) |> knitr::kable(caption = paste("Key attributes in the original (cat\\_raster)", "and projected (cat\\_raster\\_wgs84)", "categorical raster datasets."), caption.short = paste("Key attributes in the original and", "projected raster datasets"), digits = 4, booktabs = TRUE) ``` Reprojecting numeric rasters (with `numeric` or in this case `integer` values) follows an almost identical procedure. This is demonstrated below with `srtm.tif` in **spDataLarge** from [the Shuttle Radar Topography Mission (SRTM)](https://www2.jpl.nasa.gov/srtm/), which represents height in meters above sea level (elevation) with the WGS84 CRS: ```{r 06-reproj-32, out.lines=6} con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) cat(crs(con_raster)) ``` We will reproject this dataset into a projected CRS, but *not* with the nearest neighbor method which is appropriate for categorical data. Instead, we will use the bilinear method which computes the output cell value based on the four nearest cells in the original raster.^[ Other methods mentioned in Section \@ref(resampling) also can be used here. ] The values in the projected dataset are the distance-weighted average of the values from these four cells: the closer the input cell is to the center of the output cell, the greater its weight. The following commands create a text string representing WGS 84 / UTM zone 12N, and reproject the raster into this CRS, using the `bilinear` method (output not shown). ```{r 06-reproj-34} #| eval: false con_raster_ea = project(con_raster, "EPSG:32612", method = "bilinear") cat(crs(con_raster_ea)) ``` Raster reprojection on numeric variables also leads to changes to values and spatial properties, such as the number of cells, resolution, and extent. These changes are demonstrated in Table \@ref(tab:rastercrs).^[ Another minor change, which is not represented in Table \@ref(tab:rastercrs), is that the class of the values in the new projected raster dataset is `numeric`. This is because the `bilinear` method works with continuous data and the results are rarely coerced into whole integer values. This can have implications for file sizes when raster datasets are saved. ] ```{r rastercrs, echo=FALSE} tibble( CRS = c("WGS84", "UTM zone 12N"), nrow = c(nrow(con_raster), nrow(con_raster_ea)), ncol = c(ncol(con_raster), ncol(con_raster_ea)), ncell = c(ncell(con_raster), ncell(con_raster_ea)), resolution = c(mean(res(con_raster)), mean(res(con_raster_ea), na.rm = TRUE)), mean = c(mean(values(con_raster)), mean(values(con_raster_ea), na.rm = TRUE))) |> knitr::kable(caption = paste("Key attributes in the original (con\\_raster)", "and projected (con\\_raster\\_ea) continuous raster", "datasets."), caption.short = paste("Key attributes in the original and", "projected raster datasets"), digits = 4, booktabs = TRUE) ``` ```{block2 06-reproj-35, type='rmdnote'} Of course, the limitations of 2D Earth projections apply as much to vector as to raster data. At best we can comply with two out of three spatial properties (distance, area, direction). Therefore, the task at hand determines which projection to choose. For instance, if we are interested in a density (points per grid cell or inhabitants per grid cell), we should use an equal-area projection (see also Chapter \@ref(location)). ``` ## Custom map projections {#mapproj} \index{CRS!custom} Established CRSs captured by `AUTHORITY:CODE` identifiers such as `EPSG:4326` are well suited for many applications. However, it is desirable to use alternative projections or to create custom CRSs in some cases. Section \@ref(which-crs) mentioned reasons for using custom CRSs and provided several possible approaches. Here, we show how to apply these ideas in R. One is to take an existing WKT definition of a CRS, modify some of its elements, and then use the new definition for reprojecting. This can be done for spatial vectors with `st_crs()` and `st_transform()`, and for spatial rasters with `crs()` and `project()`, as demonstrated in the following example which transforms the `zion` object to a custom azimuthal equidistant (AEQD) CRS. ```{r} zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) ``` Using a custom AEQD CRS requires knowing the coordinates of the center point of a dataset in degrees (geographic CRS). In our case, this information can be extracted by calculating a centroid of the `zion` area and transforming it into WGS84. ```{r, warning=FALSE} zion_centr = st_centroid(zion) zion_centr_wgs84 = st_transform(zion_centr, "EPSG:4326") st_as_text(st_geometry(zion_centr_wgs84)) ``` Next, we can use the newly obtained values to update the WKT definition of the AEQD CRS seen below. Notice that we modified just two values below -- `"Central_Meridian"` to the longitude and `"Latitude_Of_Origin"` to the latitude of our centroid. ```{r} my_wkt = 'PROJCS["Custom_AEQD", GEOGCS["GCS_WGS_1984", DATUM["WGS_1984", SPHEROID["WGS_1984",6378137.0,298.257223563]], PRIMEM["Greenwich",0.0], UNIT["Degree",0.0174532925199433]], PROJECTION["Azimuthal_Equidistant"], PARAMETER["Central_Meridian",-113.0263], PARAMETER["Latitude_Of_Origin",37.29818], UNIT["Meter",1.0]]' ``` This approach's last step is to transform our original object (`zion`) to our new custom CRS (`zion_aeqd`). ```{r} zion_aeqd = st_transform(zion, my_wkt) ``` Custom projections can also be made interactively, for example, using the [Projection Wizard](https://projectionwizard.org/#) web application [@savric_projection_2016]. This website allows you to select a spatial extent of your data and a distortion property, and returns a list of possible projections. The list also contains WKT definitions of the projections that you can copy and use for reprojections. Also, see @opengeospatialconsortium_wellknown_2019 for details on creating custom CRS definitions with WKT strings. \index{CRS!proj-string} PROJ strings can also be used to create custom projections, accepting the limitations inherent to projections, especially of geometries covering large geographic areas, mentioned in Section \@ref(crs-in-r). Many projections have been developed and can be set with the `+proj=` element of PROJ strings, with dozens of projects described in detail on the [PROJ website](https://proj.org/operations/projections/index.html) alone. When mapping the world while preserving area relationships, the Mollweide projection, illustrated in Figure \@ref(fig:mollproj), is a popular and often sensible choice [@jenny_guide_2017]. To use this projection, we need to specify it using the proj-string element, `"+proj=moll"`, in the `st_transform` function: ```{r 06-reproj-22} world_mollweide = st_transform(world, crs = "+proj=moll") ``` ```{r mollproj, fig.cap="Mollweide projection of the world.", warning=FALSE, message=FALSE, echo=FALSE} library(tmap) world_mollweide_gr = st_graticule(lat = c(-89.9, seq(-80, 80, 20), 89.9)) |> st_transform(crs = "+proj=moll") tm_shape(world_mollweide_gr) + tm_lines(col = "gray") + tm_shape(world_mollweide) + tm_borders(col = "black") ``` It is often desirable to minimize distortion for all spatial properties (area, direction, distance) when mapping the world. One of the most popular projections to achieve this is [Winkel tripel](https://www.winkel.org/other/Winkel%20Tripel%20Projections.htm), illustrated in Figure \@ref(fig:wintriproj).^[ This projection is used, among others, by the National Geographic Society. ] The result was created with the following command: ```{r 06-reproj-23} world_wintri = st_transform(world, crs = "+proj=wintri") ``` ```{r 06-reproj-23-tests, eval=FALSE, echo=FALSE} world_wintri = lwgeom::st_transform_proj(world, crs = "+proj=wintri") world_wintri2 = st_transform(world, crs = "+proj=wintri") world_tissot = st_transform(world, crs = "+proj=tissot +lat_1=60 +lat_2=65") waldo::compare(world_wintri$geom[1], world_wintri2$geom[1]) world_tpers = st_transform(world, crs = "+proj=tpers +h=5500000 +lat_0=40") plot(st_cast(world_tpers, "MULTILINESTRING")) # fails plot(st_coordinates(world_tpers)) # fails world_tpers_complete = world_tpers[st_is_valid(world_tpers), ] world_tpers_complete = world_tpers_complete[!st_is_empty(world_tpers_complete), ] plot(world_tpers_complete["pop"]) ``` ```{r wintriproj, fig.cap="Winkel tripel projection of the world.", echo=FALSE} world_wintri_gr = st_graticule(lat = c(-89.9, seq(-80, 80, 20), 89.9)) |> st_transform(crs = "+proj=wintri") library(tmap) tm_shape(world_wintri_gr) + tm_lines(col = "gray") + tm_shape(world_wintri) + tm_borders(col = "black") ``` ```{block2 06-reproj-24, type='rmdnote', echo=FALSE} The two main functions for transformation of simple features coordinates are `sf::st_transform()`, and `sf::sf_project()`. `st_transform()` uses the GDAL interface to PROJ, while `sf_project()` (which works with two-column numeric matrices, representing points) uses PROJ directly. `st_tranform()` is appropriate for most situations, and provides a set of the most often used parameters and well-defined transformations. `sf_project()` may be suited for point transformations when speed is important. ``` ```{r 06-reproj-25, eval=FALSE, echo=FALSE} # demo of sf_project mat_lonlat = as.matrix(data.frame(x = 0:20, y = 50:70)) plot(mat_lonlat) mat_projected = sf_project(from = st_crs(4326)$proj4string, to = st_crs(27700)$proj4string, pts = mat_lonlat) plot(mat_projected) ``` Moreover, proj-string parameters can be modified in most CRS definitions, for example the center of the projection can be adjusted using the `+lon_0` and `+lat_0` parameters. The below code transforms the coordinates to the Lambert azimuthal equal-area projection centered on the longitude and latitude of New York City (Figure \@ref(fig:laeaproj2)). ```{r 06-reproj-27} world_laea2 = st_transform(world, crs = "+proj=laea +x_0=0 +y_0=0 +lon_0=-74 +lat_0=40") ``` ```{r laeaproj2, fig.cap="Lambert azimuthal equal-area projection of the world centered on New York City.", fig.scap="Lambert azimuthal equal-area projection centered on New York City.", warning=FALSE, echo=FALSE} world_laea2_g = st_graticule(ndiscr = 10000) |> st_transform("+proj=laea +x_0=0 +y_0=0 +lon_0=-74 +lat_0=40.1 +ellps=WGS84 +no_defs") |> st_geometry() tm_shape(world_laea2_g) + tm_lines(col = "gray") + tm_shape(world_laea2) + tm_borders(col = "black") ``` More information on CRS modifications can be found in the [Using PROJ](https://proj.org/usage/index.html) documentation. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_07-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 08-read-write-plot.Rmd ================================================ # Geographic data I/O {#read-write} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} This chapter requires the following packages: ```{r 07-read-write-plot-1, message=FALSE} library(sf) library(terra) library(dplyr) library(spData) ``` ## Introduction This chapter is about reading and writing geographic data. Geographic data *input* is essential for geocomputation\index{geocomputation}: real-world applications are impossible without data. Data *output* is also vital, enabling others to use valuable new or improved datasets resulting from your work. Taken together, these processes of input/output can be referred to as data I/O. Geographic data I/O is often done in haste at the beginning and end of projects and otherwise ignored. However, data import and export are fundamental to the success or otherwise of projects: small I/O mistakes made at the beginning of projects (e.g., using an out-of-date dataset) can lead to large problems down the line. There are many geographic file formats, each with its own advantages and disadvantages, as described in Section \@ref(file-formats). Reading and writing from and to these file formats is covered in Sections \@ref(data-input) and \@ref(data-output), respectively. In terms of where to *find* data, Section \@ref(retrieving-data) describes *geoportals* and how to import data from them. Dedicated packages to ease geographic data import, from sources including OpenStreetMap, are described in Section \@ref(geographic-data-packages). If you want to put your data 'into production' in web services (or if you want to be sure that your data adheres to established standards), geographic metadata is important, as described in Section \@ref(geographic-metadata). Another possibility to obtain spatial data is to use web services, outlined in Section \@ref(geographic-web-services). The final Section \@ref(visual-outputs) demonstrates methods for saving visual outputs (maps), in preparation for Chapter \@ref(adv-map) on visualization. ## File formats \index{file formats} Geographic datasets are usually stored as files or in spatial databases. File formats can either store vector or raster data, while spatial databases such as [PostGIS](https://postgis.net/) can store both (see also Section \@ref(postgis)). Today the variety of file formats may seem bewildering, but there has been much consolidation and standardization since the beginnings of GIS software in the 1960s when the first widely distributed program ([SYMAP](https://news.harvard.edu/gazette/story/2011/10/the-invention-of-gis/)) for spatial analysis was created at Harvard University [@coppock_history_1991]. \index{GDAL} GDAL (which should be pronounced "goo-dal", with the double "o" making a reference to object-orientation), Geospatial Data Abstraction Library, has resolved many issues associated with incompatibility between geographic file formats since its release in 2000. GDAL provides a unified and high-performance interface for reading and writing many raster and vector data formats.^[As we mentioned in Chapter \@ref(geometry-operations), GDAL also contains a set of utility functions allowing for raster mosaicing, resampling, cropping, and reprojecting, etc.] Many open and proprietary GIS programs, including GRASS GIS, ArcGIS\index{ArcGIS} and QGIS\index{QGIS}, use GDAL\index{GDAL} behind their GUIs\index{graphical user interface} for doing the legwork of ingesting and spitting out geographic data in appropriate formats. GDAL\index{GDAL} provides access to more than 200 vector and raster data formats. Table \@ref(tab:formats) presents some basic information about selected and often used spatial file formats. ```{r formats, echo=FALSE} file_formats = tibble::tribble(~Name, ~Extension, ~Information, ~Type, ~Model, "ESRI Shapefile", ".shp (the main file)", "Popular format consisting of at least three files. No support for: files > 2 GB; mixed types; names > 10 chars; cols > 255.", "Vector", "Partially open", "GeoJSON", ".geojson", "Extends the JSON exchange format by including a subset of the simple feature representation; mostly used for storing coordinates in longitude and latitude; it is extended by the TopoJSON format.", "Vector", "Open", "KML", ".kml", "XML-based format for spatial visualization, developed for use with Google Earth. Zipped KML file forms the KMZ format.", "Vector", "Open", "GPX", ".gpx", "XML schema created for exchange of GPS data.", "Vector", "Open", "FlatGeobuf", ".fgb", "Single file format allowing for quick reading and writing of vector data. Has streaming capabilities.", "Vector", "Open", "GeoTIFF", ".tif/.tiff", "Popular raster format. A TIFF file containing additional spatial metadata.", "Raster", "Open", "Arc ASCII", ".asc", "Text format where the first six lines represent the raster header, followed by the raster cell values arranged in rows and columns.", "Raster", "Open", "SQLite/SpatiaLite", ".sqlite", "Standalone relational database, SpatiaLite is the spatial extension of SQLite.", "Vector and raster", "Open", "ESRI FileGDB", ".gdb", "Spatial and non-spatial objects created by ArcGIS. Allows multiple feature classes; topology. Limited support from GDAL.", "Vector and raster", "Proprietary", "GeoPackage", ".gpkg", "Lightweight database container based on SQLite allowing an easy and platform-independent exchange of geodata.", "Vector and (very limited) raster", "Open" ) knitr::kable(file_formats, caption = "Selected spatial file formats.", caption.short = "Selected spatial file formats.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "5em") |> kableExtra::column_spec(3, width = "12em") |> kableExtra::column_spec(4, width = "4em") |> kableExtra::column_spec(5, width = "5em") |> kableExtra::kable_styling(latex_options = "scale_down") ``` \index{Shapefile} \index{GeoPackage} An important development ensuring the standardization and open-sourcing of file formats was the founding of the Open Geospatial Consortium ([OGC](https://www.ogc.org/)) in 1994. Beyond defining the simple features data model (see Section \@ref(intro-sf)), the OGC also coordinates the development of open standards, for example as used in file formats such as GML\index{GML}, KML\index{KML} and GeoPackage\index{GeoPackage}. Open file formats of the kind endorsed by the OGC have several advantages over proprietary formats: the standards are published, ensure transparency and open up the possibility for users to further develop and adjust the file formats to their specific needs. ESRI Shapefile\index{Shapefile} is the most popular vector data exchange format; however, it is not an open format (though its specification is open). It was developed in the early 1990s and has a number of limitations. First of all, it is a multi-file format, which consists of at least three files. It only supports 255 columns, column names are restricted to ten characters and the file size limit is 2 GB. Furthermore, ESRI Shapefile\index{Shapefile} does not support all possible geometry types, for example, it is unable to distinguish between a polygon and a multipolygon.^[To learn more about ESRI Shapefile limitations and possible alternative file formats, visit http://switchfromshapefile.org/.] Despite these limitations, a viable alternative had been missing for a long time. In recent years, [GeoPackage](https://www.geopackage.org/)\index{GeoPackage} emerged, and seems to be a more than suitable replacement candidate for ESRI Shapefile. Geopackage is a format for exchanging geospatial information and an OGC standard. The GeoPackage standard describes the rules on how to store geospatial information in a tiny SQLite container. Hence, GeoPackage is a lightweight spatial database container, which allows the storage of vector and raster data but also of non-spatial data and extensions. Aside from GeoPackage, there are other geospatial data exchange formats worth checking out (Table \@ref(tab:formats)). \index{GeoTIFF} \index{COG} The GeoTIFF format seems to be the most prominent raster data format. It allows spatial information, such as CRS, to be embedded within a TIFF file. Similar to ESRI Shapefile, this format was first developed in the 1990s, but as an open format. Additionally, GeoTIFF is still being expanded and improved. One of the most significant recent additions to the GeoTIFF format is its variant called [COG](https://www.cogeo.org/) (*Cloud Optimized GeoTIFF*). Raster objects saved as COGs can be hosted on HTTP servers, so other people can read only parts of the file without downloading the whole file (see Sections \@ref(raster-data-read) and \@ref(raster-data-write)). There are many geographic file formats beyond those shown in Table \@ref(tab:formats) and new data formats capable of representing geographic are being developed. Recent examples are formats based on the [GeoArrow](https://github.com/geoarrow/geoarrow) and [Zarr](https://zarr.dev/) specifications. GDAL's documentation provides a good resource for learning about other [vector](https://gdal.org/drivers/vector/index.html) and [raster](https://gdal.org/drivers/raster/index.html) drivers. Furthermore, some data formats can store other data models (types) beyond the vector and raster data models introduced in Section \@ref(intro-sf). It includes LAS and LAZ formats for storing lidar point clouds, and NetCDF and HDF for storing multidimensional arrays. Spatial data is also often stored using tabular (non-spatial) text formats, including CSV files or Excel spreadsheets. For example, this can be convenient to share spatial samples with people who do not use GIS tools or exchange data with other software that does not accept spatial data formats. However, this approach has downsides: it is challenging for storing geometries that are more complex than POINTs and omits important spatial metadata such as the CRS. ## Data input (I) {#data-input} Executing commands such as `sf::read_sf()` (the main function we use for loading vector data) or `terra::rast()` (the main function used for loading raster data) silently sets off a chain of events that reads data from files. Many R packages provide example datasets (e.g., the dataset `spData::world` that we used in earlier chapters) and functions to get geographic datasets from a range of data sources. All of them load the data into R or, more precisely, assign objects to your workspace. This means that when objects are imported into R they are stored in RAM^[There are some exceptions to this rule, e.g., **terra**'s `SpatRaster` objects (which are C++ pointers to the actual data) and objects that represent database connections that can be imported into memory with functions such as `dplyr::collect()`, as outlined in Section \@ref(postgis).], can be listed with `ls()` (and should be viewable in 'Environment' panels in your development environement) and can be accessed from the [`.GlobalEnv`](http://adv-r.had.co.nz/Environments.html) of the R session. ### Vector data {#iovec} \index{vector!data input} Spatial vector data comes in a wide variety of file formats. Most popular representations such as `.geojson` and `.gpkg` files can be imported directly into R with the **sf** function `read_sf()` (or the equivalent `st_read()`), which uses [GDAL's vector drivers](https://gdal.org/drivers/vector/index.html)\index{GDAL} behind the scenes. The `st_drivers()` function returns a data frame containing `name` and `long_name` in the first two columns, and features of each driver available to GDAL (and therefore **sf**), including ability to write data and store raster data in the subsequent columns, as illustrated for key file formats in Table \@ref(tab:drivers). ```{r drivers, echo=FALSE} sf_drivers = st_drivers() |> dplyr::filter(name %in% c("ESRI Shapefile", "GeoJSON", "KML", "GPX", "GPKG", "FlatGeobuf")) |> tibble::as_tibble() # remove unhelpful row names knitr::kable(head(sf_drivers, n = 6), caption = paste("Popular drivers/formats for reading/writing", "vector data."), caption.short = "Sample of available vector drivers.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "7em") ``` The following commands show the first three drivers reported the computer's GDAL installation (results can vary depending on the GDAL version installed) and a summary of their features. Note that the majority of drivers can write data, while only a dozen or so formats can efficiently represent raster data in addition to vector data (see `?st_drivers()` for details): ```{r 07-read-write-plot-17, eval=FALSE} sf_drivers = st_drivers() head(sf_drivers, n = 3) summary(sf_drivers[-c(1:2)]) ``` The first argument of `read_sf()` is `dsn`, which should be a text string or an object containing a single text string. The content of a text string could vary between different drivers. In most cases, as with the ESRI Shapefile\index{Shapefile} (`.shp`) or the `GeoPackage`\index{GeoPackage} format (`.gpkg`), the `dsn` would be a file name. `read_sf()` guesses the driver based on the file extension, as illustrated for a `.gpkg` file below: ```{r 07-read-write-plot-19} f = system.file("shapes/world.gpkg", package = "spData") world = read_sf(f) ``` For some drivers, `dsn` could be provided as a folder name, access credentials for a database, or a GeoJSON string representation (see the examples of the `read_sf()` help page for more details). Some vector driver formats can store multiple data layers. By default, `read_sf()` automatically reads the first layer of the file specified in `dsn`; however, using the `layer` argument you can specify any other layer. \index{OGR SQL} The `read_sf()` function also allows for reading just parts of the file into RAM with two possible mechanisms. The first one is related to the `query` argument, which allows specifying what part of the data to read with [the OGR SQL query text](https://gdal.org/user/ogr_sql_dialect.html). An example below extracts data for Tanzania only (Figure \@ref(fig:readsfquery)A). It is done by specifying that we want to get all columns (`SELECT *`) from the `"world"` layer for which the `name_long` is equal to `"Tanzania"`: ```{r} tanzania = read_sf(f, query = 'SELECT * FROM world WHERE name_long = "Tanzania"') ``` If you do not know the names of the available columns, a good approach is to just read one row of the data with `'SELECT * FROM world WHERE FID = 1'`. `FID` represents a *feature ID* -- most often, it is a row number; however, its values depend on the used file format. For example, `FID` starts from 0 in ESRI Shapefile, from 1 in some other file formats, or can be even arbitrary. ```{r, eval=FALSE, echo=FALSE} read_sf(f, query = 'SELECT * FROM world WHERE FID = 0') ``` The second mechanism uses the `wkt_filter` argument. This argument expects a well-known text representing a study area for which we want to extract the data. Let's try it using a small example -- we want to read polygons from our file that intersect with the buffer of 50,000 meters of Tanzania's borders. To do it, we need to prepare our "filter" by (a) creating the buffer (Section \@ref(buffers)), (b) converting the `sf` buffer object into an `sfc` geometry object with `st_geometry()`, and (c) translating geometries into their well-known text representation with `st_as_text()`: ```{r} tanzania_buf = st_buffer(tanzania, 50000) tanzania_buf_geom = st_geometry(tanzania_buf) tanzania_buf_wkt = st_as_text(tanzania_buf_geom) ``` Now, we can apply this "filter" using the `wkt_filter` argument. ```{r} tanzania_neigh = read_sf(f, wkt_filter = tanzania_buf_wkt) ``` Our result, shown in Figure \@ref(fig:readsfquery)(B), contains Tanzania and every country within its 50-km buffer. ```{r readsfquery, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Reading a subset of the vector data using (A) a query and (B) a wkt filter."} library(tmap) tm1 = tm_shape(tanzania) + tm_polygons(lwd = 2) + tm_text(text = "name_long") + tm_scalebar(c(0, 200, 400), position = c("left", "bottom")) + tm_title("A. query") tanzania_neigh[tanzania_neigh$iso_a2 == "CD", "name_long"] = "Democratic\nRepublic\nof the Congo" tm2 = tm_shape(tanzania_neigh) + tm_polygons() + tm_text(text = "name_long", text.scale = tm_scale(auto.placement = FALSE, remove.overlap = FALSE), size = "area_km2", size.legend = tm_legend_hide(), size.scale = tm_scale_continuous(values = tm_seq(power = 1/6))) + tm_shape(tanzania_buf) + tm_polygons(col = "red", fill = "red", fill_alpha = 0.05) + tm_add_legend(type = "polygons", labels = "50km buffer around Tanzania", col = "red", fill_alpha = 0.1, fill = "red") + tm_scalebar(c(0, 200, 400), position = c("right", "bottom")) + tm_title("B. wkt_filter") + tm_layout(legend.position = c("LEFT", "BOTTOM")) tmap_arrange(tm1, tm2) ``` Naturally, some options are specific to certain drivers.^[ A list of supported vector formats and options can be found at https://gdal.org/ogr_formats.html. ] For example, think of coordinates stored in a spreadsheet format (`.csv`). To read-in such files as spatial objects, we naturally have to specify the names of the columns (`X` and `Y` in our example below) representing the coordinates. We can do this with the help of the `options` parameter. To find out about possible options, please refer to the 'Open Options' section of the corresponding GDAL\index{GDAL} driver description. For the comma-separated value (csv) format, visit https://gdal.org/drv_csv.html. ```{r 07-read-write-plot-20, results='hide'} cycle_hire_txt = system.file("misc/cycle_hire_xy.csv", package = "spData") cycle_hire_xy = read_sf(cycle_hire_txt, options = c("X_POSSIBLE_NAMES=X", "Y_POSSIBLE_NAMES=Y")) ``` Instead of columns describing 'XY' coordinates, a single column can also contain the geometry information. Well-known text (WKT)\index{well-known text}, well-known binary (WKB)\index{well-known binary}, and the GeoJSON formats are examples of this. For instance, the `world_wkt.csv` file has a column named `WKT` representing polygons of the world's countries. We will again use the `options` parameter to indicate this. ```{r 07-read-write-plot-21, results='hide'} world_txt = system.file("misc/world_wkt.csv", package = "spData") world_wkt = read_sf(world_txt, options = "GEOM_POSSIBLE_NAMES=WKT") ``` ```{block2 07-read-write-plot-22, type='rmdnote'} Not all of the supported vector file formats store information about their coordinate reference system. In these situations, it is possible to add the missing information using the `st_set_crs()` function. Please refer also to Section \@ref(crs-setting) for more information. ``` \index{KML} As a final example, we will show how `read_sf()` also reads KML files. A KML file stores geographic information in XML format, which is a data format for the creation of web pages and the transfer of data in an application-independent way [@nolan_xml_2014]. Here, we access a KML file from the web. This file contains more than one layer. `st_layers()` lists all available layers. We choose the first layer `Placemarks` and say so with the help of the `layer` parameter in `read_sf()`. ```{r 07-read-write-plot-23, out.lines=6} u = "https://developers.google.com/kml/documentation/KML_Samples.kml" download.file(u, "KML_Samples.kml") st_layers("KML_Samples.kml") kml = read_sf("KML_Samples.kml", layer = "Placemarks") ``` All the examples presented in this section so far have used the **sf** package for geographic data import. It is fast and flexible, but it may be worth looking at other packages such as **duckdb**, an R interface to the DuckDB database system which has a [spatial extension](https://duckdb.org/docs/extensions/spatial.html). ### Raster data {#raster-data-read} \index{raster!data input} Similar to vector data, raster data comes in many file formats with some supporting multi-layer files. **terra**'s `rast()` command reads in a single layer when a file with just one layer is provided. ```{r 07-read-write-plot-24, message=FALSE} raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") single_layer = rast(raster_filepath) ``` It also works in case you want to read a multi-layer file. ```{r 07-read-write-plot-25} multilayer_filepath = system.file("raster/landsat.tif", package = "spDataLarge") multilayer_rast = rast(multilayer_filepath) ``` \index{vsicurl} \index{GDAL} \index{COG} All of the previous examples read spatial information from files stored on your hard drive. However, GDAL also allows reading data directly from online resources, such as HTTP/HTTPS/FTP web resources. The only thing we need to do is to add a `/vsicurl/` prefix before the path to the file. Let's try it by connecting to the global monthly snow probability at 500-m resolution for the period 2000-2012. Snow probability for December is stored as a Cloud Optimized GeoTIFF (COG) file (see Section \@ref(file-formats)) at [zenodo.org](https://zenodo.org/record/5774954/files/clm_snow.prob_esacci.dec_p.90_500m_s0..0cm_2000..2012_v2.0.tif). To read an online file, we just need to provide its URL together with the `/vsicurl/` prefix. ```{r, linewidth=80} myurl = paste0("/vsicurl/https://zenodo.org/record/5774954/files/", "clm_snow.prob_esacci.dec_p.90_500m_s0..0cm_2000..2012_v2.0.tif") snow = rast(myurl) snow ``` \index{COG} Due to the fact that the input data is COG, we are actually not reading this file to our RAM, but rather creating a connection to it without obtaining any values. Its values will be read if we apply any value-based operation (e.g., `crop()` or `extract()`). This allows us also to just read a tiny portion of the data without downloading the entire file. For example, we can get the snow probability for December in Reykjavik (70%) by specifying its coordinates and applying the `extract()` function: ```{r} rey = data.frame(lon = -21.94, lat = 64.15) snow_rey = extract(snow, rey) snow_rey ``` This way, we just downloaded a single value instead of the whole, large GeoTIFF file. The above example just shows one simple (but useful) case, but there is more to explore. The `/vsicurl/` prefix also works not only for raster but also for vector file formats. It allows reading vectors directly from online storage with `read_sf()` just by adding the prefix before the vector file URL. Importantly, `/vsicurl/` is not the only prefix provided by GDAL -- many more exist, such as `/vsizip/` to read spatial files from ZIP archives without decompressing them beforehand or `/vsis3/` for on-the-fly reading files available in AWS S3 buckets. You can learn more about it at https://gdal.org/user/virtual_file_systems.html. As with vector data, raster datasets can also be stored in and read from some spatial databases, notably PostGIS. See Section \@ref(postgis) for more details. ## Data output (O) {#data-output} Writing geographic data allows you to convert from one format to another and to save newly created objects. Depending on the data type (vector or raster), object class (e.g., `sf` or `SpatRaster`), and type and amount of stored information (e.g., object size, range of values), it is important to know how to store spatial files in the most efficient way. The next two sections will demonstrate how to do this. ### Vector data \index{vector!data output} ```{r 07-read-write-plot-27, echo=FALSE, results='hide'} world_files = list.files(pattern = "world*") file.remove(world_files) ``` The counterpart of `read_sf()` is `write_sf()`. It allows you to write **sf** objects to a wide range of geographic vector file formats, including the most common such as `.geojson`, `.shp` and `.gpkg`. Based on the file name, `write_sf()` decides automatically which driver to use. The speed of the writing process depends also on the driver. ```{r 07-read-write-plot-28} write_sf(obj = world, dsn = "world.gpkg") ``` **Note**: if you try to write to the same data source again, the function will overwrite the file: ```{r 07-read-write-plot-29, error=TRUE} write_sf(obj = world, dsn = "world.gpkg") ``` Instead of overwriting the file, we could add a new layer to the file by specifying the `layer` argument. This is supported by several spatial formats, including GeoPackage. ```{r 07-read-write-plot-31, results='hide'} write_sf(obj = world, dsn = "world_many_layers.gpkg", layer = "second_layer") ``` Alternatively, you can use `st_write()` since it is equivalent to `write_sf()`. However, it has different defaults -- it does not overwrite files (returns an error when you try to do it) and shows a short summary of the written file format and the object. ```{r 07-read-write-plot-32} st_write(obj = world, dsn = "world2.gpkg") ``` The `layer_options` argument could be also used for many different purposes. One of them is to write spatial data to a text file. This can be done by specifying `GEOMETRY` inside of `layer_options`. It could be either `AS_XY` for simple point datasets (it creates two new columns for coordinates) or `AS_WKT` for more complex spatial data (one new column is created which contains the well-known text representation of spatial objects). ```{r 07-read-write-plot-33, eval=FALSE} write_sf(cycle_hire_xy, "cycle_hire_xy.csv", layer_options = "GEOMETRY=AS_XY") write_sf(world_wkt, "world_wkt.csv", layer_options = "GEOMETRY=AS_WKT") ``` ```{r, echo=FALSE, results='hide'} file.remove(world_files) ``` ### Raster data {#raster-data-write} \index{raster!data output} The `writeRaster()` function saves `SpatRaster` objects to files on disk. The function expects input regarding output data type and file format, but also accepts GDAL options specific to a selected file format (see `?writeRaster` for more details). \index{raster!data types} The **terra** package offers seven data types when saving a raster: INT1U, INT2S, INT2U, INT4S, INT4U, FLT4S, and FLT8S,^[ Using INT4U is not recommended, as R does not support 32-bit unsigned integers. ] which determine the bit representation of the raster object written to disk (Table \@ref(tab:datatypes)). Which data type to use depends on the range of the values of your raster object. The more values a data type can represent, the larger the file will get on disk. Unsigned integers (INT1U, INT2U, INT4U) are suitable for categorical data, while float numbers (FLT4S and FLT8S) usually represent continuous data. `writeRaster()` uses FLT4S as the default. While this works in most cases, the size of the output file will be unnecessarily large if you save binary or categorical data. Therefore, we would recommend to use the data type that needs the least storage space but is still able to represent all values (check the range of values with the `summary()` function). ```{r datatypes, echo=FALSE} dT = tibble::tribble( ~`Data Type`, ~`Minimum Value`, ~`Maximum Value`, "INT1U", "0", "255", "INT2S", "--32,767", "32,767", "INT2U", "0", "65,534", "INT4S", "--2,147,483,647", "2,147,483,647", "INT4U", "0", "4,294,967,296", "FLT4S", "--3.4e+38", "3.4e+38", "FLT8S", "--1.7e+308", "1.7e+308" ) knitr::kable(dT, caption = "Data types supported by the terra package.", caption.short = "Data types supported by the terra package.", booktabs = TRUE) ``` By default, the output file format is derived from the filename. Naming a file `*.tif` will create a GeoTIFF file, as demonstrated below: ```{r 07-read-write-plot-34, eval=FALSE} writeRaster(single_layer, filename = "my_raster.tif", datatype = "INT2U") ``` Some raster file formats have additional options that can be set by providing [GDAL parameters](https://gdal.org/formats_list.html) to the `options` argument of `writeRaster()`. GeoTIFF files are written in **terra**, by default, with the LZW compression `gdal = c("COMPRESS=LZW")`. To change or disable the compression, we need to modify this argument. ```{r 07-read-write-plot-35, eval=FALSE} writeRaster(x = single_layer, filename = "my_raster.tif", gdal = c("COMPRESS=NONE"), overwrite = TRUE) ``` \index{COG} Additionally, we can save our raster object as COG (Cloud Optimized GeoTIFF, Section \@ref(file-formats)) with the `filetype = "COG"` options. ```{r 07-read-write-plot-35b, eval=FALSE} writeRaster(x = single_layer, filename = "my_raster.tif", filetype = "COG", overwrite = TRUE) ``` To learn more about the compression of GeoTIFF files, we recommend Paul Ramsey's [comprehensive blog post, GeoTiff Compression for Dummies](https://blog.cleverelephant.ca/2015/02/geotiff-compression-for-dummies.html) which can be found online. ## Geoportals {#retrieving-data} \index{open data} A vast and ever-increasing amount of geographic data is available on the internet, much of which is free to access and use (with appropriate credit given to its providers).^[For example, visit [freegisdata.rtwilson.com](https://freegisdata.rtwilson.com/) for a long list of websites with freely available geographic datasets.] In some ways there is now *too much* data, in the sense that there are often multiple places to access the same dataset. Some datasets are of poor quality. In this context, it is vital to know where to look, so the first section covers some of the most important sources. Various 'geoportals' (web services providing geospatial datasets such as [Data.gov](https://catalog.data.gov/dataset?metadata_type=geospatial)) are a good place to start, providing a wide range of data but often only for specific locations (as illustrated in the updated [Wikipedia page](https://en.wikipedia.org/wiki/Geoportal) on the topic). \index{geoportals} Some global geoportals overcome this issue. The [GEOSS portal](https://www.geoportal.org/) and the [Copernicus Data Space Ecosystem](https://dataspace.copernicus.eu/), for example, contain many raster datasets with global coverage. A wealth of vector datasets can be accessed from the [SEDAC](https://sedac.ciesin.columbia.edu/) portal run by the National Aeronautics and Space Administration (NASA) and the European Union's [INSPIRE geoportal](http://inspire-geoportal.ec.europa.eu/), with global and regional coverage. Most geoportals provide a graphical interface allowing datasets to be queried based on characteristics such as spatial and temporal extent, the United States Geological Survey's [EarthExplorer](https://earthexplorer.usgs.gov/) being a prime example. *Exploring* datasets interactively on a browser is an effective way of understanding available layers. *Downloading* data is best done with code, however, from reproducibility and efficiency perspectives. Downloads can be initiated from the command line using a variety of techniques, primarily via URLs and APIs\index{API} (see the [Copernicus APIs](https://dataspace.copernicus.eu/analyse/apis) for example).^[An example of using a STAC-API to download Sentinel-2 data is provided in Section \@ref(staccog).] Files hosted on static URLs can be downloaded with `download.file()`, as illustrated in the code chunk below which accesses PeRL: Permafrost Region Pond and Lake Database from [pangaea.de](https://doi.pangaea.de/10.1594/PANGAEA.868349): ```{r 07-read-write-plot-2, eval=FALSE} download.file(url = "https://hs.pangaea.de/Maps/PeRL/PeRL_permafrost_landscapes.zip", destfile = "PeRL_permafrost_landscapes.zip", mode = "wb") unzip("PeRL_permafrost_landscapes.zip") canada_perma_land = read_sf("PeRL_permafrost_landscapes/canada_perma_land.shp") ``` ## Geographic data packages \index{data packages} Many R packages have been developed for accessing geographic data, some of which are presented in Table \@ref(tab:datapackages). These provide interfaces to one or more spatial libraries or geoportals and aim to make data access even quicker from the command line. ```{r datapackages, echo=FALSE, warning=FALSE} datapackages = tibble::tribble( ~`Package`, ~Description, "climateR", "Access over 100,000 gridded climate and landscape datasets from over 2,000 data providers by area of interest.", "elevatr", "Access point and raster elevation data from various sources.", "FedData", "Datasets maintained by the US federal government, including elevation and land cover.", "geodata", "Download and import imports administrative, elevation, WorldClim data.", "osmdata", "Download and import small OpenStreetMap datasets.", "osmextract", "Download and import large OpenStreetMap datasets.", "rnaturalearth", "Access Natural Earth vector and raster data.", "rnoaa", "Import National Oceanic and Atmospheric Administration (NOAA) climate data." ) knitr::kable(datapackages, caption = "Selected R packages for geographic data retrieval.", caption.short = "Selected R packages for geographic data retrieval.", booktabs = TRUE) |> kableExtra::column_spec(1, width = "5em") |> kableExtra::column_spec(2, width = "22em") ``` It should be emphasized that Table \@ref(tab:datapackages) represents only a small number of available geographic data packages. For example, a large number of R packages exist to obtain various socio-demographic data, such as **tidycensus** and **tigris** (USA), **cancensus** (Canada), **eurostat** and **giscoR** (European Union), or **idbr** (international databases) -- read [Analyzing US Census Data](https://walker-data.com/census-r/) [@walker_analyzing_2022] to find some examples of how to analyze such data. Similarly, several R packages exist giving access to spatial data for various regions and countries, such as **bcdata** (Province of British Columbia), **geobr** (Brazil), **RCzechia** (Czech Republic), or **rgugik** (Poland). Each data package has its own syntax for accessing data. This diversity is demonstrated in the subsequent code chunks, which show how to get data using three packages from Table \@ref(tab:datapackages).^[More examples of data downloading using dedicated R packages can be found at https://rspatialdata.github.io/.] Country borders are often useful and these can be accessed with the `ne_countries()` function from the **rnaturalearth** package [@R-rnaturalearth] as follows: ```{r 07-read-write-plot-3} library(rnaturalearth) usa_sf = ne_countries(country = "United States of America", returnclass = "sf") ``` Country borders can be also accessed with other packages, such as **geodata**, **giscoR**, or **rgeoboundaries**. A second example downloads a series of rasters containing global monthly precipitation sums with spatial resolution of 10 minutes (~18.5 km at the equator) using the **geodata** package [@R-geodata]. The result is a multi-layer object of class `SpatRaster`. ```{r 07-read-write-plot-5, eval=FALSE} library(geodata) worldclim_prec = worldclim_global("prec", res = 10, path = tempdir()) class(worldclim_prec) ``` A third example uses the **osmdata** package [@R-osmdata] to find parks from the OpenStreetMap (OSM) database\index{OpenStreetMap}. As illustrated in the code-chunk below, queries begin with the function `opq()` (short for OpenStreetMap query), the first argument of which is a bounding box, or text string representing a bounding box (the city of Leeds, in this case). The result is passed to a function for selecting which OSM elements we're interested in (parks in this case), represented by *key-value pairs*. Next, they are passed to the function `osmdata_sf()` which does the work of downloading the data and converting it into a list of `sf` objects (see `vignette('osmdata')` for further details): ```{r 07-read-write-plot-6, eval=FALSE} library(osmdata) parks = opq(bbox = "leeds uk") |> add_osm_feature(key = "leisure", value = "park") |> osmdata_sf() ``` A limitation with the **osmdata** package is that it is *rate-limited*, meaning that it cannot download large OSM datasets (e.g., all the OSM data for a large city). To overcome this limitation, the **osmextract** package was developed, which can be used to download and import binary `.pbf` files containing compressed versions of the OSM database for predefined regions. OpenStreetMap is a vast global database of crowd-sourced data, is growing daily, and has a wider ecosystem of tools enabling easy access to the data, from the [Overpass turbo](https://overpass-turbo.eu/) web service for rapid development and testing of OSM queries to [osm2pgsql](https://osm2pgsql.org/) for importing the data into a PostGIS database. Although the quality of datasets derived from OSM varies, the data source and wider OSM ecosystems have many advantages: they provide datasets that are available globally, free of charge, and constantly improving thanks to an army of volunteers. Using OSM encourages 'citizen science' and contributions back to the digital commons (you can start editing data representing a part of the world you know well at [www.openstreetmap.org](https://www.openstreetmap.org)). Further examples of OSM data in action are provided in Chapters \@ref(gis), \@ref(transport) and \@ref(location). Sometimes, packages come with built-in datasets. These can be accessed in four ways: by attaching the package (if the package uses 'lazy loading' as **spData** does), with `data(dataset, package = mypackage)`, by referring to the dataset with `mypackage::dataset`, or with `system.file(filepath, package = mypackage)` to access raw data files. The following code chunk illustrates the latter two options using the `world` dataset (already loaded by attaching its parent package with `library(spData)`):^[ For more information on data import with R packages, see Sections 5.5 and 5.6 of @gillespie_efficient_2016. ] ```{r 07-read-write-plot-7, eval=FALSE} world2 = spData::world world3 = read_sf(system.file("shapes/world.gpkg", package = "spData")) ``` The last example, `system.file("shapes/world.gpkg", package = "spData")`, returns a path to the `world.gpkg` file, which is stored inside of the `"shapes/"` folder of the **spData** package. \index{geocoding} Another way to obtain spatial information is to perform geocoding -- transform a description of a location, usually an address, into its coordinates. This is usually done by sending a query to an online service and getting the location as a result. Many such services exist that differ in the used method of geocoding, usage limitations, costs, or application programming interface (API) key requirements. R has several packages for geocoding; however, **tidygeocoder** seems to allow to connect to [the largest number of geocoding services](https://jessecambon.github.io/tidygeocoder/articles/geocoder_services.html) with a consistent interface. The **tidygeocoder** main function is `geocode`, which takes a data frame with addresses and adds coordinates as `"lat"` and `"long"`. This function also allows to select a geocoding service with the `method` argument and has many additional parameters. Let's try this package by searching for coordinates of the John Snow blue plaque located on a building in the Soho district of London. ```{r, eval=FALSE} library(tidygeocoder) geo_df = data.frame(address = "54 Frith St, London W1D 4SJ, UK") geo_df = geocode(geo_df, address, method = "osm") geo_df ``` The resulting data frame can be converted into an `sf` object with `st_as_sf()`. ```{r, eval=FALSE} geo_sf = st_as_sf(geo_df, coords = c("long", "lat"), crs = "EPSG:4326") ``` **tidygeocoder** also allows performing the opposite process called reverse geocoding used to get a set of information (name, address, etc.) based on a pair of coordinates. Geographic data can also be imported into R from various 'bridges' to geographic software, as described in Chapter \@ref(gis). ## Geographic metadata Geographic metadata\index{geographic metadata} are a cornerstone of geographic information management, used to describe datasets, data structures and services. They help make datasets FAIR (Findable, Accessible, Interoperable, Reusable) and are defined by the ISO/OGC standards, in particular the ISO 19115 standard and underlying schemas. These standards are widely used within spatial data infrastructures, handled through metadata catalogs. Geographic metadata can be managed with **geometa**, a package that allows writing, reading and validating geographic metadata according to the ISO/OGC standards. It already supports various international standards for geographic metadata information, such as the ISO 19110 (feature catalogue), legacy ISO 19115-1 and 19115-2 (geographic metadata for vector and gridded/imagery datasets), ISO 19115-3, ISO 19119 (geographic metadata for service), and ISO 19136 (Geographic Markup Language) providing methods to read, validate and write geographic metadata from R using the ISO/TS 19139 (XML) and ISO 19115-3 technical specifications. Geographic metadata can be created with **geometa** as follows, which creates and saves a metadata file: ```{r 07-read-write-plot-m5, eval=FALSE} library(geometa) # create a metadata md = ISOMetadata$new() #... fill the metadata 'md' object # validate metadata md$validate() # XML representation of the ISOMetadata xml = md$encode() # save metadata md$save("my_metadata.xml") # read a metadata from an XML file md = readISO19139("my_metadata.xml") ``` The package comes with more [examples](https://github.com/eblondel/geometa/tree/master/inst/extdata/examples) and has been extended by packages such as **[geoflow](https://github.com/r-geoflow/geoflow)** to ease and automate the management of metadata. In the field of standard geographic information management, the distinction between data and metadata is less clear. The Geography Markup Language (GML) standard and file format covers both data and metadata, for example. The **geometa** package allows exporting GML (ISO 19136) objects from geometry objects modeled with **sf**. Such functionality allows use of geographic metadata (enabling the inclusion of metadata on detailed geographic and temporal extents, rather than simple bounding boxes, for example) and the provision of services that extend the GML standard (e.g., Open Geospatial Consortium Web Coverage Service, OGC-WCS). ## Geographic web services \index{geographic web services} In an effort to standardize web APIs for accessing spatial data, the Open Geospatial Consortium (OGC) has created a number of standard specifications for web services (collectively known as OWS, which is short for OGC Web Services). These services complement and use core standards developed to model geographic information, such as the [ISO/OGC Spatial Schema (ISO 19107:2019)](https://www.iso.org/standard/66175.html), or the [Simple Features (ISO 19125-1:2004)](https://www.iso.org/standard/40114.html), and to format data, such as with the [Geographic Markup Language (GML)](https://www.iso.org/standard/75676.html). These specifications cover common access services for data and metadata. Vector data can be accessed with the Web Feature Service (WFS)\index{geographic web services!WFS}, whereas grid/imagery can be accessed with the Web Coverage Service (WCS)\index{geographic web services!WCS}. Map image representations, such as tiles, can be accessed with the Web Map Service (WMS)\index{geographic web services!WMS} or the Web Map Tile Service (WMTS)\index{geographic web services!WMTS}. Metadata is also covered by means of the Catalogue Service for the Web (CSW)\index{geographic web services!CSW}. Finally, standard processing is handled through the Web Processing Service (WPS)\index{geographic web services!WPS} or the the Web Coverage Processing Service (WCPS)\index{geographic web services!WCPS}. Various open-source projects have adopted these protocols, such as [GeoServer](https://geoserver.org/) and [MapServer](https://mapserver.org/) for data-handling, or [GeoNetwork](https://geonetwork-opensource.org/) and [PyCSW](https://pycsw.org/) for metadata-handling, leading to standardization of queries. Integrated tools for Spatial Data Infrastructures (SDI), such as [GeoNode](https://geonode.org/), [GeOrchestra](https://www.georchestra.org/) or [Examind](https://www.examind.com/) have also adopted these standard webservices, either directly or by using previously mentioned open-source tools. Like other web APIs, OWS APIs use a 'base URL', an 'endpoint' and 'URL query arguments' following a `?` to request data (see the [`best-practices-api-packages`](https://httr.r-lib.org/articles/api-packages.html) vignette in the **httr** package). There are many requests that can be made to a OWS service. Below examples illustrate how some requests can be made directly with **httr** or more straightforward with the **ows4R** package (OGC Web-Services for R). Let's start with examples using the **httr** package, which can be useful for understanding how web services work. One of the most fundamental requests is `getCapabilities`, demonstrated with **httr** functions `GET()` and `modify_url()` below. The following code chunk demonstrates how API\index{API} queries can be constructed and dispatched, in this case to discover the capabilities of a service run by the Fisheries and Aquaculture Division of the Food and Agriculture Organization of the United Nations (UN-FAO). ```{r 07-read-write-plot-8} library(httr) base_url = "https://www.fao.org" endpoint = "/fishery/geoserver/wfs" q = list(request = "GetCapabilities") res = GET(url = modify_url(base_url, path = endpoint), query = q) res$url ``` The above code chunk demonstrates how API\index{API} requests can be constructed programmatically with the `GET()` function, which takes a base URL and a list of query parameters that can easily be extended. The result of the request is saved in `res`, an object of class `response` defined in the **httr** package, which is a list containing information of the request, including the URL. As can be seen by executing `browseURL(res$url)`, the results can also be read directly in a browser. One way of extracting the contents of the request is as follows: ```{r 07-read-write-plot-9, eval=FALSE} txt = content(res, "text") xml = xml2::read_xml(txt) xml #> {xml_document} ... #> [1] \n GeoServer WFS... #> [2] \n UN-FAO Fishe... #> ... ``` Data can be downloaded from WFS services with the `GetFeature` request and a specific `typeName` (as illustrated in the code chunk below). ```{r 07-read-write-plot-11, echo=FALSE, eval=FALSE} library(XML) library(curl) library(httr) base_url = "https://www.fao.org/fishery/geoserver/wfs" q = list(request = "GetCapabilities") res = GET(url = base_url, query = q) doc = xmlParse(res) root = xmlRoot(doc) names(root) names(root[["FeatureTypeList"]]) root[["FeatureTypeList"]][["FeatureType"]][["Name"]] tmp = xmlSApply(root[["FeatureTypeList"]], function(x) xmlValue(x[["Name"]])) ``` Available names differ depending on the accessed web feature service. One can extract them programmatically using web technologies [@nolan_xml_2014] or scrolling manually through the contents of the `GetCapabilities` output in a browser. ```{r 07-read-write-plot-12, eval=FALSE} library(sf) sf::sf_use_s2(FALSE) qf = list(request = "GetFeature", typeName = "fifao:FAO_MAJOR") file = tempfile(fileext = ".gml") GET(url = base_url, path = endpoint, query = qf, write_disk(file)) fao_areas = read_sf(file) ``` In order to keep geometry validity along the data access chain, and since standards and underlying open-source server solutions (such as GeoServer) have been built on the Simple Features access, it is important to deactivate the new default behavior introduced in **sf**, and to not use the S2 geometry model at data access time. This is done with above code `sf::sf_use_s2(FALSE)`. Also note the use of `write_disk()` to ensure that the results are written to disk rather than loaded into memory, allowing them to be imported with **sf**. For many everyday tasks, however, a higher-level interface may be more appropriate, and a number of R packages, and tutorials, have been developed precisely for this purpose. The package **ows4R** has been developed for working with OWS services. It provides a stable interface to common access services, such as the WFS, WCS for data, CSW for metadata, and WPS for processing. The OGC services coverage is described in the README of the package, hosted at [github.com/eblondel/ows4R](https://github.com/eblondel/ows4R?tab=readme-ov-file#ogc-standards-coverage-status), with new standard protocols under investigation/development. Based on the above example, the code below shows how to perform `getCapabilities` and `getFeatures` operations with this package. The **ows4R** package relies on the principle of clients. To interact with an OWS service (such as WFS), a client is created as follows: ```{r 07-read-write-plot-12b, eval=FALSE} library(ows4R) WFS = WFSClient$new( url = "https://www.fao.org/fishery/geoserver/wfs", serviceVersion = "1.0.0", logger = "INFO" ) ``` The operations are then accessible from this client object, e.g., `getCapabilities` or `getFeatures`. ```{r 07-read-write-plot-12c, eval=FALSE} library(ows4R) caps = WFS$getCapabilities() features = WFS$getFeatures("fifao:FAO_MAJOR") ``` As explained before, when accessing data with OGC services, handling **sf** features should be done by deactivating the new default behavior introduced in **sf**, with `sf::sf_use_s2(FALSE)`. This is done by default with **ows4R**. Additional examples are available through vignettes, such as [how to access raster data with the WCS](https://cran.r-project.org/web/packages/ows4R/vignettes/wcs.html), or [how to access metadata with the CSW](https://cran.r-project.org/web/packages/ows4R/vignettes/csw.html). ## Visual outputs \index{map-making!outputs} R supports many different static and interactive graphics formats. Chapter \@ref(adv-map) covers map-making in detail, but it is worth mentioning ways to output visualizations here. The most general method to save a static plot is to open a graphic device, create a plot, and close it, for example: ```{r 07-read-write-plot-36, eval=FALSE} png(filename = "lifeExp.png", width = 500, height = 350) plot(world["lifeExp"]) dev.off() ``` Other available graphic devices include `pdf()`, `bmp()`, `jpeg()`, and `tiff()`. You can specify several properties of the output plot, including width, height and resolution. \index{tmap (package)!saving maps} Additionally, several graphic packages provide their own functions to save a graphical output. For example, the **tmap** package has the `tmap_save()` function. You can save a `tmap` object to different graphic formats or an HTML file by specifying the object name and a file path to a new file. ```{r 07-read-write-plot-37, eval=FALSE} library(tmap) tmap_obj = tm_shape(world) + tm_polygons(col = "lifeExp") tmap_save(tmap_obj, filename = "lifeExp_tmap.png") ``` On the other hand, you can save interactive maps created in the **mapview** package as an HTML file or image using the `mapshot2()` function: ```{r 07-read-write-plot-38, eval=FALSE} library(mapview) mapview_obj = mapview(world, zcol = "lifeExp", legend = TRUE) mapshot2(mapview_obj, url = "my_interactive_map.html") ``` ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_08-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 09-mapping.Rmd ================================================ # (PART) Extensions {-} # Making maps with R {#adv-map} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires the following packages that we have already been using: ```{r 08-mapping-1, message=FALSE } library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ``` - The main package used in this chapter is **tmap**. We recommend you to install its development version from the [r-universe](https://r-universe.dev/) repository, which is updated more frequently than the CRAN version: ```{r} #| eval: false install.packages("tmap", repos = c("https://r-tmap.r-universe.dev", "https://cloud.r-project.org")) ``` - It uses the following visualization packages (also install **shiny** if you want to develop interactive mapping applications): ```{r 08-mapping-2, message=FALSE} library(tmap) # for static and interactive maps library(leaflet) # for interactive maps library(ggplot2) # tidyverse data visualization package ``` - You also need to read-in a couple of datasets as follows for Section \@ref(spatial-ras): ```{r 04-spatial-operations-1-1} nz_elev = rast(system.file("raster/nz_elev.tif", package = "spDataLarge")) ``` ## Introduction A satisfying and important aspect of geographic research is communicating the results. Map-making\index{map-making} --- the art of cartography --- is an ancient skill involving communication, attention to detail, and an element of creativity. Static mapping in R is straightforward with the `plot()` function, as we saw in Section \@ref(basic-map). It is possible to create advanced maps using base R methods [@murrell_r_2016]. The focus of this chapter, however, is cartography with dedicated map-making packages. When learning a new skill, it makes sense to gain depth-of-knowledge in one area before branching out. Map-making is no exception, hence this chapter's coverage of one package (**tmap**) in depth rather than many superficially. In addition to being fun and creative, cartography also has important practical applications. A carefully crafted map can be the best way of communicating the results of your work, but poorly designed maps can leave a bad impression. Common design issues include poor placement, size and readability of text and careless selection of colors, as outlined in the [style guide](https://files.taylorandfrancis.com/TJOM-suppmaterial-quick-guide.pdf) of the *Journal of Maps*. Furthermore, poor map-making can hinder the communication of results [@brewer_designing_2015]: > Amateur-looking maps can undermine your audience’s ability to understand important information and weaken the presentation of a professional data investigation. Maps have been used for several thousand years for a wide variety of purposes. Historic examples include maps of buildings and land ownership in the Old Babylonian dynasty more than 3000 years ago and Ptolemy's world map in his masterpiece *Geography*\index{geography} nearly 2000 years ago [@talbert_ancient_2014]. Map-making has historically been an activity undertaken only by, or on behalf of, the elite. This has changed with the emergence of open source mapping software such as the R package **tmap** and the 'print layout' in QGIS\index{QGIS}, which allow anyone to make high-quality maps, enabling 'citizen science'. Maps are also often the best way to present the findings of geocomputational research in a way that is accessible. Map-making is therefore a critical part of geocomputation\index{geocomputation} and its emphasis is not only on describing, but also *changing* the world. This chapter shows how to make a wide range of maps. The next section covers a range of static maps, including aesthetic considerations, facets and inset maps. Sections \@ref(animated-maps) to \@ref(mapping-applications) cover animated and interactive maps (including web maps and mapping applications). Finally, Section \@ref(other-mapping-packages) covers a range of alternative map-making packages including **ggplot2** and **cartogram**. ## Static maps \index{map-making!static maps} Static maps are the most common type of visual output from geocomputation. They are usually stored in standard formats including `.png` and `.pdf` for *graphical* raster and vector outputs, respectively. Initially, static maps were the only type of maps that R could produce. Things have advanced with the release of **sp** [see @pebesma_classes_2005], and many map-making techniques, functions, and packages have been developed since then. However, despite the innovation of interactive mapping, static plotting was still the emphasis of geographic data visualization in R a decade later [@cheshire_spatial_2015]. The generic `plot()` function is often the fastest way to create static maps from vector and raster spatial objects (see Sections \@ref(basic-map) and \@ref(basic-map-raster)). Sometimes, simplicity and speed are priorities, especially during the development phase of a project, and this is where `plot()` excels. The base R approach is also extensible, with `plot()` offering dozens of arguments. Another approach is the **grid** package which allows low-level control of static maps, as illustrated in chapter [14](https://www.stat.auckland.ac.nz/~paul/RG2e/chapter14.html) of @murrell_r_2016. This part of the book focuses on **tmap** and emphasizes the essential aesthetic and layout options. \index{tmap (package)} **tmap** is a powerful and flexible map-making package with sensible defaults. It has a concise syntax that allows for the creation of attractive maps with minimal code which will be familiar to **ggplot2** users. It also has the unique capability to generate static and interactive maps using the same code via `tmap_mode()`. Finally, it accepts a wider range of spatial classes (including **sf** and **terra** objects) than alternatives such as **ggplot2**. ### tmap basics \index{tmap (package)!basics} Like **ggplot2**, **tmap** is based on the idea of a 'grammar of graphics' [@wilkinson_grammar_2005]. This involves a separation between the input data and the aesthetics (how data are visualized): each input dataset can be 'mapped' in a range of different ways including location on the map (defined by data's `geometry`), color, and other visual variables. The basic building block is `tm_shape()` (which defines input data: a vector or raster object), followed by one or more layer elements such as `tm_fill()` and `tm_dots()`. This layering is demonstrated in the chunk below, which generates the maps presented in Figure \@ref(fig:tmshape): ```{r 08-mapping-3, eval=FALSE} # Add fill layer to nz shape tm_shape(nz) + tm_fill() # Add border layer to nz shape tm_shape(nz) + tm_borders() # Add fill and border layers to nz shape tm_shape(nz) + tm_fill() + tm_borders() ``` ```{r tmshape, echo=FALSE, message=FALSE, fig.cap="New Zealand's shape plotted with fill (left), border (middle) and fill and border (right) layers added using tmap functions.", fig.scap="New Zealand's shape plotted using tmap functions."} source("https://github.com/geocompx/geocompr/raw/main/code/09-tmshape.R", print.eval = TRUE) ``` The object passed to `tm_shape()` in this case is `nz`, an `sf` object representing the regions of New Zealand (see Section \@ref(intro-sf) for more on `sf` objects). Layers are added to represent `nz` visually, with `tm_fill()` and `tm_borders()` creating shaded areas (left panel) and border outlines (middle panel) in Figure \@ref(fig:tmshape), respectively. \index{map-making!layers} This is an intuitive approach to map-making: the common task of *adding* new layers is undertaken by the addition operator `+`, followed by `tm_*()`. The asterisk (\*) refers to a wide range of layer types which have self-explanatory names including: - `tm_fill()`: shaded areas for (multi)polygons - `tm_borders()`: border outlines for (multi)polygons - `tm_polygons()`: both, shaded areas and border outlines for (multi)polygons - `tm_lines()`: lines for (multi)linestrings - `tm_symbols()`: symbols for (multi)points, (multi)linestrings, and (multi)polygons - `tm_raster()`: colored cells of raster data (there is also `tm_rgb()` for rasters with three layers) - `tm_text()`: text information for (multi)points, (multi)linestrings, and (multi)polygons This layering is illustrated in the right panel of Figure \@ref(fig:tmshape), the result of adding a border *on top of* the fill layer. ```{block2 qtm, type = 'rmdnote'} `qtm()` is a handy function to create **q**uick **t**hematic **m**aps (hence the snappy name). It is concise and provides a good default visualization in many cases: `qtm(nz)`, for example, is equivalent to `tm_shape(nz) + tm_fill() + tm_borders()`. Further, layers can be added concisely using multiple `qtm()` calls, such as `qtm(nz) + qtm(nz_height)`. The disadvantage is that it makes aesthetics of individual layers harder to control, explaining why we avoid teaching it in this chapter. ``` ### Map objects {#map-obj} A useful feature of **tmap** is its ability to store *objects* representing maps. The code chunk below demonstrates this by saving the last plot in Figure \@ref(fig:tmshape) as an object of class `tmap` (note the use of `tm_polygons()` which condenses `tm_fill() + tm_borders()` into a single function): ```{r 08-mapping-4} map_nz = tm_shape(nz) + tm_polygons() class(map_nz) ``` `map_nz` can be plotted later, for example by adding other layers (as shown below) or simply running `map_nz` in the console, which is equivalent to `print(map_nz)`. New *shapes* can be added with `+ tm_shape(new_obj)`. In this case, `new_obj` represents a new spatial object to be plotted on top of preceding layers. When a new shape is added in this way, all subsequent aesthetic functions refer to it, until another new shape is added. This syntax allows the creation of maps with multiple shapes and layers, as illustrated in the next code chunk which uses the function `tm_raster()` to plot a raster layer (with `col_alpha` set to make the layer semi-transparent): ```{r 08-mapping-5, results='hide'} map_nz1 = map_nz + tm_shape(nz_elev) + tm_raster(col_alpha = 0.7) ``` Building on the previously created `map_nz` object, the preceding code creates a new map object `map_nz1` that contains another shape (`nz_elev`) representing average elevation across New Zealand (see Figure \@ref(fig:tmlayers), left). More shapes and layers can be added, as illustrated in the code chunk below which creates `nz_water`, representing New Zealand's [territorial waters](https://en.wikipedia.org/wiki/Territorial_waters), and adds the resulting lines to an existing map object. ```{r 08-mapping-6} nz_water = st_union(nz) |> st_buffer(22200) |> st_cast(to = "LINESTRING") map_nz2 = map_nz1 + tm_shape(nz_water) + tm_lines() ``` There is no limit to the number of layers or shapes that can be added to `tmap` objects, and the same shape can even be used multiple times. The final map illustrated in Figure \@ref(fig:tmlayers) is created by adding a layer representing high points (stored in the object `nz_height`) onto the previously created `map_nz2` object with `tm_symbols()` (see `?tm_symbols` for details on **tmap**'s point plotting functions). The resulting map, which has four layers, is illustrated in the right-hand panel of Figure \@ref(fig:tmlayers): ```{r 08-mapping-7} map_nz3 = map_nz2 + tm_shape(nz_height) + tm_symbols() ``` \index{map-making!metaplot} A useful and little known feature of **tmap** is that multiple map objects can be arranged in a single 'metaplot' with `tmap_arrange()`. This is demonstrated in the code chunk below which plots `map_nz1` to `map_nz3`, resulting in Figure \@ref(fig:tmlayers). ```{r tmlayers, message=FALSE, fig.cap="Maps with added layers to the final map of Figure 9.1.", fig.scap="Additional layers added to the output of Figure 9.1."} tmap_arrange(map_nz1, map_nz2, map_nz3) ``` More elements can also be added with the `+` operator. Aesthetic settings, however, are controlled by arguments to layer functions. ### Visual variables \index{map-making!aesthetics} \index{map-making!visual variables} The plots in the previous section demonstrate **tmap**'s default aesthetic settings. Gray shades are used for `tm_fill()` and `tm_symbols()` layers and a continuous black line is used to represent lines created with `tm_lines()`. Of course, these default values and other aesthetics can be overridden. The purpose of this section is to show how. There are two main types of map aesthetics: those that change with the data and those that are constant. Unlike **ggplot2**, which uses the helper function `aes()` to represent variable aesthetics, **tmap** accepts a few aesthetic arguments, depending on a selected layer type: - `fill`: fill color of a polygon - `col`: color of a polygon border, line, point, or raster - `lwd`: line width - `lty`: line type - `size`: size of a symbol - `shape`: shape of a symbol Additionally, we may customize the fill and border color transparency using `fill_alpha` and `col_alpha`. To map a variable to an aesthetic, pass its column name to the corresponding argument, and to set a fixed aesthetic, pass the desired value instead.^[ If there is a clash between a fixed value and a column name, the column name takes precedence. This can be verified by running the next code chunk after running `nz$red = 1:nrow(nz)`. ] The impact of setting these with fixed values is illustrated in Figure \@ref(fig:tmstatic). ```{r tmstatic, message=FALSE, fig.cap="Impact of changing commonly used fill and border aesthetics to fixed values.", fig.scap="The impact of changing commonly used aesthetics."} ma1 = tm_shape(nz) + tm_polygons(fill = "red") ma2 = tm_shape(nz) + tm_polygons(fill = "red", fill_alpha = 0.3) ma3 = tm_shape(nz) + tm_polygons(col = "blue") ma4 = tm_shape(nz) + tm_polygons(lwd = 3) ma5 = tm_shape(nz) + tm_polygons(lty = 2) ma6 = tm_shape(nz) + tm_polygons(fill = "red", fill_alpha = 0.3, col = "blue", lwd = 3, lty = 2) tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6) ``` Like base R plots, arguments defining aesthetics can also receive values that vary. Unlike the base R code below (which generates the left panel in Figure \@ref(fig:tmcol)), **tmap** aesthetic arguments will not accept a numeric vector: ```{r 08-mapping-9, eval=FALSE} plot(st_geometry(nz), col = nz$Land_area) # works tm_shape(nz) + tm_fill(fill = nz$Land_area) # fails #> Error: palette should be a character value ``` Instead `fill` (and other aesthetics that can vary such as `lwd` for line layers and `size` for point layers) requires a character string naming an attribute associated with the geometry to be plotted. Thus, one would achieve the desired result as follows (Figure \@ref(fig:tmcol), right panel): ```{r 08-mapping-10, fig.show='hide', message=FALSE} tm_shape(nz) + tm_fill(fill = "Land_area") ``` ```{r tmcol, message=FALSE, fig.cap="Comparison of base (left) and tmap (right) handling of a numeric color field.", fig.scap="Comparison of base graphics and tmap", echo=FALSE, fig.show='hold', warning=FALSE, message=FALSE, fig.height=6, out.width="45%"} plot(nz["Land_area"]) tm_shape(nz) + tm_fill(fill = "Land_area") ``` Each visual variable has three related additional arguments, with suffixes of `.scale`, `.legend`, and `.free`. For example, the `tm_fill()` function has arguments such as `fill`, `fill.scale`, `fill.legend`, and `fill.free`. The `.scale` argument determines how the provided values are represented on the map and in the legend (Section \@ref(scales)), while the `.legend` argument is used to customize the legend settings, such as its title, orientation, or position (Section \@ref(legends)). The `.free` argument is relevant only for maps with many facets to determine if each facet has the same or different scale and legend. ### Scales \index{tmap (package)!scales} Scales control how the values are represented on the map and in the legend, and they largely depend on the selected visual variable. For example, when our visual variable is `col`, then `col.scale` controls how the colors of spatial objects are related to the provided values; and when our visual variable is `size`, then `size.scale` controls how the sizes represent the provided values. By default, the used scale is `tm_scale()`, which selects the visual settings automatically given by the input data type (factor, numeric, and integer). \index{tmap (package)!color breaks} Let's see how the scales work by customizing polygons' fill colors. Color settings are an important part of map design -- they can have a major impact on how spatial variability is portrayed as illustrated in Figure \@ref(fig:tmpal). This figure shows four ways of coloring regions in New Zealand depending on median income, from left to right (and demonstrated in the code chunk below): - The default setting uses 'pretty' breaks, described in the next paragraph - `breaks` allows you to manually set the breaks - `n` sets the number of bins into which numeric variables are categorized - `values` defines the color scheme, for example, `BuGn` ```{r 08-mapping-12, eval=FALSE} tm_shape(nz) + tm_polygons(fill = "Median_income") tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(breaks = c(0, 30000, 40000, 50000))) tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(n = 10)) tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(values = "BuGn")) ``` ```{r tmpal, message=FALSE, fig.cap="Color settings. The results show (from left to right): default settings, manual breaks, n breaks, and the impact of changing the palette.", fig.scap="Color settings.", echo=FALSE, fig.asp=0.56, warning=FALSE} source("https://github.com/geocompx/geocompr/raw/main/code/09-tmpal.R", print.eval = TRUE) ``` ```{block2 break-style-note0, type='rmdnote'} All of the above arguments (`breaks`, `n`, and `values`) also work for other types of visual variables. For example, `values` expects a vector of colors or a palette name for `fill.scale` or `col.scale`, a vector of sizes for `size.scale`, or a vector of symbols for `shape.scale`. ``` \index{tmap (package)!break styles} We are also able to customize scales using a family of functions that start with the `tm_scale_` prefix. The most important ones are `tm_scale_intervals()`, `tm_scale_continuous()`, and `tm_scale_categorical()`. ```{r} #| eval: false #| echo: false tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_intervals()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_continuous()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_categorical()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_continuous_log()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_continuous_log1p()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_discrete()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_ordinal()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_rgb()) tm_shape(nz) + tm_fill(fill = "Land_area", fill.scale = tm_scale_continuous()) tm_shape(nz) + tm_symbols(size = "Land_area", size.scale = tm_scale_intervals()) ``` \index{tmap (package)!interval scale} The `tm_scale_intervals()` function splits the input data values into a set of intervals. In addition to manually setting `breaks`, **tmap** allows users to specify algorithms to create breaks with the `style` argument automatically. The default is `tm_scale_intervals(style = "pretty")`, which rounds breaks into whole numbers where possible and spaces them evenly. Other options are listed below and presented in Figure \@ref(fig:break-styles). - `style = "equal"`: divides input values into bins of equal range and is appropriate for variables with a uniform distribution (not recommended for variables with a skewed distribution as the resulting map may end up having little color diversity) - `style = "quantile"`: ensures the same number of observations fall into each category (with the potential downside that bin ranges can vary widely) - `style = "jenks"`: identifies groups of similar values in the data and maximizes the differences between categories - `style = "log10_pretty"`: a common logarithmic (the logarithm to base 10) version of the regular pretty style used for variables with a right-skewed distribution ```{block2 break-style-note, type='rmdnote'} Although `style` is an argument of **tmap** functions, in fact it originates as an argument in `classInt::classIntervals()` --- see the help page of this function for details. ``` ```{r break-styles, message=FALSE, fig.cap="Different interval scale methods set using the style argument in tmap.", fig.scap="Different binning methods using tmap.", echo=FALSE, warning=FALSE, fig.width=8} source("code/09-break-styles.R", print.eval = TRUE) ``` \index{tmap (package)!continuous scale} The `tm_scale_continuous()` function presents a continuous color field and is particularly suited for continuous rasters (Figure \@ref(fig:concat), left panel). In case of variables with q skewed distribution, you can also use its variants -- `tm_scale_continuous_log()` and `tm_scale_continuous_log1p()`. \index{tmap (package)!categorical scale} Finally, `tm_scale_categorical()` was designed to represent categorical values and ensures that each category receives a unique color (Figure \@ref(fig:concat), right panel). ```{r concat, message=FALSE, fig.cap="Continuous and categorical scales in tmap.", echo=FALSE, fig.width=8} library(tmap) library(spData) library(spDataLarge) m_cont1 = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale_continuous(n = 5)) + tm_title('tm_scale_continuous()', fontfamily = "monospace") + tm_layout(legend.position = tm_pos_auto_in(), scale = 0.9) m_cat1 = tm_shape(nz) + tm_polygons(fill = "Island", fill.scale = tm_scale_categorical()) + tm_title('tm_scale_categorical()', fontfamily = "monospace") + tm_layout(legend.position = tm_pos_auto_in(), scale = 0.9) tmap_arrange(m_cont1, m_cat1) ``` \index{tmap (package)!palettes} Palettes define the color ranges associated with the bins and determined by the `tm_scale_*()` functions, and its `breaks` and `n` arguments described above. It expects a vector of colors or a new color palette name, which can be found interactively with `cols4all::c4a_gui()`. You can also add a `-` as the color palette name prefix to reverse the palette order. ```{block2 visual-vars-values, type='rmdnote'} All of the default `values` of the visual variables, such as default color palettes for different types of input variables, can be found with `tmap_options()`. For example, run `tmap_options()$values.var`. ``` \index{color palettes} There are three main groups of color palettes\index{map-making!color palettes}: categorical, sequential and diverging (Figure \@ref(fig:colpal)), and each of them serves a different purpose.^[ A fourth group of color palettes, called bivariate, also exists. They are used when we want to represent relations between two variables on one map. ] Categorical palettes consist of easily distinguishable colors and are most appropriate for categorical data without any particular order such as state names or land cover classes. Colors should be intuitive: rivers should be blue, for example, and pastures green. Avoid too many categories: maps with large legends and many colors can be uninterpretable.^[`fill = "MAP_COLORS"` can be used in maps with a large number of individual polygons (for example, a map of individual countries) to create unique fill colors for adjacent polygons.] The second group is sequential palettes. These follow a gradient, for example from light to dark colors (light colors often tend to represent lower values), and are appropriate for continuous (numeric) variables. Sequential palettes can be single (`greens` goes from light to dark green, for example) or multi-color/hue (`yl_gn_bu` is gradient from light yellow to blue via green, for example), as demonstrated in the code chunk below --- output not shown, run the code yourself to see the results! ```{r 08-mapping-13, eval=FALSE} tm_shape(nz) + tm_polygons("Median_income", fill.scale = tm_scale(values = "greens")) tm_shape(nz) + tm_polygons("Median_income", fill.scale = tm_scale(values = "yl_gn_bu")) ``` The third group, diverging palettes, typically range between three distinct colors (purple-white-green in Figure \@ref(fig:colpal)) and are usually created by joining two single-color sequential palettes with the darker colors at each end. Their main purpose is to visualize the difference from an important reference point, e.g., a certain temperature, the median household income or the mean probability for a drought event. The reference point's value can be adjusted in **tmap** using the `midpoint` argument. ```{r 08-mapping-13b, eval=FALSE} tm_shape(nz) + tm_polygons("Median_income", fill.scale = tm_scale_continuous(values = "pu_gn_div", midpoint = 28000)) ``` ```{r colpal, echo=FALSE, message=FALSE, fig.cap="Examples of categorical, sequential and diverging palettes.", out.width="75%"} library(cols4all) many_palette_plotter = function(color_names, n, titles){ n_colors = length(color_names) ylim = c(0, n_colors) par(mar = c(0, 5, 0, 0)) plot(1, 1, xlim = c(0, max(n)), ylim = ylim, type = "n", axes = FALSE, bty = "n", xlab = "", ylab = "") for(i in seq_len(n_colors)){ one_color = cols4all::c4a(n = n, palette = color_names[i]) rect(xleft = 0:(n - 1), ybottom = i - 1, xright = 1:n, ytop = i - 0.2, col = one_color, border = "light gray") } text(rep(-0.1, n_colors), (1: n_colors) - 0.6, labels = titles, xpd = TRUE, adj = 1) } all_default_pals = tmap_options()$values.var$fill many_palette_plotter(c(all_default_pals$div, all_default_pals$seq, all_default_pals$unord), 7, titles = c("Diverging", "Sequential", "Categorical")) ``` There are two important principles for consideration when working with colors: perceptibility and accessibility. Firstly, colors on maps should match our perception. This means that certain colors are viewed through our experience and also cultural lenses. For example, green colors usually represent vegetation or lowlands, and blue is connected with water or coolness. Color palettes should also be easy to understand to effectively convey information. It should be clear which values are lower and which are higher, and colors should change gradually. Secondly, changes in colors should be accessible to the largest number of people. Therefore, it is important to use colorblind friendly palettes as often as possible.^[See the "Color vision" options and the "Color Blind Friendliness" panel in `cols4all::c4a_gui()`.] ### Legends \index{tmap (package)!legends} After we decided on our visual variable and its properties, we should move our attention toward the related map legend style. Using the `tm_legend()` function, we may change its title, position, orientation, or even disable it. The most important argument in this function is `title`, which sets the title of the associated legend. In general, a map legend title should provide two pieces of information: what the legend represents and what the units are of the presented variable. The following code chunk demonstrates this functionality by providing a more attractive name than the variable name `Land_area` (note the use of `expression()` to create superscript text): ```{r 08-mapping-11} #| eval: false legend_title = expression("Area (km"^2*")") tm_shape(nz) + tm_polygons(fill = "Land_area", fill.legend = tm_legend(title = legend_title)) ``` The default legend orientation in **tmap** is `"portrait"`, however, an alternative legend orientation, `"landscape"`, is also possible. Other than that, we can also customize the location of the legend using the `position` argument. ```{r} #| eval: false tm_shape(nz) + tm_polygons(fill = "Land_area", fill.legend = tm_legend(title = legend_title, orientation = "landscape", position = tm_pos_out("center", "bottom"))) ``` ```{r} #| eval: false #| echo: false legend_title = expression("Area (km"^2*")") map_nza = tm_shape(nz) + tm_polygons(fill = "Land_area", fill.legend = tm_legend(title = legend_title), position = tm_pos_out("right", "top")) map_nza2 = tm_shape(nz) + tm_polygons(fill = "Land_area", fill.legend = tm_legend(title = legend_title, orientation = "landscape", position = tm_pos_out("center", "bottom"))) tmap_arrange(map_nza, map_nza2) ``` The legend position (and also the position of several other map elements in **tmap**) can be customized using one of a few functions. The two most important are: - `tm_pos_out()`: the default, adds the legend outside of the map frame area. We can customize its location with two values that represent the horizontal position (`"left"`, `"center"`, or `"right"`), and the vertical position (`"bottom"`, `"center"`, or `"top"`) - `tm_pos_in()`: puts the legend inside of the map frame area. We may decide on its position using two arguments, where the first one can be `"left"`, `"center"`, or `"right"`, and the second one can be `"bottom"`, `"center"`, or `"top"`. Alternatively, we may just provide a vector of two values (or two numbers between 0 and 1) here -- and in such case, the legend will be put inside the map frame. ### Layouts \index{tmap (package)!layouts} The map layout refers to the combination of all map elements into a cohesive map. Map elements include among others the objects to be mapped, the map grid, the scale bar, the title, and margins, while the color settings covered in the previous section relate to the palette and breakpoints used to affect how the map looks. Both may result in subtle changes that can have an equally large impact on the impression left by your maps. Additional map elements such as graticules \index{tmap (package)!graticules}, north arrows\index{tmap (package)!north arrows}, scale bars\index{tmap (package)!scale bars} and map titles have their own functions: `tm_graticules()`, `tm_compass()`, `tm_scalebar()`, and `tm_title()` (Figure \@ref(fig:na-sb)).^[Another additional map elements include `tm_grid()`, `tm_logo()` and `tm_credits()`.] ```{r na-sb, message=FALSE, fig.cap="Map with additional elements: a north arrow and scale bar.", out.width="65%", fig.asp=1, fig.scap="Map with a north arrow and scale bar."} map_nz + tm_graticules() + tm_compass(type = "8star", position = c("left", "top")) + tm_scalebar(breaks = c(0, 100, 200), text.size = 1, position = c("left", "top")) + tm_title("New Zealand") ``` **tmap** also allows a wide variety of layout settings to be changed, some of which, produced using the following code (see `args(tm_layout)` or `?tm_layout` for a full list), are illustrated in Figure \@ref(fig:layout1). ```{r 08-mapping-14, eval=FALSE} map_nz + tm_layout(scale = 4) map_nz + tm_layout(bg.color = "lightblue") map_nz + tm_layout(frame = FALSE) ``` ```{r layout1, message=FALSE, fig.cap="Layout options specified by (from left to right) scale, bg.color, and frame arguments.", fig.scap="Layout options specified by the tmap arguments.", echo=FALSE, fig.asp=0.56} source("https://github.com/geocompx/geocompr/raw/main/code/09-layout1.R", print.eval = TRUE) ``` The other arguments in `tm_layout()` provide control over many more aspects of the map in relation to the canvas on which it is placed. Here are some useful layout settings (some of which are illustrated in Figure \@ref(fig:layout2)): - Margin settings including `inner.margin` and `outer.margin` - Font settings controlled by `fontface` and `fontfamily` - Legend settings including options such as `legend.show` (whether or not to show the legend) `legend.orientation`, `legend.position`, and `legend.frame` - Frame width (`frame.lwd`) and an option to allow double lines (`frame.double.line`) - Color settings controlling `color.sepia.intensity` (how *yellowy* the map looks) and `color.saturation` (a color-grayscale) ```{r layout2, message=FALSE, fig.cap="Selected layout options.", echo=FALSE, warning=FALSE, fig.width=8} source("code/09-layout2.R", print.eval = TRUE) ``` ### Faceted maps \index{map-making!faceted maps} \index{tmap (package)!faceted maps} Faceted maps, also referred to as 'small multiples', are composed of many maps arranged side-by-side, and sometimes stacked vertically [@meulemans_small_2017]. Facets enable the visualization of how spatial relationships change with respect to another variable, such as time. The changing populations of settlements, for example, can be represented in a faceted map with each panel representing the population at a particular moment in time. The time dimension could be represented via another *visual variable* such as color. However, this risks cluttering the map because it will involve multiple overlapping points (cities do not tend to move over time!). Typically all individual facets in a faceted map contain the same geometry data repeated multiple times, once for each column in the attribute data (this is the default plotting method for `sf` objects, see Chapter \@ref(spatial-class)). However, facets can also represent shifting geometries such as the evolution of a point pattern over time. This use case of a faceted plot is illustrated in Figure \@ref(fig:urban-facet). ```{r urban-facet, message=FALSE, fig.cap="Faceted map showing the top 30 largest urban agglomerations from 1970 to 2030 based on population projections by the United Nations.", fig.scap="Faceted map showing urban agglomerations.", fig.asp=0.5} urb_1970_2030 = urban_agglomerations |> filter(year %in% c(1970, 1990, 2010, 2030)) tm_shape(world) + tm_polygons() + tm_shape(urb_1970_2030) + tm_symbols(fill = "black", col = "white", size = "population_millions") + tm_facets_wrap(by = "year", nrow = 2) ``` The preceding code chunk demonstrates key features of faceted maps created using the `tm_facets_wrap()` function: - Shapes that do not have a facet variable are repeated (countries in `world` in this case) - The `by` argument which varies depending on a variable (`"year"` in this case) - The `nrow`/`ncol` setting specifying the number of rows and columns that facets should be arranged into Alternatively, it is possible to use the `tm_facets_grid()` function that allows to have facets based on up to three different variables: one for `rows`, one for `columns`, and possibly one for `pages`. In addition to their utility for showing changing spatial relationships, faceted maps are also useful as the foundation for animated maps (see Section \@ref(animated-maps)). ### Inset maps \index{map-making!inset maps} \index{tmap (package)!inset maps} An inset map is a smaller map rendered within or next to the main map. It could serve many different purposes, including providing a context (Figure \@ref(fig:insetmap1)) or bringing some non-contiguous regions closer to ease their comparison (Figure \@ref(fig:insetmap2)). They could be also used to focus on a smaller area in more detail or to cover the same area as the map, but representing a different topic. In the example below, we create a map of the central part of New Zealand's Southern Alps. Our inset map will show where the main map is in relation to the whole New Zealand. The first step is to define the area of interest, which can be done by creating a new spatial object, `nz_region`. ```{r 08-mapping-16} nz_region = st_bbox(c(xmin = 1340000, xmax = 1450000, ymin = 5130000, ymax = 5210000), crs = st_crs(nz_height)) |> st_as_sfc() ``` In the second step, we create a base-map showing New Zealand's Southern Alps area. This is a place where the most important message is stated. ```{r 08-mapping-17} nz_height_map = tm_shape(nz_elev, bbox = nz_region) + tm_raster(col.scale = tm_scale_continuous(values = "YlGn"), col.legend = tm_legend(position = c("left", "top"))) + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 1) + tm_scalebar(position = c("left", "bottom")) ``` The third step consists of the inset map creation. It gives a context and helps to locate the area of interest. Importantly, this map needs to clearly indicate the location of the main map, for example by stating its borders. ```{r 08-mapping-18} nz_map = tm_shape(nz) + tm_polygons() + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.1) + tm_shape(nz_region) + tm_borders(lwd = 3) + tm_layout(bg.color = "lightblue") ``` One of the main differences between regular charts (e.g., scatterplots) and maps is that the input data determine the aspect ratio of maps. Thus, in this case, we need to calculate the aspect ratios of our two main datasets, `nz_region` and `nz`. The following function, `norm_dim()` returns the normalized width (`"w"`) and height (`"h"`) of the object (as `"snpc"` units understood by the graphic device). ```{r, message=FALSE} library(grid) norm_dim = function(obj){ bbox = st_bbox(obj) width = bbox[["xmax"]] - bbox[["xmin"]] height = bbox[["ymax"]] - bbox[["ymin"]] w = width / max(width, height) h = height / max(width, height) return(unit(c(w, h), "snpc")) } main_dim = norm_dim(nz_region) ins_dim = norm_dim(nz) ``` Next, knowing the aspect ratios, we need to specify the sizes and locations of our two maps -- the main map and the inset map -- using the `viewport()` function. A viewport is part of a graphics device we use to draw the graphical elements at a given moment. The viewport of our main map is just the representation of its aspect ratio. ```{r} main_vp = viewport(width = main_dim[1], height = main_dim[2]) ``` On the other hand, the viewport of the inset map needs to specify its size and location. Here, we would make the inset map twice smaller as the main one by multiplying the width and height by 0.5, and we will locate it 0.5 cm from the bottom right of the main map frame. ```{r} ins_vp = viewport(width = ins_dim[1] * 0.5, height = ins_dim[2] * 0.5, x = unit(1, "npc") - unit(0.5, "cm"), y = unit(0.5, "cm"), just = c("right", "bottom")) ``` Finally, we combine the two maps by creating a new, blank canvas, printing out the main map, and then placing the inset map inside of the main map viewport. ```{r insetmap1, message=FALSE, fig.cap="Inset map providing a context -- location of the central part of the Southern Alps in New Zealand.", fig.scap="Inset map providing a context.", fig.width=9} grid.newpage() print(nz_height_map, vp = main_vp) pushViewport(main_vp) print(nz_map, vp = ins_vp) ``` Inset maps can be saved to file either by using a graphic device (see Section \@ref(visual-outputs)) or the `tmap_save()` function and its arguments: `insets_tm` and `insets_vp`. Inset maps are also used to create one map of non-contiguous areas. Probably, the most often used example is a map of the United States, which consists of the contiguous United States, Hawaii and Alaska. It is very important to find the best projection for each individual inset in these types of cases (see Chapter \@ref(reproj-geo-data) to learn more). We can use US National Atlas Equal Area for the map of the contiguous United States by putting its EPSG code in the `crs` argument of `tm_shape()`. ```{r 08-mapping-19} us_states_map = tm_shape(us_states, crs = "EPSG:9311") + tm_polygons() + tm_layout(frame = FALSE) ``` The rest of our objects, `hawaii` and `alaska`, already have proper projections; therefore, we just need to create two separate maps: ```{r 08-mapping-20} hawaii_map = tm_shape(hawaii) + tm_polygons() + tm_title("Hawaii") + tm_layout(frame = FALSE, bg.color = NA, title.position = c("LEFT", "BOTTOM")) alaska_map = tm_shape(alaska) + tm_polygons() + tm_title("Alaska") + tm_layout(frame = FALSE, bg.color = NA) ``` The final map is created by combining, resizing and arranging these three maps: ```{r insetmap2, message=FALSE, fig.cap="Map of the United States.", warning=FALSE} us_states_map print(hawaii_map, vp = grid::viewport(0.35, 0.1, width = 0.2, height = 0.1)) print(alaska_map, vp = grid::viewport(0.15, 0.15, width = 0.3, height = 0.3)) ``` The code presented above is compact and can be used as the basis for other inset maps, but the results, in Figure \@ref(fig:insetmap2), provide a poor representation of the locations and sizes of Hawaii and Alaska. For a more in-depth approach, see the [`us-map`](https://geocompx.github.io/geocompkg/articles/us-map.html) vignette from the **geocompkg**. ## Animated maps \index{map-making!animated maps} \index{tmap (package)!animated maps} Faceted maps, described in Section \@ref(faceted-maps), can show how spatial distributions of variables change (e.g., over time), but the approach has disadvantages. Facets become tiny when there are many of them. Furthermore, the fact that each facet is physically separated on the screen or page means that subtle differences between facets can be hard to detect. Animated maps solve these issues. Although they depend on digital publication, this is becoming less of an issue as more and more content moves online. Animated maps can still enhance paper reports: you can always link readers to a webpage containing an animated (or interactive) version of a printed map to help make it come alive. There are several ways to generate animations in R, including with animation packages such as **gganimate**, which builds on **ggplot2** (see Section \@ref(other-mapping-packages)). This section focuses on creating animated maps with **tmap** because its syntax will be familiar from previous sections and the flexibility of the approach. Figure \@ref(fig:urban-animated) is a simple example of an animated map. Unlike the faceted plot, it does not squeeze multiple maps into a single screen and allows the reader to see how the spatial distribution of the world's most populous agglomerations evolve over time (see the book's website for the animated version). ```{r urban-animated, message=FALSE, fig.cap="Animated map showing the top 30 largest urban agglomerations from 1950 to 2030 based on population projects by the United Nations. Animated version available online at: r.geocompx.org.", fig.scap="Animated map showing the top 30 largest 'urban agglomerations'.", echo=FALSE, fig.height=3.3} if (knitr::is_latex_output()){ knitr::include_graphics("images/urban-animated.png") } else if (knitr::is_html_output()){ knitr::include_graphics("images/urban-animated.gif") } ``` ```{r 08-mapping-21, echo=FALSE, eval=FALSE} source("https://github.com/geocompx/geocompr/raw/main/code/09-urban-animation.R") ``` The animated map illustrated in Figure \@ref(fig:urban-animated) can be created using the same **tmap** techniques that generate faceted maps, demonstrated in Section \@ref(faceted-maps). There are two differences, however, related to arguments in `tm_facets_wrap()`: - `nrow = 1, ncol = 1` are added to keep one moment in time as one layer - `free.coords = FALSE`, which maintains the map extent for each map iteration These additional arguments are demonstrated in the subsequent code chunk^[There is also a shortcut for this approach: `tm_facets_pagewise()`.]: ```{r 08-mapping-22} urb_anim = tm_shape(world) + tm_polygons() + tm_shape(urban_agglomerations) + tm_symbols(size = "population_millions") + tm_facets_wrap(by = "year", nrow = 1, ncol = 1, free.coords = FALSE) ``` The resulting `urb_anim` represents a set of separate maps for each year. The final stage is to combine them and save the result as a `.gif` file with `tmap_animation()`. The following command creates the animation illustrated in Figure \@ref(fig:urban-animated), with a few elements missing, that we will add during the exercises: ```{r 08-mapping-23, eval=FALSE} tmap_animation(urb_anim, filename = "urb_anim.gif", delay = 25) ``` Another illustration of the power of animated maps is provided in Figure \@ref(fig:animus). This shows the development of states in the United States, which first formed in the east and then incrementally to the west and finally into the interior. Code to reproduce this map can be found in the script `code/09-usboundaries.R` in the book GitHub repository. ```{r 08-mapping-24, echo=FALSE, eval=FALSE} source("https://github.com/geocompx/geocompr/raw/main/code/09-usboundaries.R") ``` ```{r animus, echo=FALSE, message=FALSE, fig.cap="Animated map showing population growth, state formation and boundary changes in the United States, 1790-2010. Animated version available online at r.geocompx.org.", fig.scap="Animated map showing boundary changes in the United States."} u_animus_html = "https://user-images.githubusercontent.com/1825120/38543030-5794b6f0-3c9b-11e8-9da9-10ec1f3ea726.gif" u_animus_pdf = "images/animus.png" if (knitr::is_latex_output()){ knitr::include_graphics(u_animus_pdf) } else if (knitr::is_html_output()){ knitr::include_graphics(u_animus_html) } ``` ## Interactive maps \index{map-making!interactive maps} \index{tmap (package)!interactive maps} While static and animated maps can enliven geographic datasets, interactive maps can take them to a new level. Interactivity can take many forms, the most common and useful of which is the ability to pan around and zoom into any part of a geographic dataset overlaid on a 'web map' to show context. Less advanced interactivity levels include pop-ups which appear when you click on different features, a kind of interactive label. More advanced levels of interactivity include the ability to tilt and rotate maps, as demonstrated in the **mapdeck** example below, and the provision of "dynamically linked" sub-plots which automatically update when the user pans and zooms [@pezanowski_senseplace3_2018]. The most important type of interactivity, however, is the display of geographic data on interactive or 'slippy' web maps. The release of the **leaflet** package in 2015 (that uses the leaflet JavaScript library) revolutionized interactive web map creation from within R, and a number of packages have built on these foundations adding new features (e.g., **leaflet.extras2**) and making the creation of web maps as simple as creating static maps (e.g., **mapview** and **tmap**). This section illustrates each approach in the opposite order. We will explore how to make slippy maps with **tmap** (the syntax of which we have already learned), **mapview**\index{mapview (package)}, **mapdeck**\index{mapdeck (package)} and finally **leaflet**\index{leaflet (package)} (which provides low-level control over interactive maps). A unique feature of **tmap** mentioned in Section \@ref(static-maps) is its ability to create static and interactive maps using the same code. Maps can be viewed interactively at any point by switching to view mode, using the command `tmap_mode("view")`. This is demonstrated in the code below, which creates an interactive map of New Zealand based on the `tmap` object `map_nz`, created in Section \@ref(map-obj), and illustrated in Figure \@ref(fig:tmview): ```{r 08-mapping-25, eval=FALSE} tmap_mode("view") map_nz ``` ```{r tmview, message=FALSE, fig.cap="Interactive map of New Zealand created with tmap in view mode. Interactive version available online at: r.geocompx.org.", fig.scap="Interactive map of New Zealand.", echo=FALSE} if (knitr::is_latex_output()){ knitr::include_graphics("images/tmview-1.png") } else if (knitr::is_html_output()){ # tmap_mode("view") # m_tmview = map_nz # tmap_save(m_tmview, "tmview-1.html") # file.copy("tmview-1.html", "../geocompx.org/static/img/tmview-1.html") knitr::include_url("https://geocompx.org/static/img/tmview-1.html") } ``` Now that the interactive mode has been 'turned on', all maps produced with **tmap** will launch (another way to create interactive maps is with the `tmap_leaflet()` function). Notable features of this interactive mode include the ability to specify the basemap with `tm_basemap()` (or `tmap_options()`) as demonstrated below (result not shown): ```{r 08-mapping-26, eval=FALSE} map_nz + tm_basemap(server = "OpenTopoMap") ``` An impressive and little-known feature of **tmap**'s view mode is that it also works with faceted plots. The argument `sync` in `tm_facets_wrap()` can be used in this case to produce multiple maps with synchronized zoom and pan settings, as illustrated in Figure \@ref(fig:sync), which was produced by the following code: ```{r 08-mapping-27, eval=FALSE} world_coffee = left_join(world, coffee_data, by = "name_long") facets = c("coffee_production_2016", "coffee_production_2017") tm_shape(world_coffee) + tm_polygons(facets) + tm_facets_wrap(nrow = 1, sync = TRUE) ``` ```{r sync, message=FALSE, fig.cap="Faceted interactive maps of global coffee production in 2016 and 2017 in sync, demonstrating tmap's view mode in action.", fig.scap="Faceted interactive maps of global coffee production.", echo=FALSE} knitr::include_graphics("images/interactive-facets.png") ``` Switch **tmap** back to plotting mode with the same function: ```{r 08-mapping-28} tmap_mode("plot") ``` If you are not proficient with **tmap**, the quickest way to create interactive maps in R may be with **mapview**\index{mapview (package)}. The following 'one liner' is a reliable way to interactively explore a wide range of geographic data formats: ```{r 08-mapping-29, eval=FALSE} mapview::mapview(nz) ``` ```{r mapview, message=FALSE, fig.cap="Illustration of mapview in action.", echo=FALSE} knitr::include_graphics("images/mapview.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979522-e8277398-573e-11e8-8c55-d72c6bcc58a4.png") # mv = mapview::mapview(nz) # mv@map ``` **mapview** has a concise syntax, yet, it is powerful. By default, it has some standard GIS functionality such as mouse position information, attribute queries (via pop-ups), scale bar, and zoom-to-layer buttons. It also offers advanced controls including the ability to 'burst' datasets into multiple layers and the addition of multiple layers with `+` followed by the name of a geographic object. Additionally, it provides automatic coloring of attributes via the `zcol` argument. In essence, it can be considered a data-driven **leaflet** API\index{API} (see below for more information about **leaflet**). Given that **mapview** always expects a spatial object (including `sf` and `SpatRaster`) as its first argument, it works well at the end of piped expressions. Consider the following example where **sf** is used to intersect lines and polygons and then is visualized with **mapview** (Figure \@ref(fig:mapview2)). ```{r 08-mapping-30, eval=FALSE} library(mapview) oberfranken = subset(franconia, district == "Oberfranken") trails |> st_transform(st_crs(oberfranken)) |> st_intersection(oberfranken) |> st_collection_extract("LINESTRING") |> mapview(color = "red", lwd = 3, layer.name = "trails") + mapview(franconia, zcol = "district") + breweries ``` ```{r mapview2, message=FALSE, fig.cap="Using mapview at the end of an sf-based pipe expression.", echo=FALSE, warning=FALSE} knitr::include_graphics("images/mapview-example.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979271-5f515256-573d-11e8-9ede-e472ca007d73.png") ``` One important thing to keep in mind is that **mapview** layers are added via the `+` operator (similar to **ggplot2** or **tmap**). By default, **mapview** uses the leaflet JavaScript library to render the output maps, which is user-friendly and has a lot of features. However, some alternative rendering libraries could be more performant (work more smoothly on larger datasets). **mapview** allows to set alternative rendering libraries (`"leafgl"` and `"mapdeck"`) in the `mapviewOptions()`.^[You may also try to use `mapviewOptions(georaster = TRUE)` for more performant visualizations of large raster data.] For further information on **mapview**, see the package's website at: [r-spatial.github.io/mapview/](https://r-spatial.github.io/mapview/articles/). There are other ways to create interactive maps with R. The **googleway** package\index{googleway (package)}, for example, provides an interactive mapping interface that is flexible and extensible (see the [`googleway-vignette`](https://cran.r-project.org/package=googleway/vignettes/googleway-vignette.html) for details). Another approach by the same author is **[mapdeck](https://github.com/SymbolixAU/mapdeck)**, which provides access to Uber's `Deck.gl` framework\index{mapdeck (package)}. Its use of WebGL enables it to interactively visualize large datasets up to millions of points. The package uses Mapbox [access tokens](https://docs.mapbox.com/help/getting-started/access-tokens/), which you must register for before using the package. ```{block2 08-mapping-31, type='rmdnote'} Note that the following block assumes the access token is stored in your R environment as `MAPBOX=your_unique_key`. This can be added with `usethis::edit_r_environ()`. ``` A unique feature of **mapdeck** is its provision of interactive 2.5D perspectives, illustrated in Figure \@ref(fig:mapdeck). This means you can can pan, zoom and rotate around the maps, and view the data 'extruded' from the map. Figure \@ref(fig:mapdeck), generated by the following code chunk, visualizes road traffic crashes in the UK, with bar height representing casualties per area. ```{r 08-mapping-32, engine='zsh', echo=FALSE, eval=FALSE} https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv curl -i https://git.io -F "url=https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" \ -F "code=geocompr-mapdeck" ``` ```{r 08-mapping-33, eval=FALSE} library(mapdeck) set_token(Sys.getenv("MAPBOX")) crash_data = read.csv("https://git.io/geocompr-mapdeck") crash_data = na.omit(crash_data) ms = mapdeck_style("dark") mapdeck(style = ms, pitch = 45, location = c(0, 52), zoom = 4) |> add_grid(data = crash_data, lat = "lat", lon = "lng", cell_size = 1000, elevation_scale = 50, colour_range = hcl.colors(6, "plasma")) ``` ```{r mapdeck, echo=FALSE, fig.cap="Map generated by mapdeck, representing road traffic casualties across the UK. Height of 1-km cells represents number of crashes.", fig.scap="Map generated by mapdeck."} knitr::include_graphics("images/mapdeck-mini.png") ``` You can zoom and drag the map in the browser, in addition to rotating and tilting it when pressing `Cmd`/`Ctrl`. Multiple layers can be added with the pipe operator, as demonstrated in the [`mapdeck` vignettes](https://cran.r-project.org/package=mapdeck/vignettes/mapdeck.html). **mapdeck** also supports `sf` objects, as can be seen by replacing the `add_grid()` function call in the preceding code chunk with `add_polygon(data = lnd, layer_id = "polygon_layer")`, to add polygons representing London to an interactive tilted map. ```{r 08-mapping-35, eval=FALSE, echo=FALSE} library(mapdeck) set_token(Sys.getenv("MAPBOX")) df = read.csv("https://git.io/geocompr-mapdeck") ms = mapdeck_style('dark') mapdeck(style = ms, pitch = 45, location = c(0, 52), zoom = 4) |> add_polygon(data = lnd, layer_id = "polygon_layer") ``` Last is **leaflet** which is the most mature and widely used interactive mapping package in R\index{leaflet (package)}. **leaflet** provides a relatively low-level interface to the Leaflet JavaScript library and many of its arguments can be understood by reading the documentation of the original JavaScript library (see [leafletjs.com](https://leafletjs.com/)). Leaflet maps are created with `leaflet()`, the result of which is a `leaflet` map object which can be piped to other **leaflet** functions. This allows multiple map layers and control settings to be added interactively, as demonstrated in the code below which generates Figure \@ref(fig:leaflet) (see [rstudio.github.io/leaflet/](https://rstudio.github.io/leaflet/) for details). ```{r leaflet-code, echo=TRUE, eval=FALSE} pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes) leaflet(data = cycle_hire) |> addProviderTiles(providers$CartoDB.Positron) |> addCircles(col = ~pal(nbikes), opacity = 0.9) |> addPolygons(data = lnd, fill = FALSE) |> addLegend(pal = pal, values = ~nbikes) |> setView(lng = -0.1, 51.5, zoom = 12) |> addMiniMap() ``` ```{r leaflet, message=FALSE, fig.cap="The leaflet package in action, showing cycle hire points in London. See interactive version [online](https://geocompr.github.io/img/leaflet.html).", fig.scap="The leaflet package in action.", echo=FALSE} if (knitr::is_latex_output() || knitr::is_html_output()){ knitr::include_graphics("images/leaflet-1.png") } else { # pre-generated for https://github.com/ropensci/stplanr/issues/385 # pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes) # m = leaflet(data = cycle_hire) |> # addProviderTiles(providers$CartoDB.Positron) |> # addCircles(col = ~pal(nbikes), opacity = 0.9) |> # addPolygons(data = lnd, fill = FALSE) |> # addLegend(pal = pal, values = ~nbikes) |> # setView(lng = -0.1, 51.5, zoom = 12) |> # addMiniMap() # htmlwidgets::saveWidget(m, "leaflet.html") # browseURL("leaflet.html") # file.rename("leaflet.html", "~/geocompr/geocompr.github.io/static/img/leaflet.html") # abort old way of including - mixed content issues knitr::include_url("https://geocompr.github.io/img/leaflet.html") } ``` ## Mapping applications \index{map-making!mapping applications} The interactive web maps demonstrated in Section \@ref(interactive-maps) can go far. Careful selection of layers to display, basemaps and pop-ups can be used to communicate the main results of many projects involving geocomputation. But the web mapping approach to interactivity has limitations: - Although the map is interactive in terms of panning, zooming and clicking, the code is static, meaning the user interface is fixed - All map content is generally static in a web map, meaning that web maps cannot scale to handle large datasets easily - Additional layers of interactivity, such a graphs showing relationships between variables and 'dashboards', are difficult to create using the web mapping-approach Overcoming these limitations involves going beyond static web mapping and toward geospatial frameworks and map servers. Products in this field include [GeoDjango](https://docs.djangoproject.com/en/4.0/ref/contrib/gis/)\index{GeoDjango} (which extends the Django web framework and is written in [Python](https://github.com/django/django))\index{Python}, [MapServer](https://github.com/mapserver/mapserver)\index{MapServer} (a framework for developing web applications, largely written in C and C++)\index{C++} and [GeoServer](https://github.com/geoserver/geoserver) (a mature and powerful map server written in Java\index{Java}). Each of these is scalable, enabling maps to be served to thousands of people daily, assuming there is sufficient public interest in your maps! The bad news is that such server-side solutions require much skilled developer time to set up and maintain, often involving teams of people with roles such as a dedicated geospatial database administrator ([DBA](https://wiki.gis.com/wiki/index.php/Database_administrator)). Fortunately for R programmers, web mapping applications can now be rapidly created with **shiny**.\index{shiny (package)} As described in the open source book [Mastering Shiny](https://mastering-shiny.org/), **shiny** is an R package and framework for converting R code into interactive web applications [@wickham_mastering_2021]. You can embed interactive maps in shiny apps thanks to functions such as [`leaflet::renderLeaflet()`](https://rstudio.github.io/leaflet/shiny.html). This section gives some context, teaches the basics of **shiny** from a web mapping perspective, and culminates in a full-screen mapping application in less than 100 lines of code. **shiny** is well documented at [shiny.posit.co](https://shiny.posit.co/), which highlights the two components of every **shiny** app: 'front end' (the bit the user sees) and 'back end' code. In **shiny** apps, these elements are typically created in objects named `ui` and `server` within an R script named `app.R`, which lives in an 'app folder'. This allows web mapping applications to be represented in a single file, such as the [`CycleHireApp/app.R`](https://github.com/geocompx/geocompr/blob/main/apps/CycleHireApp/app.R) file in the book's GitHub repo. ```{block2 shiny, type = 'rmdnote'} In **shiny** apps these are often split into `ui.R` (short for user interface) and `server.R` files, naming conventions used by `shiny-server`, a server-side Linux application for serving shiny apps on public-facing websites. `shiny-server` also serves apps defined by a single `app.R` file in an 'app folder'. Learn more at: https://github.com/rstudio/shiny-server. ``` Before considering large apps, it is worth seeing a minimal example, named 'lifeApp', in action.^[ The word 'app' in this context refers to 'web application' and should not be confused with smartphone apps, the more common meaning of the word. ] The code below defines and launches --- with the command `shinyApp()` --- a lifeApp, which provides an interactive slider allowing users to make countries appear with progressively lower levels of life expectancy (see Figure \@ref(fig:lifeApp)): ```{r 08-mapping-37, eval=FALSE} library(shiny) # for shiny apps library(leaflet) # renderLeaflet function library(spData) # loads the world dataset ui = fluidPage( sliderInput(inputId = "life", "Life expectancy", 49, 84, value = 80), leafletOutput(outputId = "map") ) server = function(input, output) { output$map = renderLeaflet({ leaflet() |> # addProviderTiles("OpenStreetMap.BlackAndWhite") |> addPolygons(data = world[world$lifeExp < input$life, ])}) } shinyApp(ui, server) ``` ```{r lifeApp, echo=FALSE, message=FALSE, fig.cap="Screenshot showing minimal example of a web mapping application created with shiny.", fig.scap="Minimal example of a web mapping application."} # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39690606-8f9400c8-51d2-11e8-84d7-f4a66a477d2a.png") knitr::include_graphics("images/shiny-app.png") ``` The **user interface** (`ui`) of lifeApp is created by `fluidPage()`. This contains input and output 'widgets' --- in this case, a `sliderInput()` (many other `*Input()` functions are available) and a `leafletOutput()`. These are arranged row-wise by default, explaining why the slider interface is placed directly above the map in Figure \@ref(fig:lifeApp) (see `?column` for adding content column-wise). The **server side** (`server`) is a function with `input` and `output` arguments. `output` is a list of objects containing elements generated by `render*()` function --- `renderLeaflet()` which in this example generates `output$map`. Input elements such as `input$life` referred to in the server must relate to elements that exist in the `ui` --- defined by `inputId = "life"` in the code above. The function `shinyApp()` combines both the `ui` and `server` elements and serves the results interactively via a new R process. When you move the slider in the map shown in Figure \@ref(fig:lifeApp), you are actually causing R code to re-run, although this is hidden from view in the user interface. Building on this basic example and knowing where to find help (see `?shiny`), the best way forward now may be to stop reading and start programming! The recommended next step is to open the previously mentioned [`CycleHireApp/app.R`](https://github.com/geocompx/geocompr/blob/main/apps/CycleHireApp/app.R) script in an integrated development environment (IDE) of choice, modify it and re-run it repeatedly. The example contains some of the components of a web mapping application implemented in **shiny** and should 'shine' a light on how they behave. The `CycleHireApp/app.R` script contains **shiny** functions that go beyond those demonstrated in the simple 'lifeApp' example, deployed at [shiny.robinlovelace.net/CycleHireApp](https://shiny.robinlovelace.net/CycleHireApp). These include `reactive()` and `observe()`, (for creating outputs that respond to the user interface, see `?reactive`) and `leafletProxy()` (for modifying a `leaflet` object that has already been created). Such elements enable web mapping applications implemented in **shiny** [@lovelace_propensity_2017]. A range of 'events' can be programmed including advanced functionality such as drawing new layers or subsetting data, as described in the shiny section of RStudio's **leaflet** [website](https://rstudio.github.io/leaflet/shiny.html). ```{block2 shinynote, type='rmdnote'} There are a number of ways to run a **shiny** app. For RStudio users, the simplest way is probably to click on the 'Run App' button located in the top right of the source pane when an `app.R`, `ui.R` or `server.R` script is open. **shiny** apps can also be initiated by using `runApp()` with the first argument being the folder containing the app code and data: `runApp("CycleHireApp")` in this case (which assumes a folder named `CycleHireApp` containing the `app.R` script is in your working directory). You can also launch apps from a Unix command line with the command `Rscript -e 'shiny::runApp("CycleHireApp")'`. ``` Experimenting with apps such as `CycleHireApp` will build not only your knowledge of web mapping applications in R, but also your practical skills. Changing the contents of `setView()`, for example, will change the starting bounding box that the user sees when the app is initiated. Such experimentation should not be done at random, but with reference to relevant documentation, starting with `?shiny`, and motivated by a desire to solve problems such as those posed in the exercises. **shiny** used in this way can make prototyping mapping applications faster and more accessible than ever before (deploying **shiny** apps, https://shiny.posit.co/deploy/, is a separate topic beyond the scope of this chapter). Even if your applications are eventually deployed using different technologies, **shiny** undoubtedly allows web mapping applications to be developed in relatively few lines of code (86 in the case of CycleHireApp). That does not stop shiny apps getting rather large. The Propensity to Cycle Tool (PCT) hosted at [pct.bike](https://www.pct.bike/), for example, is a national mapping tool funded by the UK's Department for Transport. The PCT is used by dozens of people each day and has multiple interactive elements based on more than 1000 lines of [code](https://github.com/npct/pct-shiny/blob/master/regions_www/m/server.R) [@lovelace_propensity_2017]. While such apps undoubtedly take time and effort to develop, **shiny** provides a framework for reproducible prototyping that should aid the development process. One potential problem with the ease of developing prototypes with **shiny** is the temptation to start programming too early, before the purpose of the mapping application has been envisioned in detail. For that reason, despite advocating **shiny**, we recommend starting with the longer established technology of a pen and paper as the first stage for interactive mapping projects. This way your prototype web applications should be limited not by technical considerations, but by your motivations and imagination. ```{r CycleHireApp-html, echo=FALSE, message=FALSE, fig.cap="CycleHireApp, a simple web mapping application for finding the closest cycle hiring station based on your location and requirement of cycles. Interactive version available online at: r.geocompx.org.",fig.scap="Cycle Hire App, a simple web mapping application.", eval=knitr::is_html_output(), out.width="690"} if (knitr::is_html_output()){ knitr::include_url("https://shiny.robinlovelace.net/CycleHireApp/") } ``` ## Other mapping packages **tmap** provides a powerful interface for creating a wide range of static maps (Section \@ref(static-maps)) and also supports interactive maps (Section \@ref(interactive-maps)). But there are many other options for creating maps in R. The aim of this section is to provide a taste of some of these and pointers for additional resources: map-making is a surprisingly active area of R package development, so there is more to learn than can be covered here. The most mature option is to use `plot()` methods provided by core spatial packages **sf** and **terra**, covered in Sections \@ref(basic-map) and \@ref(basic-map-raster), respectively. What we have not mentioned in those sections was that plot methods for vector and raster objects can be combined when the results draw onto the same plot area (elements such as keys in **sf** plots and multi-band rasters will interfere with this). This behavior is illustrated in the subsequent code chunk which generates Figure \@ref(fig:nz-plot). `plot()` has many other options which can be explored by following links in the `?plot` help page and the fifth **sf** vignette [`sf5`](https://cran.r-project.org/package=sf/vignettes/sf5.html). ```{r nz-plot, message=FALSE, fig.cap="Map of New Zealand created with plot(). The legend to the right refers to elevation (1000 m above sea level).", fig.scap="Map of New Zealand created with plot().", fig.height=3.5} g = st_graticule(nz, lon = c(170, 175), lat = c(-45, -40, -35)) plot(nz_water, graticule = g, axes = TRUE, col = "blue") terra::plot(nz_elev / 1000, add = TRUE, axes = FALSE) plot(st_geometry(nz), add = TRUE) ``` The **tidyverse**\index{tidyverse (package)} plotting package **ggplot2** also supports `sf` objects with `geom_sf()`\index{ggplot2 (package)}. The syntax is similar to that used by **tmap**: an initial `ggplot()` call is followed by one or more layers, that are added with `+ geom_*()`, where `*` represents a layer type such as `geom_sf()` (for `sf` objects) or `geom_points()` (for points). **ggplot2** plots graticules by default. The default settings for the graticules can be overridden using `scale_x_continuous()`, `scale_y_continuous()` or [`coord_sf(datum = NA)`](https://github.com/tidyverse/ggplot2/issues/2071). Other notable features include the use of unquoted variable names encapsulated in `aes()` to indicate which aesthetics vary and switching data sources using the `data` argument, as demonstrated in the code chunk below which creates Figure \@ref(fig:nz-gg2): ```{r nz-gg, eval=FALSE} library(ggplot2) g1 = ggplot() + geom_sf(data = nz, aes(fill = Median_income)) + geom_sf(data = nz_height) + scale_x_continuous(breaks = c(170, 175)) g1 ``` Another benefit of maps based on **ggplot2** is that they can easily be given a level of interactivity when printed using the function `ggplotly()` from the **plotly** package\index{plotly (package)}. Try `plotly::ggplotly(g1)`, for example, and compare the result with other **plotly** mapping functions described at: [blog.cpsievert.me](https://blog.cpsievert.me/2018/03/30/visualizing-geo-spatial-data-with-sf-and-plotly/). ```{r 08-mapping-38, eval=FALSE, echo=FALSE} plotly::ggplotly(g1) ``` An advantage of **ggplot2** is that it has a strong user community and many add-on packages. It includes **ggspatial**, which enhances **ggplot2**'s mapping capabilities by providing options to add a north arrow (`annotation_north_arrow()`) and a scale bar (`annotation_scale()`), or to add background tiles (`annotation_map_tile()`). It also accepts various spatial data classes with `layer_spatial()`. Thus, we are able to plot `SpatRaster` objects from **terra** using this function as seen in Figure \@ref(fig:nz-gg2). ```{r ggterra, eval=FALSE, message=FALSE, warning=FALSE} library(ggspatial) ggplot() + layer_spatial(nz_elev) + geom_sf(data = nz, fill = NA) + annotation_scale() + scale_x_continuous(breaks = c(170, 175)) + scale_fill_continuous(na.value = NA) ``` ```{r nz-gg2, message=FALSE, fig.cap="Comparison of map of New Zealand created with ggplot2 alone (left) and ggplot2 and ggspatial (right).", out.width="45%", fig.show='hold', echo=FALSE, warning=FALSE, fig.height=7} library(ggplot2) g1 = ggplot() + geom_sf(data = nz, aes(fill = Median_income)) + geom_sf(data = nz_height) + scale_x_continuous(breaks = c(170, 175)) g1 library(ggspatial) ggplot() + layer_spatial(nz_elev) + geom_sf(data = nz, fill = NA) + annotation_scale() + scale_x_continuous(breaks = c(170, 175)) + scale_fill_continuous(na.value = NA) ``` At the same time, **ggplot2** has a few drawbacks, for example the `geom_sf()` function is not always able to create a desired legend to use from the spatial [data](https://github.com/tidyverse/ggplot2/issues/2037). Good additional **ggplot2** resources can be found in the open source [ggplot2 book](https://ggplot2-book.org/) [@wickham_ggplot2_2016] and in the descriptions of the multitude of '**gg**packages' such as **ggrepel** and **tidygraph**. We have covered mapping with **sf**, **terra** and **ggplot2** first because these packages are highly flexible, allowing for the creation of a wide range of static maps. Before we cover mapping packages for plotting a specific type of map (in the next paragraph), it is worth considering alternatives to the packages already covered for general-purpose mapping (Table \@ref(tab:map-gpkg)). ```{r map-gpkg, echo=FALSE, message=FALSE, warning=FALSE} # code/09-map-pkgs.R gpkg_df = readr::read_csv("extdata/generic_map_pkgs.csv") map_gpkg_df = select(gpkg_df, Package = package, Title = title) map_gpkg_df$Title[map_gpkg_df$Package == "leaflet"] = "Create Interactive Web Maps with Leaflet" knitr::kable(map_gpkg_df, caption = "Selected general-purpose mapping packages.", caption.short = "Selected general-purpose mapping packages.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "9cm") ``` Table \@ref(tab:map-gpkg) shows a range of mapping packages that are available, and there are many others not listed in this table. Of note is **mapsf**, which can generate a range of geographic visualizations including choropleth, 'proportional symbol' and 'flow' maps. These are documented in the [`mapsf`](https://cran.r-project.org/package=mapsf/vignettes/mapsf.html)\index{mapsf (package)} vignette. Several packages focus on specific map types, as illustrated in Table \@ref(tab:map-spkg). Such packages create cartograms that distort geographical space, create line maps, transform polygons into regular or hexagonal grids, visualize complex data on grids representing geographic topologies, and create 3D visualizations. ```{r map-spkg, echo=FALSE, message=FALSE} # code/09-map-pkgs.R spkg_df = readr::read_csv("extdata/specific_map_pkgs.csv") map_spkg_df = select(spkg_df, Package = package, Title = title) knitr::kable(map_spkg_df, caption = paste("Selected specific-purpose mapping packages,", "with associated metrics."), caption.short = "Selected specific-purpose mapping packages.", booktabs = TRUE) ``` All of the aforementioned packages, however, have different approaches for data preparation and map creation. In the next paragraph, we focus solely on the **cartogram** package [@R-cartogram]\index{cartogram (package)}. Therefore, we suggest to read the [geogrid](https://github.com/jbaileyh/geogrid)\index{geogrid (package)}, [geofacet](https://github.com/hafen/geofacet)\index{geofacet (package)}, [linemap](https://github.com/riatelab/linemap)\index{linemap (package)}, [tanaka](https://github.com/riatelab/tanaka)\index{tanaka (package)}, and [rayshader](https://github.com/tylermorganwall/rayshader)\index{rayshader (package)} documentations to learn more about them. A cartogram is a map in which the geometry is proportionately distorted to represent a mapping variable. Creation of this type of map is possible in R with **cartogram**, which allows for creating contiguous and non-contiguous area cartograms. It is not a mapping package per se, but it allows for construction of distorted spatial objects that could be plotted using any generic mapping package. The `cartogram_cont()` function creates contiguous area cartograms. It accepts an `sf` object and name of the variable (column) as inputs. Additionally, it is possible to modify the `intermax` argument -- maximum number of iterations for the cartogram transformation. For example, we could represent median income in New Zeleand's regions as a contiguous cartogram (Figure \@ref(fig:cartomap1), right panel) as follows: ```{r 08-mapping-39, fig.show='hide', message=FALSE} library(cartogram) nz_carto = cartogram_cont(nz, "Median_income", itermax = 5) tm_shape(nz_carto) + tm_polygons("Median_income") ``` ```{r cartomap1, echo=FALSE, message=FALSE, fig.cap="Comparison of standard map (left) and contiguous area cartogram (right).", fig.scap="Comparison of standard map and contiguous area cartogram.", warning=FALSE, fig.width=9} carto_map1 = tm_shape(nz) + tm_polygons("Median_income", fill.scale = tm_scale(values = "Greens"), fill.legend = tm_legend_hide()) carto_map2 = tm_shape(nz_carto) + tm_polygons("Median_income", fill.scale = tm_scale(values = "Greens"), fill.legend = tm_legend(title = "Median income (NZD)", position = c("right", "bottom"))) tmap_arrange(carto_map1, carto_map2) ``` **cartogram** also offers creation of non-contiguous area cartograms using `cartogram_ncont()` and Dorling cartograms using `cartogram_dorling()`. Non-contiguous area cartograms are created by scaling down each region based on the provided weighting variable. Dorling cartograms consist of circles with their area proportional to the weighting variable. The code chunk below demonstrates creation of non-contiguous area and Dorling cartograms of US states' population (Figure \@ref(fig:cartomap2)): ```{r 08-mapping-40, fig.show='hide', message=FALSE} us_states9311 = st_transform(us_states, "EPSG:9311") us_states9311_ncont = cartogram_ncont(us_states9311, "total_pop_15") us_states9311_dorling = cartogram_dorling(us_states9311, "total_pop_15") ``` ```{r cartomap2, echo=FALSE, message=FALSE, fig.cap="Comparison of non-contiguous area cartogram (left) and Dorling cartogram (right).", fig.scap="Comparison of cartograms.", fig.asp=0.32, warning=FALSE} carto_map3 = tm_shape(us_states9311_ncont) + tm_polygons("total_pop_15", fill.scale = tm_scale(values = "BuPu"), fill.legend = tm_legend(title = "Total population")) + tm_layout(legend.show = FALSE) carto_map4 = tm_shape(us_states9311_dorling) + tm_polygons("total_pop_15", fill.scale = tm_scale(values = "BuPu"), fill.legend = tm_legend(title = "Total population")) + tm_layout(legend.show = FALSE) carto_map_34legend = tm_shape(us_states9311_dorling) + tm_polygons("total_pop_15", fill.scale = tm_scale(values = "BuPu"), fill.legend = tm_legend(title = "Total population")) + tm_layout(legend.only = TRUE) tmap_arrange(carto_map3, carto_map4, carto_map_34legend, widths = c(0.4, 0.4, 0.2), ncol = 3) ``` ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_09-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 10-gis.Rmd ================================================ # Bridges to GIS software {#gis} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires QGIS\index{QGIS}, SAGA\index{SAGA} and GRASS GIS\index{GRASS GIS} to be installed and the following packages to be attached: ```{r 09-gis-1, message=FALSE,echo=FALSE} library(sf) library(terra) ``` ```{r 09-gis-1-2, message=FALSE, eval=FALSE} library(sf) library(terra) library(qgisprocess) library(Rsagacmd) library(rgrass) library(rstac) library(gdalcubes) ``` ```{r 09-gis-1-3, echo=FALSE, message=FALSE} # Only run code if qgis and relevant plugins are installed has_qgis_plugins = # make sure that qgisprocess is installed "qgisprocess" %in% installed.packages() && # make sure that QGIS is available qgisprocess::has_qgis() && # make sure that all required plugins are available all(qgisprocess::qgis_has_plugin(c("grassprovider", "processing_saga_nextgen"))) ``` ## Introduction A defining feature of [interpreted](https://en.wikipedia.org/wiki/Interpreter_(computing)) languages with an interactive console --- technically a read-eval-print loop (REPL) --- such as R is the way you interact with them: rather than relying on pointing and clicking on different parts of a screen, you type commands into the console and execute them with the `Enter` key. A common and effective workflow when using interactive development environments such as RStudio or VS Code is to type code into source files in a source editor and control interactive execution of the code with a shortcut such as `Ctrl+Enter`. Command line interfaces (CLIs) are not unique to R: most early computing environments relied on a command line 'shell' and it was only after the invention and widespread adoption of the computer mouse in the 1990s that graphical user interfaces (GUIs)\index{graphical user interface} became common. GRASS GIS, the longest-standing continuously developed open source GIS\index{GIS} software, for example, relied on its CLI before it gained a GUI [@landa_new_2008]. Most popular GIS software projects are GUI-driven. You *can* interact with QGIS\index{QGIS}, SAGA\index{SAGA}, GRASS GIS\index{GRASS GIS} and gvSIG from system terminals and embedded CLIs, but their design encourages most people to interact with them by 'pointing and clicking'. An unintended consequence of this is that most GIS users miss out on the advantages of CLI-driven and scriptable approaches. According to the creator of the popular QGIS software [@sherman_desktop_2008]: > With the advent of 'modern' GIS software, most people want to point and click their way through life. That’s good, but there is a tremendous amount of flexibility and power waiting for you with the command line. Many times you can do something on the command line in a fraction of the time you can do it with a GUI. The 'CLI vs. GUI' debate does not have to be adversarial: both ways of working have advantages, depending on a range of factors including the task (with drawing new features being well suited to GUIs), the level of reproducibility desired, and the user's skillset. GRASS GIS is a good example of GIS software that is primarily based on a CLI but which also has a prominent GUI. Likewise, while R is focused on its CLI, IDEs such as RStudio provide a GUI for improving accessibility. Software cannot be neatly categorized into CLI or GUI-based. However, interactive command-line interfaces have several important advantages in terms of: - Automating repetitive tasks - Enabling transparency and reproducibility - Encouraging software development by providing tools to modify existing functions and implement new ones - Developing future-proof and efficient programming skills which are in high demand - Improving touch typing, a key skill in the digital age On the other hand, good GUIs\index{graphical user interface} also have advantages, including: - 'Shallow' learning curves meaning geographic data can be explored and visualized without hours of learning a new language - Support for 'digitizing' (creating new vector datasets), including trace, snap and topological tools^[The **mapedit** R package allows the quick editing of a few spatial features in a browser window opened from R but not professional, large-scale cartographic digitizing.] - Enables georeferencing (matching raster images to existing maps) with ground control points and orthorectification - Supports stereoscopic mapping (e.g., LiDAR and structure from motion) Another advantage of dedicated GIS software projects is that they provide access to hundreds of 'geoalgorithms' via 'GIS bridges' [@neteler_open_2008]. Such bridges to these computational recipes for enhancing R's capabilities for solving geographic data problems are the topic of this chapter. ```{block2 09-gis-2, type="rmdnote"} A command line interface is an environment for interacting with computer programs by typing and entering successive commands (command lines). `bash` in Linux and `PowerShell` in Windows are well-known examples that allow the user to control almost any part of their operating system. IDEs such as RStudio and VS Code provide code auto-completion and other features to improve the user experience when developing code. ``` R is a natural choice for people wanting to build bridges between reproducible data analysis workflows and GIS because it *originated* as an interface language. A key feature of R (and its predecessor S) is that it provides access to statistical algorithms in other languages (particularly FORTRAN\index{FORTRAN} and C), but from a powerful high-level functional language with an intuitive REPL environment, which C and FORTRAN lacked [@chambers_extending_2016]. R continues this tradition with interfaces to numerous languages, notably C++\index{C++}. Although R was not designed as a command line GIS, its ability to interface with dedicated GISs gives it astonishing geospatial capabilities. With GIS bridges, R can replicate more diverse workflows, with the additional reproducibility, scalability and productivity benefits of controlling them from a programming environment and a consistent CLI. Furthermore, R outperforms GISs in some areas of geocomputation\index{geocomputation}, including interactive/animated map-making (see Chapter \@ref(adv-map)) and spatial statistical modeling (see Chapter \@ref(spatial-cv)). This chapter focuses on 'bridges' to three mature open source GIS products, summarized in Table \@ref(tab:gis-comp): - QGIS\index{QGIS}, via the package **qgisprocess**\index{qgisprocess (package)} [@R-qgisprocess; Section \@ref(rqgis)] - SAGA\index{SAGA}, via **Rsagacmd**\index{Rsagacmd (package)} [@R-Rsagacmd; Section \@ref(saga)] - GRASS GIS\index{GRASS GIS}, via **rgrass**\index{rgrass (package)} [@R-rgrass; Section \@ref(grass)] There have also been major developments in enabling open source GIS software to write and execute R scripts inside QGIS\index{QGIS} (see [docs.qgis.org](https://docs.qgis.org/3.28/en/docs/training_manual/processing/r_intro.html)) and GRASS GIS (see [grasswiki.osgeo.org](https://grasswiki.osgeo.org/wiki/R_statistics/rgrass#R_within_GRASS)). ```{r gis-comp, echo=FALSE, message=FALSE} library(dplyr) d = tibble( "GIS" = c("QGIS", "SAGA", "GRASS GIS"), "First Release" = c("2002", "2004", "1982"), "No. Functions" = c(">1000", ">600", ">500"), "Support" = c("hybrid", "hybrid", "hybrid") ) knitr::kable( x = d, caption = paste( "Comparison between three open-source GIS.", "Hybrid refers to the support of vector and", "raster operations." ), caption.short = "Comparison between three open-source GIS.", booktabs = TRUE ) #|> # kableExtra::add_footnote(label = "Comparing downloads of different providers is rather difficult (see http://spatialgalaxy.net/2011/12/19/qgis-users-around-the-world), and here also useless since every Windows QGIS download automatically also downloads SAGA and GRASS GIS.", notation = "alphabet") ``` In addition to the three R-GIS bridges mentioned above, this chapter also provides a brief introduction to R interfaces to spatial libraries (Section \@ref(gdal)), spatial databases\index{spatial database} (Section \@ref(postgis)), and cloud-based processing of Earth observation data (Section \@ref(cloud)). ## **qgisprocess**: a bridge to QGIS and beyond {#rqgis} QGIS\index{QGIS} is the most popular open-source GIS (Table \@ref(tab:gis-comp); @graser_processing_2015). QGIS provides a unified interface to QGIS's native geoalgorithms, GDAL, and --- when they are installed --- from other *providers* such as GRASS GIS\index{GRASS GIS}, and SAGA\index{SAGA} [@graser_processing_2015]. Since version 3.14 (released in summer 2020), QGIS ships with the `qgis_process` command-line utility for accessing a bounty of functionality for geocomputation. `qgis_process` provides access to 300+ geoalgorithms in the standard QGIS installation and 1,000+ via plugins to external providers such as GRASS GIS and SAGA. The **qgisprocess** package\index{qgisprocess (package)} provides access to `qgis_process` from R. The package requires QGIS --- and any other relevant plugins such as GRASS GIS and SAGA, used in this chapter --- to be installed and available to the system. For installation instructions, see **qgisprocess**'s [documentation](https://r-spatial.github.io/qgisprocess/). ```{block2 qgisdocker, type='rmdnote'} A quick way to get up-and-running with **qgisprocess** if you have Docker installed is via the `qgis` image developed as part of this project. Assuming you have Docker installed and sufficient computational resources, you can run an R session with **qgisprocess** and relevant plugins with the following command (see the [geocompx/docker](https://github.com/geocompx/docker) repository for details): `docker run -e DISABLE_AUTH=true -p 8786:8787 ghcr.io/geocompx/docker:qgis` ``` ```{r, eval=FALSE} library(qgisprocess) #> Attempting to load the cache ... Success! #> QGIS version: 3.30.3-'s-Hertogenbosch #> ... ``` This package automatically tries to detect a QGIS installation and complains if it cannot find it.^[You can see details of the detection process with `qgis_configure()`.] There are a few possible solutions when the configuration fails: you can set `options(qgisprocess.path = "path/to/your_qgis_process")`, or set up the `R_QGISPROCESS_PATH` environment variable. The above approaches can also be used when you have more than one QGIS installation and want to decide which one to use. For more details, please refer to the **qgisprocess** ['getting started' vignette](https://r-spatial.github.io/qgisprocess/articles/qgisprocess.html). Next, we can find which plugins (meaning different software) are available on our computer: ```{r plugins, eval=FALSE} qgis_plugins() #> # A tibble: 4 × 2 #> name enabled #> #> 1 grassprovider FALSE #> 2 otbprovider FALSE #> 3 processing TRUE #> 4 processing_saga_nextgen FALSE ``` This tells us that the GRASS GIS (`grassprovider`) and SAGA (`processing_saga_nextgen`) plugins are available on the system but are not yet enabled. Since we need both later on in the chapter, let's enable them. ```{r enable-providers, eval=FALSE} qgis_enable_plugins(c("grassprovider", "processing_saga_nextgen"), quiet = TRUE) ``` Please note that aside from installing SAGA on your system, you also need to install the QGIS Python plugin Processing Saga NextGen. You can do so from within QGIS with the [Plugin Manager](https://docs.qgis.org/latest/en/docs/training_manual/qgis_plugins/fetching_plugins.html) or programmatically with the help of the Python package [qgis-plugin-manager](https://github.com/3liz/qgis-plugin-manager) (at least on Linux). `qgis_providers()` lists the name of the software and the corresponding count of available geoalgorithms. ```{r providers, eval=FALSE} qgis_providers() #> # A tibble: 7 × 3 #> provider provider_title algorithm_count #> #> 1 gdal GDAL 56 #> 2 grass GRASS 306 #> 3 qgis QGIS 50 #> 4 3d QGIS (3D) 1 #> 5 native QGIS (native c++) 243 #> 6 pdal QGIS (PDAL) 17 #> 7 sagang SAGA Next Gen 509 ``` The output table affirms that we can use QGIS geoalgorithms (`native`, `qgis`, `3d`, `pdal`) and external ones from the third-party providers GDAL, SAGA and GRASS GIS through the QGIS interface. Now, we are ready for some geocomputation with QGIS and friends, from within R! Let's try two example case studies. The first one shows how to unite two polygonal datasets with different borders\index{union} (Section \@ref(qgis-vector)). The second one focuses on deriving new information from a digital elevation model represented as a raster (Section \@ref(qgis-raster)). ### Vector data {#qgis-vector} Consider a situation when you have two polygon objects with different spatial units (e.g., regions, administrative units). Our goal is to merge these two objects into one, containing all of the boundary lines and related attributes. We use again the incongruent polygons\index{spatial congruence} we have already encountered in Section \@ref(incongruent) (Figure \@ref(fig:uniondata)). Both polygon datasets are available in the **spData** package, and for both we would like to use a geographic CRS\index{CRS!geographic} (see also Chapter \@ref(reproj-geo-data)). ```{r 09-gis-4} data("incongruent", "aggregating_zones", package = "spData") incongr_wgs = st_transform(incongruent, "EPSG:4326") aggzone_wgs = st_transform(aggregating_zones, "EPSG:4326") ``` ```{r uniondata, echo=FALSE, fig.cap="Two areal units: incongruent (black lines) and aggregating zones (red borders).", fig.width=8} #| message: FALSE #| results: hide library(tmap) tm_shape(incongr_wgs) + tm_polygons(col = "gray5") + tm_shape(aggzone_wgs) + tm_borders(col_alpha = 0.5, col = "red") + tm_add_legend(type = "lines", labels = c("incongr_wgs", "aggzone_wgs"), col = c("gray5", "red"), lwd = 3, position = tm_pos_in("right", "top")) + tm_scalebar(position = c("left", "bottom"), breaks = c(0, 0.5, 1)) + tm_layout(frame = FALSE, legend.text.size = 1, inner.margins = c(0.02, 0.02, 0.03, 0.02)) ``` The first step is to find an algorithm that can merge two vector objects. To list all of the available algorithms, we can use the `qgis_algorithms()` function. This function returns a data frame containing all of the available providers and the algorithms they contain.^[Therefore, if you cannot see an expected provider, it is probably because you still need to install some external GIS software.] ```{r, eval=FALSE} # output not shown qgis_algorithms() ``` To find an algorithm, we can use the `qgis_search_algorithms()` function. Assuming that the short description of the function contains the word "union"\index{union}, we can run the following code to find the algorithm of interest: ```{r, eval=FALSE} qgis_search_algorithms("union") #> # A tibble: 2 × 5 #> provider provider_title group algorithm algorithm_title #> #> 1 native QGIS (native c++) Vector overlay native:multiunion Union (multiple) #> 2 native QGIS (native c++) Vector overlay native:union Union ``` One of the algorithms on the above list, `"native:union"`, sounds promising. The next step is to find out what this algorithm does and how we can use it. This is the role of the `qgis_show_help()`, which returns a short summary of what the algorithm does, its arguments, and outputs.^[We can also extract some of information independently with `qgis_get_description()`, `qgis_get_argument_specs()`, and `qgis_get_output_specss()`.] This makes its output rather long. The following command returns a data frame with each row representing an argument required by `"native:union"` and columns with the name, description, type, default value, available values, and acceptable values associated with each: ```{r 09-gis-6, eval=FALSE, linewidth=80} alg = "native:union" union_arguments = qgis_get_argument_specs(alg) union_arguments #> # A tibble: 5 × 6 #> name description qgis_type default_value available_values acceptable_... #> #> 1 INPUT Input layer source #> 2 OVERLAY Overlay la… source #> 3 OVERLA… Overlay fi… string #> 4 OUTPUT Union sink #> 5 GRID_S… Grid size number #> [[1]] #> [1] "A numeric value" #> [2] "field:FIELD_NAME to use a data defined value taken from the FIELD_NAME #> field" #> [3] "expression:SOME EXPRESSION to use a data defined value calculated using #> a custom QGIS expression" ``` The arguments, contained in `union_arguments$name`, are `INPUT`, `OVERLAY`, `OVERLAY_FIELDS_PREFIX`, and `OUTPUT`. `union_arguments$acceptable_values` contains a list with the possible input values for each argument. Many functions require inputs representing paths to a vector layer; **qgisprocess** functions accept `sf` objects for such arguments. Objects from the **terra** and **stars** package can be used when a "path to a raster layer" is expected. This can be very convenient, but we recommend providing the path to your spatial data on disk when you only read it in to submit it to a **qgisprocess** algorithm: the first thing **qgisprocess** does when executing a geoalgorithm is to export the spatial data living in your R session back to disk in a format known to QGIS such as .gpkg or .tif files. This can increase algorithm runtimes. The main function of **qgisprocess** is `qgis_run_algorithm()`, which sends inputs to QGIS and returns the outputs. It accepts the algorithm name and a set of named arguments shown in the help list, and it performs expected calculations. In our case, three arguments seem important: `INPUT`, `OVERLAY`, and `OUTPUT`. The first one, `INPUT`, is our main vector object `incongr_wgs`, while the second one, `OVERLAY`, is `aggzone_wgs`. The last argument, `OUTPUT`, is an output file name, which **qgisprocess** will automatically choose and create in `tempdir()` if none is provided. ```{r 09-gis-7, eval=FALSE} union = qgis_run_algorithm(alg, INPUT = incongr_wgs, OVERLAY = aggzone_wgs ) union #> $ OUTPUT: 'qgis_outputVector' chr "/tmp/...gpkg" ``` Running the above line of code will save our two input objects into temporary .gpkg files, run the selected algorithm on them, and return a temporary .gpkg file as the output. The **qgisprocess** package stores the `qgis_run_algorithm()` result as a list containing, in this case, a path to the output file. We can either read this file back into R with `read_sf()` (e.g., `union_sf = read_sf(union[[1]])`) or directly with `st_as_sf()`: ```{r, eval=FALSE} union_sf = st_as_sf(union) ``` Note that the QGIS\index{QGIS} union\index{vector!union} operation merges the two input layers into one layer by using the intersection\index{vector!intersection} and the symmetrical difference of the two input layers (which, by the way, is also the default when doing a union operation in GRASS GIS\index{GRASS GIS} and SAGA\index{SAGA}). This is **not** the same as `st_union(incongr_wgs, aggzone_wgs)` (see the Exercises)! The result, `union_sf`, is a multipolygon with a larger number of features than two input objects. Notice, however, that many of these polygons are small and do not represent real areas but are rather a result of our two datasets having a different level of detail. These artifacts of error are called sliver polygons\index{sliver polygons} (see red-colored polygons in the left panel of Figure \@ref(fig:sliver)). One way to identify slivers is to find polygons with comparatively very small areas, here, e.g., 25000 m^2^, and next remove them. Let's search for an appropriate algorithm. ```{r, eval=FALSE} qgis_search_algorithms("clean") #> # A tibble: 1 × 5 #> provider provider_title group algorithm algorithm_title #> #> 1 grass GRASS Vector (v.*) grass:v.clean v.clean ``` This time the found algorithm, `v.clean`, is not included in QGIS, but GRASS GIS\index{GRASS GIS}. GRASS GIS's `v.clean` is a powerful tool for cleaning topology of spatial vector data\index{topology cleaning}. Importantly, we can use it through **qgisprocess**. ```{block2 grass7, type='rmdnote'} The GRASS GIS provider in QGIS was called `grass7` until QGIS version 3.34. Thus, if you have an older QGIS version, you must prefix the algorithms with `grass7` instead of `grass`. ``` Similar to the previous step, we should start by looking at this algorithm's help. ```{r, eval=FALSE} qgis_show_help("grass:v.clean") ``` We have omitted the output here, because the help text is quite long and contains a lot of arguments.^[Also note that these arguments, contrary to the QGIS's ones, are in lowercase.] This is because `v.clean` is a multi-tool -- it can clean different types of geometries and solve different types of topological problems. For this example, let's focus on just a few arguments, however, we encourage you to visit this [algorithm's documentation](https://grass.osgeo.org/grass-stable/manuals/v.clean.html) to learn more about `v.clean` capabilities. ```{r, eval=FALSE} qgis_get_argument_specs("grass:v.clean") |> select(name, description) |> slice_head(n = 4) #> # A tibble: 4 × 2 #> name description #> #> 1 input Layer to clean #> 2 type Input feature type #> 3 tool Cleaning tool #> 4 threshold Threshold (comma separated for each tool) ``` The main argument for this algorithm is `input` -- our vector object. Next, we need to select a tool -- a cleaning method. ^[It is also possible to select several tools, which will then be executed sequentially.] About a dozen tools exist in `v.clean` allowing to remove duplicate geometries, remove small angles between lines, or remove small areas, among others. In this case, we are interested in the latter tool, `rmarea`. Several of the tools, `rmarea` included, expect an additional argument `threshold`, whose behavior depends on the selected tool. In our case, the `rmarea` tool removes all areas smaller or equal to a provided `threshold`. Note that the threshold must be specified in square meters regardless of the coordinate reference system of the input layer. Let's run this algorithm and convert its output into a new `sf` object `clean_sf`. ```{r 09-gis-7c, eval=FALSE, message=FALSE} clean = qgis_run_algorithm("grass:v.clean", input = union_sf, tool = "rmarea", threshold = 25000 ) clean_sf = st_as_sf(clean) ``` The result, the right panel of Figure \@ref(fig:sliver), looks as expected -- sliver polygons are now removed. ```{r sliver, echo=FALSE, fig.cap="Sliver polygons colored in red (left panel). Cleaned polygons (right panel)."} knitr::include_graphics("images/10-sliver.png") ``` ### Raster data {#qgis-raster} Digital elevation models (DEMs)\index{digital elevation model} contain elevation information for each raster cell. They are used for many purposes, including satellite navigation, water flow models, surface analysis, or visualization. Here, we are interested in deriving new information from a DEM raster that could be used as predictors for statistical learning. Various terrain parameters can be helpful, for example, for the prediction of landslides (see Chapter \@ref(spatial-cv)). For this section, we will use `dem.tif` -- a digital elevation model of the Mongón study area (downloaded from the Land Process Distributed Active Archive Center, see also `?dem.tif`). It has a resolution of about 30 x 30 meters and uses a projected CRS. ```{r, eval=FALSE, message=FALSE} library(qgisprocess) library(terra) dem = system.file("raster/dem.tif", package = "spDataLarge") ``` The **terra** package's `terrain()` command already allows the calculation of several fundamental topographic characteristics such as slope, aspect, TPI (*Topographic Position Index*), TRI (*Topographic Ruggedness Index*), roughness, and flow directions. However, GIS programs offer many more terrain characteristics, some of which can be more suitable in certain contexts. For example, the topographic wetness index (TWI)\index{topographic wetness index} was found useful in studying hydrological and biological processes [@sorensen_calculation_2006]. Let's search the algorithm list for this index using `"wetness"` as keyword. ```{r, eval=FALSE} qgis_search_algorithms("wetness") |> dplyr::select(provider_title, algorithm) |> head(2) #> # A tibble: 2 × 2 #> provider_title algorithm #> #> 1 SAGA Next Gen sagang:sagawetnessindex #> 2 SAGA Next Gen sagang:topographicwetnessindexonestep ``` An output of the above code suggests that the desired algorithm exists in the SAGA software\index{SAGA}.^[TWI can be also calculated using the `r.topidx` GRASS GIS function.] Though SAGA is a hybrid GIS, its main focus has been on raster processing, and here, particularly on digital elevation models\index{digital elevation model} (soil properties, terrain attributes, climate parameters). Hence, SAGA is especially good at the fast processing of large (high-resolution) raster\index{raster} datasets [@conrad_system_2015]. The `"sagang:sagawetnessindex"` algorithm is actually a modified TWI, that results in a more realistic soil moisture potential for the cells located in valley floors [@bohner_spatial_2006]. ```{r, eval=FALSE} qgis_show_help("sagang:sagawetnessindex") ``` Here, we stick with the default values for all arguments. Therefore, we only have to specify one argument -- the input `DEM`. Of course, when applying this algorithm you should make sure that the parameter values are in correspondence with your study aim.^[The additional arguments of `"sagang:sagawetnessindex"` are well-explained at https://gis.stackexchange.com/a/323454/20955.] Before running the SAGA algorithm from within QGIS, we change the default raster output format from `.tif` to SAGA's native raster format `.sdat`. Hence, all output rasters that we do not specify ourselves will from now on be written to the `.sdat` format. Depending on the software versions (SAGA, GDAL) you are using, this might not be necessary, but often enough this will save you trouble when trying to read-in output rasters created with SAGA. ```{r, eval=FALSE} options(qgisprocess.tmp_raster_ext = ".sdat") dem_wetness = qgis_run_algorithm("sagang:sagawetnessindex", DEM = dem ) ``` `"sagang:sagawetnessindex"` returns not one but four rasters -- catchment area, catchment slope, modified catchment area, and topographic wetness index. We can read a selected output by providing an output name in the `qgis_as_terra()` function. And since we are done with the SAGA processing from within QGIS, we change the raster output format back to `.tif`. ```{r, eval=FALSE} dem_wetness_twi = qgis_as_terra(dem_wetness$TWI) # plot(dem_wetness_twi) options(qgisprocess.tmp_raster_ext = ".tif") ``` You can see the TWI map in the left panel of Figure \@ref(fig:qgis-raster-map). The topographic wetness index is unitless: its low values represent areas that will not accumulate water, while higher values show areas that will accumulate water at increasing levels. Information from digital elevation models can also be categorized, for example, to geomorphons\index{geomorphons} -- the geomorphological phenotypes consisting of ten classes that represent terrain forms, such as slopes, ridges, or valleys [@jasiewicz_geomorphons_2013]. These phenotypes are used in many studies, including landslide susceptibility, ecosystem services, human mobility, and digital soil mapping. The original implementation of the geomorphons' algorithm was created in GRASS GIS, and we can find it in the **qgisprocess** list as `"grass:r.geomorphon"`: ```{r, eval=FALSE} qgis_search_algorithms("geomorphon") #> [1] "grass:r.geomorphon" "sagang:geomorphons" qgis_show_help("grass:r.geomorphon") # output not shown ``` Calculation of geomorphons requires an input DEM (`elevation`) and can be customized with a set of optional arguments. It includes, `search` -- a length for which the line-of-sight is calculated, and ``-m`` -- a flag specifying that the search value will be provided in meters (and not the number of cells). More information about additional arguments can be found in the original paper and the [GRASS GIS documentation](https://grass.osgeo.org/grass-stable/manuals/r.geomorphon.html). ```{r, eval=FALSE} dem_geomorph = qgis_run_algorithm("grass:r.geomorphon", elevation = dem, `-m` = TRUE, search = 120 ) ``` Our output, `dem_geomorph$forms`, contains a raster file with ten categories -- each representing a terrain form. We can read it into R with `qgis_as_terra()`, and then visualize it (Figure \@ref(fig:qgis-raster-map), right panel) or use it in our subsequent calculations. ```{r, eval=FALSE} dem_geomorph_terra = qgis_as_terra(dem_geomorph$forms) ``` Interestingly, there are connections between some geomorphons and the TWI values, as shown in Figure \@ref(fig:qgis-raster-map). The largest TWI values mostly occur in valleys and hollows, while the lowest values are seen, as expected, on ridges. ```{r qgis-raster-map, echo=FALSE, fig.cap="Topographic wetness index (TWI, left panel) and geomorphons (right panel) derived for the Mongón study area."} knitr::include_graphics("images/10-qgis-raster-map.png") ``` ## SAGA {#saga} The System for Automated Geoscientific Analyses (SAGA\index{SAGA}; Table \@ref(tab:gis-comp)) provides the possibility to execute SAGA modules via the command-line interface\index{command line interface} (`saga_cmd.exe` under Windows and just `saga_cmd` under Linux) (see the [SAGA wiki on modules](https://sourceforge.net/p/saga-gis/wiki/Executing%20Modules%20with%20SAGA%20CMD/)). In addition, there is a Python interface (SAGA Python API\index{API}). **Rsagacmd**\index{Rsagacmd (package)} uses the former to run SAGA\index{SAGA} from within R. We will use **Rsagacmd** in this section to delineate areas with similar values of the normalized difference vegetation index (NDVI) of the Mongón study area in Peru from September in the year 2000 (Figure \@ref(fig:sagasegments), left panel) by using a seeded region growing algorithm from SAGA\index{segmentation}.^[Read Section \@ref(local-operations) on details of how to calculate NDVI from a remote-sensing image.] ```{r, eval=FALSE} ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) ``` To start using **Rsagacmd**, we need to run the `saga_gis()` function. It serves two main purposes: - It dynamically^[This means that the available libraries will depend on the installed SAGA version.] creates a new object that contains links to all valid SAGA libraries and tools - It sets up general package options, such as `raster_backend` (R package to use for handling raster data), `vector_backend` (R package to use for handling vector data), and `cores` (a maximum number of CPU cores used for processing, default: all) ```{r, eval=FALSE} library(Rsagacmd) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") ``` Our `saga` object contains connections to all of the available SAGA tools. It is organized as a list of libraries (groups of tools), and inside of a library it has a list of tools. We can access any tool with the `$` sign (remember to use TAB for autocompletion). The seeded region growing algorithm works\index{segmentation!seeded region growing algorithm} in two main steps [@adams_seeded_1994;@bohner_image_2006]. First, initial cells ("seeds") are generated by finding the cells with the smallest variance in local windows of a specified size\index{autocorrelation!spatial}. Second, the region growing algorithm is used to merge neighboring pixels of the seeds to create homogeneous areas. ```{r, eval=FALSE} sg = saga$imagery_segmentation$seed_generation ``` In the above example, we first pointed to the `imagery_segmentation` library and then its `seed_generation` tool. We also assigned it to the `sg` object, not to retype the whole tool code in our next steps.^[You can read more about the tool at https://saga-gis.sourceforge.io/saga_tool_doc/8.3.0/imagery_segmentation_2.html.] If we just type `sg`, we will get a quick summary of the tool and a data frame with its parameters, descriptions, and defaults. You may also use `tidy(sg)` to extract just the parameters' table. The `seed_generation` tool takes a raster dataset as its first argument (`features`); optional arguments include `band_width` that specifies the size of initial polygons. ```{r, eval=FALSE} ndvi_seeds = sg(ndvi, band_width = 2) # plot(ndvi_seeds$seed_grid) ``` Our output is a list of three objects: `variance` -- a raster map of local variance, `seed_grid` -- a raster map with the generated seeds, and `seed_points` -- a spatial vector object with the generated seeds. The second SAGA tool we use is `seeded_region_growing`.^[You can read more about the tool at https://saga-gis.sourceforge.io/saga_tool_doc/8.3.0/imagery_segmentation_3.html.] The `seeded_region_growing` tool requires two inputs: our `seed_grid` calculated in the previous step and the `ndvi` raster object. Additionally, we can specify several parameters, such as `normalize` to standardize the input features, `neighbour` (4- or 8-neighborhood), and `method`. The last parameter can be set to either `0` or `1` (region growing is based on raster cells' values and their positions or just the values). For a more detailed description of the method, see @bohner_image_2006. Here, we will only change `method` to `1`, meaning that our output regions will be created only based on the similarity of their NDVI values. ```{r, eval=FALSE} srg = saga$imagery_segmentation$seeded_region_growing ndvi_srg = srg(ndvi_seeds$seed_grid, ndvi, method = 1) plot(ndvi_srg$segments) ``` The tool returns a list of three objects: `segments`, `similarity`, `table`. The `similarity` object is a raster showing similarity between the seeds and the other cells, and `table` is a data frame storing information about the input seeds. Finally, `ndvi_srg$segments` is a raster with our resulting areas (Figure \@ref(fig:sagasegments), right panel). We can convert it into polygons with `as.polygons()` and `st_as_sf()` (Section \@ref(spatial-vectorization)). ```{r, eval=FALSE} ndvi_segments = ndvi_srg$segments |> as.polygons() |> st_as_sf() ``` ```{r sagasegments, echo=FALSE, fig.cap="Normalized difference vegetation index (NDVI, left panel) and NDVI-based segments derived using the seeded region growing algorithm for the Mongón study area."} knitr::include_graphics("images/10-saga-segments.png") ``` The resulting polygons (segments) represent areas with similar values. They can also be further aggregated into larger polygons using various techniques, such as clustering (e.g., *k*-means), regionalization (e.g., SKATER) or supervised classification methods. You can try to do it in the Exercises. R also has other tools to achieve the goal of creating polygons with similar values (so-called segments). It includes the **SegOptim** package [@goncalves_segoptim_2019] that allows running several image segmentation algorithms and **supercells** package [@nowosad_extended_2022] that implements superpixels\index{segmentation!superpixels} algorithm SLIC to work with geospatial data. ## GRASS GIS {#grass} The U.S. Army - Construction Engineering Research Laboratory (USA-CERL) created the core of the Geographical Resources Analysis Support System (GRASS GIS)\index{GRASS GIS} (Table \@ref(tab:gis-comp); @neteler_open_2008) from 1982 to 1995. Academia continued this work since 1997. Similar to SAGA\index{SAGA}, GRASS GIS focused on raster processing in the beginning while, only later since GRASS GIS 6.0, adding advanced vector functionality [@bivand_applied_2013]. GRASS GIS stores the input data in an internal database. With regard to vector data, GRASS GIS is by default a topological GIS, i.e., it only stores the geometry of adjacent features once. SQLite is the default database driver for vector attribute management, and attributes are linked to the geometry, i.e., to the GRASS GIS database, via keys ([GRASS GIS vector management](https://grasswiki.osgeo.org/wiki/Vector_Database_Management#GRASS_GIS_vector_management_model)). Before one can use GRASS GIS, one has to set up the GRASS GIS database\index{spatial database} (also from within R), and users might find this process a bit intimidating in the beginning. First of all, the GRASS GIS database requires its own directory, which, in turn, contains a location (see the [GRASS GIS Database](https://grass.osgeo.org/grass-stable/manuals/grass_database.html) help pages at [grass.osgeo.org](https://grass.osgeo.org/grass-stable/manuals/index.html) for further information). The location stores the geodata for one project or one area. Within one location, several mapsets can exist that typically refer to different users or different tasks. Each location also has a PERMANENT mapset -- a mandatory mapset that is created automatically. In order to share geographic data with all users of a project, the database owner can add spatial data to the PERMANENT mapset. In addition, the PERMANENT mapset stores the projection, the spatial extent and the default resolution for raster data. So, to sum it all up -- the GRASS GIS database may contain many locations (all data in one location have the same CRS), and each location can store many mapsets (groups of datasets). Please refer to @neteler_open_2008 and the [GRASS GIS quick start](https://grass.osgeo.org/grass-stable/manuals/helptext.html) for more information on the GRASS GIS spatial database\index{spatial database} system. To quickly use GRASS GIS from within R, we will use the **link2GI** package, however, one can also set up the GRASS GIS database step-by-step. See [GRASS within R](https://grasswiki.osgeo.org/wiki/R_statistics/rgrass#GRASS_within_R) for how to do so. Please note that the code instructions in the following paragraphs might be hard to follow when using GRASS GIS for the first time but by running through the code line-by-line and by examining the intermediate results, the reasoning behind it should become even clearer. Here, we introduce **rgrass**\index{rgrass (package)} with one of the most interesting problems in GIScience: the traveling salesman problem\index{traveling salesman}. Suppose a traveling salesman would like to visit 24 customers. Additionally, the salesman would like to start and finish the journey at home which makes a total of 25 locations while covering the shortest distance possible. There is a single best solution to this problem; however, to check all of the possible solutions, it is (mostly) impossible for modern computers [@longley_geographic_2015]. In our case, the number of possible solutions correspond to `(25 - 1)! / 2`, i.e., the factorial of 24 divided by 2 (since we do not differentiate between forward or backward direction). Even if one iteration can be done in a nanosecond, this still corresponds to `r format(factorial(25 - 1) / (2 * 10^9 * 3600 * 24 * 365))` years. Luckily, there are clever, almost optimal solutions which run in a tiny fraction of this inconceivable amount of time. GRASS GIS\index{GRASS GIS} provides one of these solutions (for more details, see [v.net.salesman](https://grass.osgeo.org/grass-stable/manuals/v.net.salesman.html)). In our use case, we would like to find the shortest path\index{shortest route} between the first 25 bicycle stations (instead of customers) on London's streets (and we simply assume that the first bike station corresponds to the home of our traveling salesman\index{traveling salesman}). ```{r 09-gis-24} data("cycle_hire", package = "spData") points = cycle_hire[1:25, ] ``` Aside from the cycle hire points data, we need a street network for this area. We can download it from OpenStreetMap\index{OpenStreetMap} with the help of the **osmdata** \index{osmdata (package)} package (see also Section \@ref(retrieving-data)). To do this, we constrain the query of the street network (in OSM language called "highway") to the bounding box\index{bounding box} of `points`, and attach the corresponding data as an `sf`-object\index{sf}. `osmdata_sf()` returns a list with several spatial objects (points, lines, polygons, etc.), but here, we only keep the line objects with their related ids.^[As a convenience to the reader, one can attach `london_streets` to the global environment using `data("london_streets", package = "spDataLarge")`.] ```{r 09-gis-25, eval=FALSE} library(osmdata) b_box = st_bbox(points) london_streets = opq(b_box) |> add_osm_feature(key = "highway") |> osmdata_sf() london_streets = london_streets[["osm_lines"]] london_streets = select(london_streets, osm_id) ``` Now that we have the data, we can go on and initiate a GRASS GIS\index{GRASS GIS} session. Luckily, `linkGRASS()` of the **link2GI** packages lets one set up the GRASS GIS environment with just one line of code. The only thing you need to provide is a spatial object which determines the projection and the extent of the spatial database\index{spatial database}. First, `linkGRASS()` finds all GRASS GIS\index{GRASS GIS} installations on your computer. Since we have set `ver_select` to `TRUE`, we can interactively choose one of the found GRASS GIS-installations. If there is just one installation, the `linkGRASS()` automatically chooses it. Second, `linkGRASS()` establishes a connection to GRASS GIS. ```{r 09-gis-30, eval=FALSE} library(rgrass) link2GI::linkGRASS(london_streets, ver_select = TRUE) ``` Before we can use GRASS GIS geoalgorithms\index{geoalgorithm}, we also need to add data to GRASS GIS's spatial database\index{spatial database}. Luckily, the convenience function `write_VECT()` does this for us. (Use `write_RAST()` for raster data.) In our case, we add the street and cycle hire point data while using only the first attribute column, and name them as `london_streets` and `points` in GRASS GIS. ```{r 09-gis-31, eval=FALSE} write_VECT(terra::vect(london_streets), vname = "london_streets") write_VECT(terra::vect(points[, 1]), vname = "points") ``` The **rgrass** package expects its inputs and gives its outputs as **terra** objects. Therefore, we need to convert our `sf` spatial vectors to **terra**'s `SpatVector`s using the `vect()` function to be able to use `write_VECT()`.^[You can learn more how to convert between spatial classes in R by reading the (Conversions between different spatial classes in R)[https://geocompx.org/post/2021/spatial-classes-conversion/] blog post and the (Coercion between object formats)[https://CRAN.R-project.org/package=rgrass/vignettes/coerce.html] vignette.] Now, both datasets exist in the GRASS GIS database. To perform our network\index{network} analysis, we need a topologically clean street network\index{topology cleaning}. GRASS GIS's `"v.clean"` takes care of the removal of duplicates, small angles and dangles, among others. Here, we break lines at each intersection to ensure that the subsequent routing algorithm can actually turn right or left at an intersection, and save the output in a GRASS GIS object named `streets_clean`. ```{r 09-gis-32, eval=FALSE} execGRASS( cmd = "v.clean", input = "london_streets", output = "streets_clean", tool = "break", flags = "overwrite" ) ``` ```{block2 06-raster-vector-22, type='rmdnote'} To learn about the possible arguments and flags of the GRASS GIS modules, you can use the `help` flag. For example, try `execGRASS("g.region", flags = "help")`. ``` It is likely that a few of our cycling station points will not lie exactly on a street segment. However, to find the shortest route\index{shortest route} between them, we need to connect them to the nearest streets segment. `"v.net"`'s connect-operator does exactly this. We save its output in `streets_points_con`. ```{r 09-gis-32b, eval=FALSE} execGRASS( cmd = "v.net", input = "streets_clean", output = "streets_points_con", points = "points", operation = "connect", threshold = 0.001, flags = c("overwrite", "c") ) ``` The resulting clean dataset serves as input for the `"v.net.salesman"` algorithm, which finally finds the shortest route between all cycle hire stations. One of its arguments is `center_cats`, which requires a numeric range as input. This range represents the points for which a shortest route should be calculated. Since we would like to calculate the route for all cycle stations, we set it to `1-25`. To access the GRASS GIS help page of the traveling salesman\index{traveling salesman} algorithm\index{algorithm}, run `execGRASS("g.manual", entry = "v.net.salesman")`. ```{r 09-gis-33, eval=FALSE} execGRASS( cmd = "v.net.salesman", input = "streets_points_con", output = "shortest_route", center_cats = paste0("1-", nrow(points)), flags = "overwrite" ) ``` To see our result, we read the result into R, convert it into an `sf`-object keeping only the geometry, and visualize it with the help of the **mapview** package (Figure \@ref(fig:grass-mapview) and Section \@ref(interactive-maps)). ```{r 09-gis-34, eval=FALSE} route = read_VECT("shortest_route") |> st_as_sf() |> st_geometry() mapview::mapview(route) + points ``` ```{r grass-mapview, fig.cap="Shortest route (blue line) between 24 cycle hire stations (blue dots) on the OSM street network of London.", fig.scap="Shortest route between 24 cycle hire stations.", echo=FALSE, out.width="80%"} knitr::include_graphics("images/10_shortest_route.png") ``` ```{r 09-gis-35, eval=FALSE, echo=FALSE} library(mapview) m_1 = mapview(route) + points mapview::mapshot(m_1, file = file.path(getwd(), "images/09_shortest_route.png"), remove_controls = c( "homeButton", "layersControl", "zoomControl" ) ) ``` There are a few important considerations to note in the process: - We could have used GRASS GIS's spatial database\index{spatial database} which allows faster processing. That means we have only exported geographic data at the beginning. Then we created new objects but only imported the final result back into R. To find out which datasets are currently available, run `execGRASS("g.list", type = "vector,raster", flags = "p")`. - We could have also accessed an already existing GRASS GIS spatial database from within R. Prior to importing data into R, you might want to perform some (spatial) subsetting\index{vector!subsetting}. Use `"v.select"` and `"v.extract"` for vector data. `"db.select"` lets you select subsets of the attribute table of a vector layer without returning the corresponding geometry. - You can also start R from within a running GRASS GIS\index{GRASS GIS} session [for more information, please refer to @bivand_applied_2013]. - Refer to the excellent [GRASS GIS online help](https://grass.osgeo.org/grass-stable/manuals/) or `execGRASS("g.manual", flags = "i")` for more information on each available GRASS GIS geoalgorithm\index{geoalgorithm}. ## When to use what? To recommend a single R-GIS interface is hard since the usage depends on personal preferences, the tasks at hand, and your familiarity with different GIS\index{GIS} software packages which in turn probably depends on your domain. As mentioned previously, SAGA\index{SAGA} is especially good at the fast processing of large (high-resolution) raster\index{raster} datasets and frequently used by hydrologists, climatologists and soil scientists [@conrad_system_2015]. GRASS GIS\index{GRASS GIS}, on the other hand, is the only GIS presented here supporting a topologically based spatial database which is especially useful for network analyses but also simulation studies. QGISS\index{QGIS} is much more user-friendly compared to GRASS GIS and SAGA, especially for first-time GIS users, and probably the most popular open-source GIS. Therefore, **qgisprocess**\index{qgisprocess (package)} is an appropriate choice for most use cases. Its main advantages are: - A unified access to several GIS, and therefore the provision of >1000 geoalgorithms (Table \@ref(tab:gis-comp)) including duplicated functionality, e.g., you can perform overlay-operations using QGIS-\index{QGIS}, SAGA-\index{SAGA} or GRASS GIS-geoalgorithms\index{GRASS GIS} - Automatic data format conversions (SAGA uses `.sdat` grid files and GRASS GIS uses its own database format, but QGIS will handle the corresponding conversions) - Its automatic passing of geographic R objects to QGIS geoalgorithms\index{geoalgorithm} and back into R - Convenience functions to support named arguments and automatic default value retrieval (as inspired by **rgrass**\index{rgrass (package)}) By all means, there are use cases when you certainly should use one of the other R-GIS bridges. Though QGIS is the only GIS providing a unified interface to several GIS\index{GIS} software packages, it only provides access to a subset of the corresponding third-party geoalgorithms (for more information, please refer to @muenchow_rqgis:_2017). Therefore, to use the complete set of SAGA and GRASS GIS functions, stick with **Rsagacmd**\index{Rsagacmd (package)} and **rgrass**. In addition, if you would like to run simulations with the help of a geodatabase\index{spatial database} [@krug_clearing_2010], use **rgrass** directly since **qgisprocess** always starts a new GRASS GIS session for each call. Finally, if you need topological correct data and/or spatial database management functionality such as multi-user access, we recommend the usage of GRASS GIS. Please note that there are a number of further GIS software packages that have a scripting interface but for which there is no dedicated R package that accesses these: gvSig, OpenJump, and the Orfeo Toolbox.^[Please note that **link2GI** provides a partial integration with the Orfeo Toolbox\index{Orfeo Toolbox} and that you can also access the Orfeo Toolbox geoalgorithms via **qgisprocess**. Note also that TauDEM\index{TauDEM} can be accessed from R with package **traudem**.] ## Bridges to GDAL {#gdal} As discussed in Chapter \@ref(read-write), GDAL\index{GDAL} is a low-level library that supports many geographic data formats. GDAL is so effective that most GIS programs use GDAL\index{GDAL} in the background for importing and exporting geographic data, rather than reinventing the wheel and using bespoke read-write code. But GDAL\index{GDAL} offers more than data I/O. It has [geoprocessing tools](https://gdal.org/programs/index.html) for vector and raster data, functionality to create [tiles](https://gdal.org/programs/gdal2tiles.html#gdal2tiles) for serving raster data online, and rapid [rasterization](https://gdal.org/programs/gdal_rasterize.html#gdal-rasterize) of vector data. Since GDAL is a command-line tool, all its commands can be accessed from within R via the `system()` command. The code chunk below demonstrates this functionality: `linkGDAL()` searches the computer for a working GDAL\index{GDAL} installation and adds the location of the executable files to the PATH variable, allowing GDAL to be called (usually only needed under Windows). ```{r, eval=FALSE} link2GI::linkGDAL() ``` Now we can use the `system()` function to call any of the GDAL tools. For example, `ogrinfo` provides metadata of a vector dataset. Here we will call this tool with two additional flags: `-al` to list all features of all layers and `-so` to get a summary only (and not a complete geometry list): ```{r 09-gis-36, eval=FALSE, message=FALSE} our_filepath = system.file("shapes/world.gpkg", package = "spData") cmd = paste("ogrinfo -al -so", our_filepath) system(cmd) #> INFO: Open of `.../spData/shapes/world.gpkg' #> using driver `GPKG' successful. #> #> Layer name: world #> Geometry: Multi Polygon #> Feature Count: 177 #> Extent: (-180.000000, -89.900000) - (179.999990, 83.645130) #> Layer SRS WKT: #> ... ``` Other commonly used GDAL tools include: - `gdalinfo`: provides metadata of a raster dataset - `gdal_translate`: converts between different raster file formats - `ogr2ogr`: converts between different vector file formats - `gdalwarp`: reprojects, transforms, and clips raster datasets - `gdaltransform`: transforms coordinates Visit https://gdal.org/programs/ to see the complete list of GDAL tools and to read their help files. The 'link' to GDAL provided by **link2GI** could be used as a foundation for doing more advanced GDAL work from the R or system CLI. TauDEM (https://hydrology.usu.edu/taudem/) and the Orfeo Toolbox (https://www.orfeo-toolbox.org/) are other spatial data processing libraries/programs offering a command-line interface -- the above example shows how to access these libraries from the system command line via R. This in turn could be the starting point for creating a proper interface to these libraries in the form of new R packages. Before diving into a project to create a new bridge, however, it is important to be aware of the power of existing R packages and that `system()` calls may not be platform-independent (they may fail on some computers). On the other hand, **sf** and **terra** brings most of the power provided by GDAL\index{GDAL}, GEOS\index{GEOS} and PROJ\index{PROJ} to R via the R/C++\index{C++} interface provided by **Rcpp**, which avoids `system()` calls.^[There are also **vapour** and **gdalraster** that provide low-level interfaces to GDAL\index{GDAL}.] ## Bridges to spatial databases {#postgis} \index{spatial database} Spatial database management systems (spatial DBMSs) store spatial and non-spatial data in a structured way. They can organize large collections of data into related tables (entities) via unique identifiers (primary and foreign keys) and implicitly via space (think for instance of a spatial join). This is useful because geographic datasets tend to become big and messy quite quickly. Databases enable storing and querying large datasets efficiently based on spatial and non-spatial fields, and provide multi-user access and topology\index{topological relations} support. The most important open source spatial database\index{spatial database} is PostGIS\index{PostGIS} [@obe_postgis_2015].^[ SQLite/SpatiaLite are certainly also important, but implicitly we have already introduced this approach since GRASS GIS\index{GRASS GIS} is using SQLite in the background (see Section \@ref(grass)). ] R bridges to spatial DBMSs such as PostGIS\index{PostGIS} are important, allowing access to huge data stores without loading several gigabytes of geographic data into RAM, and likely crashing the R session. The remainder of this section shows how PostGIS can be called from R, based on "Hello real-world" from *PostGIS in Action, Second Edition* [@obe_postgis_2015].^[ Thanks to Manning Publications, Regina Obe and Leo Hsu for permission to use this example. ] The subsequent code requires a working internet connection, since we are accessing a PostgreSQL/PostGIS\index{PostGIS} database which is living in the QGIS Cloud (https://qgiscloud.com/).^[ QGIS\index{QGIS} Cloud lets you store geographic data and maps in the cloud. In the background, it uses QGIS Server and PostgreSQL/PostGIS. This way, the reader can follow the PostGIS example without the need to have PostgreSQL/PostGIS installed on a local machine. Thanks to the QGIS Cloud team for hosting this example. ] Our first step here is to create a connection to a database by providing its name, host name, and user information. ```{r 09-gis-37, eval=FALSE} library(RPostgreSQL) conn = dbConnect( drv = PostgreSQL(), dbname = "rtafdf_zljbqm", host = "db.qgiscloud.com", port = "5432", user = "rtafdf_zljbqm", password = "d3290ead" ) ``` Our new object, `conn`, is just an established link between our R session and the database. It does not store any data. Often the first question is, 'which tables can be found in the database?'. This can be answered with `dbListTables()` as follows: ```{r 09-gis-38, eval=FALSE} dbListTables(conn) #> [1] "spatial_ref_sys" "topology" "layer" "restaurants" #> [5] "highways" ``` The answer is five tables. Here, we are only interested in the `restaurants` and the `highways` tables. The former represents the locations of fast-food restaurants in the US, and the latter are principal US highways. To find out about attributes available in a table, we can run `dbListFields`: ```{r 09-gis-39, eval=FALSE} dbListFields(conn, "highways") #> [1] "qc_id" "wkb_geometry" "gid" "feature" #> [5] "name" "state" ``` Now, as we know the available datasets, we can perform some queries -- ask the database some questions. The query needs to be provided in a language understandable by the database -- usually, it is SQL. The first query will select `US Route 1` in the state of Maryland (`MD`) from the `highways` table. Note that `read_sf()` allows us to read geographic data from a database if it is provided with an open connection to a database and a query. Additionally, `read_sf()` needs to know which column represents the geometry (here: `wkb_geometry`). ```{r 09-gis-40, eval=FALSE} query = paste( "SELECT *", "FROM highways", "WHERE name = 'US Route 1' AND state = 'MD';" ) us_route = read_sf(conn, query = query, geom = "wkb_geometry") ``` This results in an **sf**-object\index{sf} named `us_route` of type `MULTILINESTRING`. As we mentioned before, it is also possible to not only ask non-spatial questions, but also query datasets based on their spatial properties. To show this, the next example adds a 35-kilometer (35,000 m) buffer around the selected highway (Figure \@ref(fig:postgis)). ```{r 09-gis-41, eval=FALSE} query = paste( "SELECT ST_Union(ST_Buffer(wkb_geometry, 35000))::geometry", "FROM highways", "WHERE name = 'US Route 1' AND state = 'MD';" ) buf = read_sf(conn, query = query) ``` Note that this was a spatial query using functions (`ST_Union()`\index{vector!union}, `ST_Buffer()`\index{vector!buffers}) you should be already familiar with. You find them also in the **sf**-package, though here they are written in lowercase characters (`st_union()`, `st_buffer()`). In fact, function names of the **sf** package largely follow the PostGIS\index{PostGIS} naming conventions.^[ The prefix `st` stands for space/time. ] The last query will find all Hardee's restaurants (`HDE`) within the 35-km buffer zone (Figure \@ref(fig:postgis)). ```{r 09-gis-42, eval=FALSE, warning=FALSE} query = paste( "SELECT *", "FROM restaurants r", "WHERE EXISTS (", "SELECT gid", "FROM highways", "WHERE", "ST_DWithin(r.wkb_geometry, wkb_geometry, 35000) AND", "name = 'US Route 1' AND", "state = 'MD' AND", "r.franchise = 'HDE');" ) hardees = read_sf(conn, query = query) ``` Please refer to @obe_postgis_2015 for a detailed explanation of the spatial SQL query. Finally, it is good practice to close the database connection as follows:^[ It is important to close the connection here because QGIS Cloud (free version) allows only ten concurrent connections. ] ```{r 09-gis-43, eval=FALSE} RPostgreSQL::postgresqlCloseConnection(conn) ``` ```{r 09-gis-44, echo=FALSE} load("extdata/postgis_data.Rdata") ``` ```{r postgis, echo=FALSE, fig.cap="Visualization of the output of previous PostGIS commands showing the highway (black line), a buffer (light yellow) and four restaurants (red points) within the buffer.", fig.scap="Visualization of the output of previous PostGIS commands."} library(tmap) tm_shape(buf) + tm_polygons(fill = "#FFFDD0", col_alpha = 0.3) + tm_shape(us_route) + tm_lines(col = "black", lwd = 3) + tm_shape(hardees) + tm_symbols(fill = "#F10C26") + tm_add_legend(type = "lines", col = "black", lwd = 3, labels = "The US Route 1 highway") + tm_add_legend(type = "polygons", fill = "#FFFDD0", border.alpha = 0.3, labels = "35km buffer") + tm_add_legend(type = "symbols", fill = "#F10C26", labels = "Restaurants", shape = 21) + tm_scalebar() + tm_layout(frame = FALSE) ``` Unlike PostGIS, **sf** only supports spatial vector data. To query and manipulate raster data stored in a PostGIS database, use the **rpostgis** package [@bucklin_rpostgis_2018] and/or use command line tools such as `rastertopgsql` which comes as part of the PostGIS\index{PostGIS} installation. This subsection is only a brief introduction to PostgreSQL/PostGIS. Nevertheless, we would like to encourage the practice of storing geographic and non-geographic data in a spatial DBMS\index{spatial database} while only attaching those subsets to R's global environment which are needed for further (geo-)statistical analysis. Please refer to @obe_postgis_2015 for a more detailed description of the SQL queries presented and a more comprehensive introduction to PostgreSQL/PostGIS in general. PostgreSQL/PostGIS is a formidable choice as an open-source spatial database. But the same is true for the lightweight SQLite/SpatiaLite database engine and GRASS GIS\index{GRASS GIS} which uses SQLite in the background (see Section \@ref(grass)). If your datasets are too big for PostgreSQL/PostGIS and you require massive spatial data management and query performance, it may be worth exploring large-scale geographic querying on distributed computing systems. Such systems are outside the scope of this book, but it is worth mentioning that open source software providing this functionality exists. Prominent projects in this space include [GeoMesa](http://www.geomesa.org/) and [Apache Sedona](https://sedona.apache.org/). The [**apache.sedona**](https://cran.r-project.org/package=apache.sedona) package provides an interface to the latter. ## Bridges to cloud technologies and services {#cloud} In recent years, cloud technologies have become more and more prominent on the internet. This also includes their use to store and process spatial data. Major cloud computing providers (Amazon Web Services, Microsoft Azure / Planetary Computer, Google Cloud Platform, and others)\index{cloud computing} offer vast catalogs of open Earth observation data, such as the complete Sentinel-2\index{Sentinel-2} archive, on their platforms. We can use R and directly connect to and process data from these archives, ideally from a machine in the same cloud and region. Three promising developments that make working with such image archives on cloud platforms _easier_ and _more efficient_ are the [SpatioTemporal Asset Catalog (STAC)](https://stacspec.org)\index{STAC}, the [cloud-optimized GeoTIFF (COG)](https://www.cogeo.org/)\index{COG} image file format, and the concept of data cubes\index{data cube}. Section \@ref(staccog) introduces these individual developments and briefly describes how they can be used from R. Besides hosting large data archives, numerous cloud-based services\index{cloud computing} to process Earth observation data have been launched during the last few years. It includes the OpenEO initiative\index{OpenEO} -- a unified interface between programming languages (including R) and various cloud-based services. You can find more information about OpenEO in Section \@ref(openeo). ### STAC, COGs, and data cubes in the cloud {#staccog} The SpatioTemporal Asset Catalog (STAC)\index{STAC} is a general description format for spatiotemporal data that is used to describe a variety of datasets on cloud platforms including imagery, synthetic aperture radar (SAR) data, and point clouds. Besides simple static catalog descriptions, STAC-API presents a web service to query items (e.g., images) of catalogs by space, time, and other properties. In R, the **rstac** package\index{rstac (package)} [@simoes_rstac_2021] allows to connect to STAC-API endpoints and search for items. In the example below, we request all images from the [Sentinel-2 Cloud-Optimized GeoTIFF (COG) dataset on Amazon Web Services](https://registry.opendata.aws/sentinel-2-l2a-cogs)\index{COG} that intersect with a predefined area and time of interest. The result contains all found images and their metadata (e.g., cloud cover) and URLs pointing to actual files on AWS. ```{r 09-stac-example, eval=FALSE} library(rstac) # Connect to the STAC-API endpoint for Sentinel-2 data # and search for images intersecting our AOI s = stac("https://earth-search.aws.element84.com/v0") items = s |> stac_search(collections = "sentinel-s2-l2a-cogs", bbox = c(7.1, 51.8, 7.2, 52.8), datetime = "2020-01-01/2020-12-31") |> post_request() |> items_fetch() ``` Cloud storage differs from local hard disks and traditional image file formats do not perform well in cloud-based geoprocessing. Cloud-optimized GeoTIFF\index{COG} makes reading rectangular subsets of an image or reading images at lower resolution much more efficient. As an R user, you do not have to install anything to work with COGs because [GDAL](https://gdal.org)\index{GDAL} (and any package using it) can already work with COGs. However, keep in mind that the availability of COGs is a big plus while browsing through catalogs of data providers. For larger areas of interest, requested images are still relatively difficult to work with: they may use different map projections, may spatially overlap, and their spatial resolution often depends on the spectral band. The **gdalcubes** package\index{gdalcubes (package)} [@appel_gdalcubes_2019] can be used to abstract from individual images and to create and process image collections as four-dimensional data cubes\index{data cube}. The code below shows a minimal example to create a lower resolution (250 m) maximum NDVI composite from the Sentinel-2 images returned by the previous STAC-API search. ```{r 09-gdalcubes-example, eval=FALSE} library(gdalcubes) # Filter images by cloud cover and create an image collection object cloud_filter = function(x) { x[["eo:cloud_cover"]] < 10 } collection = stac_image_collection(items$features, property_filter = cloud_filter) # Define extent, resolution (250m, daily) and CRS of the target data cube v = cube_view(srs = "EPSG:3857", extent = collection, dx = 250, dy = 250, dt = "P1D") # "P1D" is an ISO 8601 duration string # Create and process the data cube cube = raster_cube(collection, v) |> select_bands(c("B04", "B08")) |> apply_pixel("(B08-B04)/(B08+B04)", "NDVI") |> reduce_time("max(NDVI)") # gdalcubes_options(parallel = 8) # plot(cube, zlim = c(0, 1)) ``` To filter images by cloud cover, we provide a property filter function that is applied on each STAC\index{STAC} result item while creating the image collection. The function receives available metadata of an image as input list and returns a single logical value such that only images for which the function yields TRUE will be considered. In this case, we ignore images with 10% or more cloud cover. For more details, please refer to this [tutorial presented at OpenGeoHub summer school 2021](https://appelmar.github.io/ogh2021/tutorial.html).^[Another tool for working with STAC\index{STAC} data in R is the **rsi** package that allows to get spatial data from STAC for a given time and location.] The combination of STAC\index{STAC}, COGs\index{COG}, and data cubes\index{data cube} forms a cloud-native workflow to analyze (large) collections of satellite imagery in the cloud\index{cloud computing}. These tools already form a backbone, for example, of the **sits** package\index{sits (package)}, which allows land use and land cover classification of big Earth observation data in R. The package builds EO data cubes from image collections available in cloud services and performs land classification of data cubes using various machine and deep learning algorithms. For more information about **sits**, visit https://e-sensing.github.io/sitsbook/ or read the related article [@rs13132428]. ### openEO OpenEO [@schramm_openeo_2021]\index{OpenEO} is an initiative to support interoperability among cloud services by defining a common language for processing the data. The initial idea has been described in an [r-spatial.org blog post](https://r-spatial.org/2016/11/29/openeo.html) and aims at making it possible for users to change between cloud services easily with as little code changes as possible. The [standardized processes](https://processes.openeo.org) use a multidimensional data cube model\index{data cube} as an interface to the data. Implementations are available for eight different backends (see https://hub.openeo.org) to which users can connect with R, Python, JavaScript, QGIS, or a web editor and define (and chain) processes on collections. Since the functionality and data availability differs among the backends, the **openeo** R package [@lahn_openeo_2021] dynamically loads available processes and collections from the connected backend. Afterwards, users can load image collections, apply and chain processes, submit jobs, and explore and plot results. The following code will connect to the [openEO platform backend](https://openeo.cloud/), request available datasets, processes, and output formats, define a process graph to compute a maximum NDVI image from Sentinel-2 data, and finally execute the graph after logging in to the backend. The openEO\index{OpenEO} platform backend includes a free tier, and registration is possible from existing institutional or internet platform accounts. ```{r 09-openeo-example, eval=FALSE} library(openeo) con = connect(host = "https://openeo.cloud") p = processes() # load available processes collections = list_collections() # load available collections formats = list_file_formats() # load available output formats # Load Sentinel-2 collection s2 = p$load_collection(id = "SENTINEL2_L2A", spatial_extent = list(west = 7.5, east = 8.5, north = 51.1, south = 50.1), temporal_extent = list("2021-01-01", "2021-01-31"), bands = list("B04", "B08")) # Compute NDVI vegetation index compute_ndvi = p$reduce_dimension(data = s2, dimension = "bands", reducer = function(data, context) { (data[2] - data[1]) / (data[2] + data[1]) }) # Compute maximum over time reduce_max = p$reduce_dimension(data = compute_ndvi, dimension = "t", reducer = function(x, y) { max(x) }) # Export as GeoTIFF result = p$save_result(reduce_max, formats$output$GTiff) # Login, see https://docs.openeo.cloud/getting-started/r/#authentication login(login_type = "oidc", provider = "egi", config = list(client_id = "...", secret = "...")) # Execute processes compute_result(graph = result, output_file = tempfile(fileext = ".tif")) ``` ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child("_10-ex.Rmd", quiet = TRUE, options = list(include = FALSE, eval = FALSE) ) cat(res, sep = "\n") ``` ================================================ FILE: 11-algorithms.Rmd ================================================ # Scripts, algorithms and functions {#algorithms} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} This chapter has minimal software prerequisites, as it primarily uses base R. Only, the **sf**\index{sf} package is used to check the results of an algorithm we will develop to calculate the area of polygons. In terms of prior knowledge, this chapter assumes you have an understanding of the geographic classes introduced in Chapter \@ref(spatial-class) and how they can be used to represent a wide range of input file formats (see Chapter \@ref(read-write)). ## Introduction {#intro-algorithms} Chapter \@ref(intro) established that geocomputation is not only about using existing tools, but developing new ones, "in the form of shareable R scripts and functions". This chapter teaches these building blocks of reproducible code. It also introduces low-level geometric algorithms, of the type used in Chapter \@ref(gis). Reading it should help you to understand how such algorithms work and to write code that can be used many times, by many people, on multiple datasets. The chapter cannot, by itself, make you a skilled programmer. Programming is hard and requires plenty of practice [@abelson_structure_1996]: > To appreciate programming as an intellectual activity in its own right you must turn to computer programming; you must read and write computer programs --- many of them. There are strong reasons for learning to program. Although this chapter does not teach programming itself --- see resources such as @wickham_advanced_2019, @gillespie_efficient_2016, and @xiao_gis_2016 which teach programming in R and other languages --- it does provide some starting points, focused on geometry data, that could form a good foundation for developing programming skills. The chapter also demonstrates and highlights the importance of reproducibility\index{reproducibility}. The advantages of reproducibility go beyond allowing others to replicate your work: reproducible code is often better in every way than code written to be run only once, including in terms of computational efficiency, 'scalability' (the capability of code to run on large datasets) and ease of adapting and maintaining it. Scripts are the basis of reproducible R code, a topic covered in Section \@ref(scripts). Algorithms are recipes for modifying inputs using a series of steps, resulting in an output, as described in Section \@ref(geometric-algorithms). To ease sharing and reproducibility, algorithms can be placed into functions. That is the topic of Section \@ref(functions). The example of finding the centroid\index{centroid} of a polygon will be used to tie these concepts together. Chapter \@ref(geometry-operations) already introduced a centroid\index{centroid} function `st_centroid()`, but this example highlights how seemingly simple operations are the result of comparatively complex code, affirming the following observation [@wise_gis_2001]: > One of the most intriguing things about spatial data problems is that things which appear to be trivially easy to a human being can be surprisingly difficult on a computer. The example also reflects a secondary aim of the chapter which, following @xiao_gis_2016, is "not to duplicate what is available out there, but to show how things out there work". ## Scripts If functions distributed in packages are the building blocks of R code, scripts are the glue that holds them together. Scripts should be stored and executed in a logical order to create reproducible workflows\index{reproducibility}, manually or with workflow automation tools such as **targets** [@landau_targets_2021]. If you are new to programming, scripts may seem intimidating when you first encounter them, but they are simply plain text files. Scripts are usually saved as a file with an extension representing the language they contain, such as `.py` for scripts written in Python or `.rs` for scripts written in Rust. R scripts should be saved with a `.R` extension and named to reflect what they do. An example is [`11-hello.R`](https://github.com/geocompx/geocompr/blob/main/code/11-hello.R), a script file stored in the [`code`](https://github.com/geocompx/geocompr/tree/main/code/) folder of the book's repository. `11-hello.R` is a simple script containing only two lines of code, one of which is a comment: ```r # Aim: provide a minimal R script print("Hello geocompr") ``` The contents of this script are not particularly exciting, but they demonstrate the point: scripts do not need to be complicated. Saved scripts can be called and executed in their entirety from the R command line with the `source()` function, as demonstrated below. The output of this command shows that the comment is ignored but `print()` command is executed: ```{r 10-algorithms-1} source("code/11-hello.R") ``` You can also call R scripts from system shells such as `bash` and `PowerShell` as follows: ```bash Rscript code/11-hello.R ``` If your `RScript` executable is [configured](https://www.reddit.com/r/Rlanguage/comments/zaovly/is_anybody_able_to_run_a_r_script_in_powershell/) so it is available, the above command will print `Hello geocompr` in the system shell. There are no strict rules on what can and cannot go into script files and nothing to prevent you from saving broken, non-reproducible code, so testing is important. Lines of code that do not contain valid R should be commented out, by adding a `#` to the start of the line, to prevent errors, as shown in line 1 of the `11-hello.R` script. It is worth following some basic rules: - Write the script in order: just like the script of a film, scripts should have a clear order such as 'setup', 'data processing' and 'save results' (roughly equivalent to 'beginning', 'middle' and 'end' in a film). - Add comments to the script so other people (and your future self) can understand it: at a minimum, a comment should state the purpose of the script (see Figure \@ref(fig:codecheck)) and (for long scripts) divide it into sections. This can be done in RStudio\index{RStudio}, for example, with the shortcut `Ctrl+Shift+R`, which creates 'foldable' code section headings - Above all, scripts should be reproducible: self-contained scripts that will work on any computer are more useful than scripts that only run on your computer, on a good day. This involves attaching required packages at the beginning, reading-in data from persistent sources (such as a reliable website) and ensuring that previous steps have been taken.^[ Prior steps can be referred to with a comment or with an if statement such as `if (!exists("x")) source("x.R")` (which would run the script file `x.R` if the object `x` is missing). ] Unless you organise your code into a package, it is hard to enforce reproducibility in R scripts, but there are tools that can help. By default, RStudio \index{RStudio} 'code checks' R scripts and underlines faulty code with a red wavy line, as shown in Figure \@ref(fig:codecheck). The **reprex** package is another tool that can help with reproducibility. ```{r codecheck, echo=FALSE, fig.cap="Code checking in RStudio. This example, from the script 11-centroid-alg.R, highlights an unclosed curly bracket on line 19.", fig.scap="Illustration of 'code checking' in RStudio."} knitr::include_graphics("images/codecheck.png") ``` ```{block2 spellcheck, type='rmdnote'} A useful tool for reproducibility is the **reprex** package. Its main function `reprex()` tests lines of R code to check if they are reproducible, and it generates markdown output to facilitate communication on sites such as GitHub. See the webpage reprex.tidyverse.org for details. ``` \index{reproducibility} The contents of this section apply to any type of R script. A particular consideration with scripts for geocomputation is that they tend to have external dependencies, such as the GDAL dependency needed for core R packages for working with geographic data, which we made heavy use of in Chapter \@ref(read-write) for data import and export. GIS software dependencies may be needed to run more specialist geoalgorithms, as outlined in Chapter \@ref(gis). Scripts for working with geographic data also often require input datasets to be available in specific formats. Such dependencies should be mentioned as comments or suitable place in the project of which it is a part, or described as dependencies with tools such as the **renv** package or Docker. 'Defensive' programming techniques and good error messages can save time by checking for dependencies and communicating with users if certain requirements are not met. If statements, implemented with `if ()` in R, can be used to send messages or run lines of code if, and only if, certain conditions are met. The following lines of code, for example, send a message to users if a certain file is missing: ```{r} if (!file.exists("required_geo_data.gpkg")) { message("No file, required_geo_data.gpkg is missing!") } ``` The work undertaken by the `11-centroid-alg.R` script is demonstrated in the reproducible example below, which creates a prerequisite object named `poly_mat`, representing a square with sides 9 units in length. This example shows that `source()` works with URLs, assuming you have an internet connection. If you do not, the same script can be called with `source("code/11-centroid-alg.R")`, assuming that you have previously downloaded the [github.com/geocompx/geocompr](https://github.com/geocompx/geocompr) repository and that you are running R from the `geocompr` folder. ```{r 10-algorithms-2, eval=FALSE} poly_mat = cbind( x = c(0, 9, 9, 0, 0), y = c(0, 0, 9, 9, 0) ) # Short URL to code/11-centroid-alg.R in the geocompr repo source("https://t.ly/0nzj") ``` ```{r 10-algorithms-3, echo=FALSE, fig.show='hide'} poly_mat = cbind( x = c(0, 9, 9, 0, 0), y = c(0, 0, 9, 9, 0) ) if (curl::has_internet()) { source("https://raw.githubusercontent.com/geocompx/geocompr/main/code/11-centroid-alg.R") } else { source("code/11-centroid-setup.R") } ``` ## Geometric algorithms Algorithms\index{algorithm} can be understood as the computing equivalent of a baking recipe. They are a complete set of instructions which, when undertaken on the inputs result in useful/tasty outcomes. Inputs are ingredients such as flour and sugar in the case of baking, data and input parameters in the case of algorithms. And while tasty cakes may result from a baking recipe, successful algorithms should have computational outcomes with environmental/social/other benefits. Before diving into a reproducible example, the brief history below shows how algorithms relate to scripts (covered in Section \@ref(scripts)) and functions (which can be used to generalize algorithms and make them more portable and easy-to-use, as we'll see in Section \@ref(functions)). The word "algorithm"\index{algorithm} originated with the publication of an early math textbook in Baghdad in the year 825. The book was translated into Latin and became so popular that the author's last name, [al-Khwārizmī](https://en.wikipedia.org/wiki/Muhammad_ibn_Musa_al-Khwarizmi), "was immortalized as a scientific term: Al-Khwarizmi became Alchoarismi, Algorismi and, eventually, algorithm" [@bellos_alex_2011]. In the computing age, algorithm\index{algorithm} refers to a series of steps that solves a problem, resulting in a predefined output. Inputs must be formally defined in a suitable data structure [@wise_gis_2001]. Algorithms often start as flow charts or pseudocode\index{pseudocode} showing the aim of the process before being implemented in code. To ease usability, common algorithms are often packaged inside functions, which may hide some or all of the steps taken (unless you look at the function's source code, see Section \@ref(functions)). Geoalgorithms\index{geoalgorithm}, such as those we encountered in Chapter \@ref(gis), are algorithms that take geographic data in and, generally, return geographic results (alternative terms for the same thing include *GIS algorithms* and *geometric algorithms*). That may sound simple, but it is a deep subject with an entire academic field, *Computational Geometry*, dedicated to their study [@berg_computational_2008] and numerous books on the subject. @orourke_computational_1998, for example, introduces the subject with a range of progressively harder geometric algorithms using reproducible and freely available C code. An example of a geometric algorithm is one that finds the centroid\index{centroid} of a polygon. There are many approaches to centroid\index{centroid} calculation, some of which work only on specific types of [spatial data](https://en.wikipedia.org/wiki/Centroid). For the purposes of this section, we choose an approach that is easy to visualize: breaking the polygon into many triangles and finding the centroid\index{centroid} of each of these, an approach discussed by @kaiser_algorithms_1993 alongside other centroid algorithms [and mentioned briefly in @orourke_computational_1998]. It helps to further break down this approach into discrete tasks before writing any code (subsequently referred to as step 1 to step 4, these could also be presented as a schematic diagram or pseudocode\index{pseudocode}): 1. Divide the polygon into contiguous triangles 2. Find the centroid\index{centroid} of each triangle 3. Find the area of each triangle 4. Find the area-weighted mean of triangle centroids\index{centroid} These steps may sound straightforward, but converting words into working code requires some work and plenty of trial-and-error, even when the inputs are constrained: The algorithm will only work for *convex polygons*, which contain no internal angles greater than 180°, no star shapes allowed (packages **decido** and **sfdct** can triangulate non-convex polygons using external libraries, as shown in the [algorithm](https://geocompx.github.io/geocompkg/articles/algorithm.html) vignette hosted at [geocompx.org](https://geocompx.org/)). The simplest data structure representing a polygon is a matrix of x and y coordinates in which each row represents a vertex tracing the polygon's border in order where the first and last rows are identical [@wise_gis_2001]. In this case, we will create a polygon with five vertices in base R, building on an example from *GIS Algorithms* [@xiao_gis_2016 see [github.com/gisalgs](https://github.com/gisalgs/geom) for Python code], as illustrated in Figure \@ref(fig:polymat): ```{r centroid-setup, echo=FALSE, eval=FALSE} # show where the data came from: source("code/11-centroid-setup.R") ``` ```{r 10-algorithms-4} # generate a simple matrix representation of a polygon: x_coords = c(10, 20, 12, 0, 0, 10) y_coords = c(0, 15, 20, 10, 0, 0) poly_mat = cbind(x_coords, y_coords) ``` Now that we have an example dataset, we are ready to undertake step 1 outlined above. The code below shows how this can be done by creating a single triangle (`T1`), that demonstrates the method; it also demonstrates step 2 by calculating its centroid\index{centroid} based on the [formula](https://math.stackexchange.com/a/1702606) $1/3(a + b + c)$ where $a$ to $c$ are coordinates representing the triangle's vertices: ```{r 10-algorithms-5} # create a point representing the origin: Origin = poly_mat[1, ] # create 'triangle matrix': T1 = rbind(Origin, poly_mat[2:3, ], Origin) C1 = (T1[1,] + T1[2,] + T1[3,]) / 3 ``` ```{r, echo=FALSE} # (Note: drop = FALSE preserves classes, resulting in a matrix): C1_alternative = (T1[1, , drop = FALSE] + T1[2, , drop = FALSE] + T1[3, , drop = FALSE]) / 3 ``` ```{r polymat, echo=FALSE, fig.cap="Polygon centroid calculation problem.", fig.height="100", warning=FALSE} # initial plot: can probably delete this: old_par = par(pty = "s") plot(poly_mat, cex = 3) lines(poly_mat, lwd = 7) lines(T1, col = "#fa8072", lwd = 2) text(x = C1[1], y = C1[2], "C1", col = "#fa8072") par(old_par) ``` Step 3 is to find the area of each triangle, so a *weighted mean* accounting for the disproportionate impact of large triangles is accounted for. The formula to calculate the area of a triangle is as follows [@kaiser_algorithms_1993]: $$ \frac{A_x ( B_y − C_y ) + B_x ( C_y − A_y ) + C_x ( A_y − B_y )}{ 2 } $$ Where $A$ to $C$ are the triangle's three points and $x$ and $y$ refer to the x and y dimensions. A translation of this formula into R code that works with the data in the matrix representation of a triangle `T1` is as follows (the function `abs()` ensures a positive result): ```{r 10-algorithms-6} # calculate the area of the triangle represented by matrix T1: abs(T1[1, 1] * (T1[2, 2] - T1[3, 2]) + T1[2, 1] * (T1[3, 2] - T1[1, 2]) + T1[3, 1] * (T1[1, 2] - T1[2, 2])) / 2 ``` This code chunk outputs the correct result.^[ The result can be verified with the following formula (which assumes a horizontal base): area is half of the base width times height, $A = B * H / 2$. In this case $10 * 10 / 2 = 50$. ] The problem is that code is clunky and must by retyped if we want to run it on another triangle matrix. To make the code more generalizable, we will see how it can be converted into a function in Section \@ref(functions). Step 4 requires steps 2 and 3 to be undertaken not just on one triangle (as demonstrated above) but on all triangles. This requires *iteration* to create all triangles representing the polygon, as illustrated in Figure \@ref(fig:polycent). `lapply()`\index{loop!lapply} and `vapply()`\index{loop!vapply} are used to iterate over each triangle here because they provide a concise solution in base R:^[ See `?lapply` for documentation and Chapter \@ref(location) for more on iteration. ] ```{r 10-algorithms-7} i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) { rbind(Origin, poly_mat[x:(x + 1), ], Origin) }) C_list = lapply(T_all, function(x) (x[1, ] + x[2, ] + x[3, ]) / 3) C = do.call(rbind, C_list) A = vapply(T_all, function(x) { abs(x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 }, FUN.VALUE = double(1)) ``` ```{r polycent, fig.cap="Iterative centroid algorithm with triangles. The X represents the area-weighted centroid in iterations 2 and 3.", fig.scap="Illustration of iterative centroid algorithm with triangles.", echo=FALSE, fig.asp=0.3} # idea: show animated version on web version source("code/11-polycent.R") ``` We are now in a position to complete step 4 to calculate the total area with `sum(A)` and the centroid\index{centroid} coordinates of the polygon with `weighted.mean(C[, 1], A)` and `weighted.mean(C[, 2], A)` (exercise for alert readers: verify these commands work). To demonstrate the link between algorithms\index{algorithm} and scripts, the contents of this section have been condensed into `11-centroid-alg.R`. We saw at the end of Section \@ref(scripts) how this script can calculate the centroid\index{centroid} of a square. The great thing about *scripting* the algorithm is that it works on the new `poly_mat` object (see exercises below to verify these results with reference to `st_centroid()`): ```{r 10-algorithms-8} source("code/11-centroid-alg.R") ``` The example above shows that low-level geographic operations *can* be developed from first principles with base R. It also shows that if a tried-and-tested solution already exists, it may not be worth reinventing the wheel: if we aimed only to find the centroid\index{centroid} of a polygon, it would have been quicker to represent `poly_mat` as an **sf** object and use the preexisting `sf::st_centroid()` function instead. However, the great benefit of writing algorithms from first principles is that you will understand every step of the process, something that cannot be guaranteed when using other peoples' code. A further consideration is performance: R may be slow compared with low-level languages such as C++\index{C++} for number crunching (see Section \@ref(software-for-geocomputation)) and optimization is difficult. If the aim is to develop new methods, computational efficiency should not be prioritized. This is captured in the saying "premature optimization is the root of all evil (or at least most of it) in programming" [@knuth_computer_1974]. Algorithm\index{algorithm} development is hard. This should be apparent from the amount of work that has gone into developing a centroid\index{centroid} algorithm\index{algorithm} in base R\index{R} that is just one, rather inefficient, approach to the problem with limited real-world applications (convex polygons are uncommon in practice). The experience should lead to an appreciation of low-level geographic libraries such as GEOS\index{GEOS} and CGAL\index{CGAL} (Computational Geometry Algorithms Library) which not only run fast but work on a wide range of input geometry types. A great advantage of the open source nature of such libraries is that their source code\index{source code} is readily available for study, comprehension and (for those with the skills and confidence) modification.^[ The CGAL\index{CGAL} function `CGAL::centroid()` is in fact composed of seven sub-functions as described at https://doc.cgal.org/latest/Kernel_23/group__centroid__grp.html allowing it to work on a wide range of input data types, whereas the solution we created works only on a very specific input data type. The source code underlying GEOS\index{GEOS} function `Centroid::getCentroid()` can be found at https://github.com/libgeos/geos/search?q=getCentroid. ] ## Functions Like algorithms\index{algorithm}, functions take an input and return an output. Functions\index{function}, however, refer to the implementation in a particular programming language, rather than the 'recipe' itself. In R, functions\index{function} are objects in their own right, that can be created and joined together in a modular fashion. We can, for example, create a function that undertakes step 2 of our centroid\index{centroid} generation algorithm\index{algorithm} as follows: ```{r 10-algorithms-9} t_centroid = function(x) { (x[1, ] + x[2, ] + x[3, ]) / 3 } ``` The above example demonstrates two key components of [functions](https://adv-r.hadley.nz/functions.html): (1) the function *body*, the code inside the curly brackets that define what the function does with the inputs; and (2) the *arguments*, the list of arguments the function works with --- `x` in this case (the third key component, the environment, is beyond the scope of this section). By default, functions return the last object that has been calculated (the coordinates of the centroid\index{centroid} in the case of `t_centroid()`).^[ You can also explicitly set the output of a function by adding `return(output)` into the body of the function, where `output` is the result to be returned. ] ```{r 10-algorithms-10, eval=FALSE, echo=FALSE} body(t_centroid) formals(t_centroid) environment(t_centroid) ``` The function now works on any inputs you pass it, as illustrated in the below command which calculates the area of the first triangle from the example polygon in the previous section (see Figure \@ref(fig:polycent)). ```{r 10-algorithms-11} t_centroid(T1) ``` We can also create a function\index{function} to calculate a triangle's area, which we will name `t_area()`: ```{r 10-algorithms-12} t_area = function(x) { abs( x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 } ``` Note that after the function's creation, a triangle's area can be calculated in a single line of code, avoiding duplication of verbose code: functions are a mechanism for *generalizing* code. The newly created function\index{function} `t_area()` takes any object `x`, assumed to have the same dimensions as the 'triangle matrix' data structure we've been using, and returns its area, as illustrated on `T1` as follows: ```{r 10-algorithms-13} t_area(T1) ``` We can test the generalizability of the function\index{function} by using it to find the area of a new triangle matrix, which has a height of 1 and a base of 3: ```{r 10-algorithms-14} t_new = cbind(x = c(0, 3, 3, 0), y = c(0, 0, 1, 0)) t_area(t_new) ``` A useful feature of functions is that they are modular. Provided that you know what the output will be, one function can be used as the building block of another. Thus, the functions `t_centroid()` and `t_area()` can be used as sub-components of a larger function\index{function} to do the work of the script `11-centroid-alg.R`: calculate the area of any convex polygon. The code chunk below creates the function `poly_centroid()` to mimic the behavior of `sf::st_centroid()` for convex polygons.^[ Note that the functions we created are called iteratively in `lapply()`\index{loop!lapply} and `vapply()`\index{loop!vapply} function calls. ] ```{r 10-algorithms-15} poly_centroid = function(poly_mat) { Origin = poly_mat[1, ] # create a point representing the origin i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) {rbind(Origin, poly_mat[x:(x + 1), ], Origin)}) C_list = lapply(T_all, t_centroid) C = do.call(rbind, C_list) A = vapply(T_all, t_area, FUN.VALUE = double(1)) c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) } ``` ```{r 10-algorithms-16, echo=FALSE, eval=FALSE} # a slightly more complex version of the function with output set poly_centroid = function(poly_mat, output = "matrix") { Origin = poly_mat[1, ] # create a point representing the origin i = 2:(nrow(poly_mat) - 2) T_all = T_all = lapply(i, function(x) { rbind(Origin, poly_mat[x:(x + 1), ], Origin) }) C_list = lapply(T_all, t_centroid) C = do.call(rbind, C_list) A = vapply(T_all, t_area, FUN.VALUE = double(1)) centroid_coords = c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) if (output == "matrix") { return(centroid_coords) } else if (output == "area") return(sum(A)) } ``` ```{r 10-algorithms-17} poly_centroid(poly_mat) ``` Functions\index{function}, such as `poly_centroid()`, can further be extended to provide different types of output. To return the result as an object of class `sfg`, for example, a 'wrapper' function can be used to modify the output of `poly_centroid()` before returning the result: ```{r 10-algorithms-18} poly_centroid_sfg = function(x) { centroid_coords = poly_centroid(x) sf::st_point(centroid_coords) } ``` We can verify that the output is the same as the output from `sf::st_centroid()` as follows: ```{r 10-algorithms-19} poly_sfc = sf::st_polygon(list(poly_mat)) identical(poly_centroid_sfg(poly_mat), sf::st_centroid(poly_sfc)) ``` ## Programming In this chapter we have moved quickly, from scripts to functions via the tricky topic of algorithms\index{algorithm}. Not only have we discussed them in the abstract, but we have also created working examples of each to solve a specific problem: - The script `11-centroid-alg.R` was introduced and demonstrated on a 'polygon matrix' - The individual steps that allowed this script to work were described as an algorithm\index{algorithm}, a computational recipe - To generalize the algorithm, we converted it into modular functions which were eventually combined to create the function `poly_centroid()` in the previous section Each of these may seem straightforward. However, skillful programming is complex and involves *combining* each element --- scripts, algorithms and functions --- into a *system*, with efficiency and style. The outcome should be robust and user-friendly tools that other people can use. If you are new to programming, as we expect most people reading this book will be, being able to follow and reproduce the results in the preceding sections is a major achievement. Programming takes many hours of dedicated study and practice before you become proficient. The challenge facing developers aiming to implement new algorithms\index{algorithm} in an efficient way is put in perspective by considering the amount of work that has gone into creating a simple function that is not intended for use in production: in its current state, `poly_centroid()` fails on most (non-convex) polygons! This raises the question: how to generalize the function? Two options are (1) to find ways to triangulate non-convex polygons (a topic covered in the online [Algorithms Extended](https://geocompx.github.io/geocompkg/articles/algorithm.html) article hosted at geocompx.github.io/geocompkg/articles/) and (2) to explore other centroid algorithms that do not rely on triangular meshes. A wider question is: is it worth programming a solution at all when high performance algorithms have already been implemented and packaged in functions such as `st_centroid()`? The reductionist answer in this specific case is 'no'. In the wider context, and considering the benefits of learning to program, the answer is 'it depends'. With programming, it's easy to waste hours trying to implement a method, only to find that someone has already done the hard work. You can understand this chapter as a stepping stone towards geometric algorithm programming wizardry. However, it can also be seen as a lesson in when to try to program a generalized solution, and when to use existing higher-level solutions. There will surely be occasions when writing new functions is the best way forward, but there will also be times when using functions that already exist is the best way forward. "Do not reinvent the wheel" applies as much, if not more, to programming than to other walks of life. A bit of research and thinking at the outset of a project can help decide where programming time is best spent. Three principles can also help maximize use of your effort when writing code, whether it's a simple script or package that is composed of hundreds of functions: 1. [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) (don't repeat yourself): minimize repetition of code and aim to use fewer lines of code to solve a particular problem. This principle is explained with reference to the use of functions to reduce code repetition in the Functions chapter of R for Data Science [@grolemund_r_2016]. 2. [KISS](https://en.wikipedia.org/wiki/KISS_principle) (keep it simple stupid): this principle suggests that simple solutions should be tried first and preferred over complex solutions, using dependencies where needed, and aiming to keep scripts concise. This principle is the computing analogy of the [quote](https://www.nature.com/articles/d41586-018-05004-4) "things should be made as simple as possible, but no simpler". 3. Modularity: your code will be easier to maintain if it's divided into well-defined pieces. A function should do only one thing, but do this really well. If you function is becoming too long, think about splitting it into multiple small functions, each of which could be reused for other purposes, supporting DRY and KISS principles. We cannot guarantee that this chapter will instantly enable you to create perfectly formed functions for your work. We are, however, confident that its contents will help you decide when is an appropriate time to try (when no other existing functions solve the problem, when the programming task is within your capabilities and when the benefits of the solution are likely to outweigh the time costs of developing it). By using the principles above, in combination with the practical experience of working through the examples above, you will build your scripting, package-writing and programming skills. First steps towards programming can be slow (the exercises below should not be rushed), but the long-term rewards can be large. ## Exercises {#ex-algorithms} ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_11-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 12-spatial-cv.Rmd ================================================ # Statistical learning {#spatial-cv} ```{r, include=FALSE} source("code/before_script.R") ``` ```{r 12-knitr-settings, include=FALSE} knitr::opts_chunk$set(cache = FALSE) ``` ## Prerequisites {-} This chapter assumes proficiency with geographic data analysis\index{geographic data analysis}, for example gained by studying the contents and working through the exercises in Chapters \@ref(spatial-class) to \@ref(reproj-geo-data). A familiarity with Generalized Linear Models (GLM)\index{GLM} and machine learning\index{machine learning} is highly recommended [for example @zuur_mixed_2009 and @james_introduction_2013]. The chapter uses the following packages:^[ Packages **GGally**, **lgr**, **kernlab**, **mlr3measures**, **paradox**, **pROC**, **progressr** and **spDataLarge** must also be installed, although these do not need to be attached. ] ```{r 12-spatial-cv-1, message=FALSE} library(sf) library(terra) library(dplyr) library(future) # parallel processing library(lgr) # logging framework for R library(mlr3) # unified interface to machine learning algorithms library(mlr3learners) # most important machine learning algorithms library(mlr3extralearners) # access to even more learning algorithms library(mlr3proba) # here needed for mlr3extralearners::list_learners() library(mlr3spatiotempcv) # spatiotemporal resampling strategies library(mlr3tuning) # hyperparameter tuning library(mlr3viz) # plotting functions for mlr3 objects library(progressr) # report progress updates library(pROC) # compute roc values ``` Required data will be attached in due course. ## Introduction {#intro-cv1} Statistical learning\index{statistical learning} is concerned with the use of statistical and computational models for identifying patterns in data and predicting from these patterns. Due to its origins, statistical learning\index{statistical learning} is one of R's\index{R} great strengths (see Section \@ref(software-for-geocomputation)).^[ Applying statistical techniques to geographic data has been an active topic of research for many decades in the fields of geostatistics, spatial statistics and point pattern analysis [@diggle_modelbased_2007; @gelfand_handbook_2010; @baddeley_spatial_2015]. ] Statistical learning\index{statistical learning} combines methods from statistics\index{statistics} and machine learning\index{machine learning} and can be categorized into supervised and unsupervised techniques. Both are increasingly used in disciplines ranging from physics, biology and ecology to geography and economics [@james_introduction_2013]. This chapter focuses on supervised techniques in which there is a training dataset, as opposed to unsupervised techniques such as clustering\index{clustering}. Response variables can be binary (such as landslide occurrence), categorical (land use), integer (species richness count) or numeric (soil acidity measured in pH). Supervised techniques model the relationship between such responses --- which are known for a sample of observations --- and one or more predictors. The primary aim of much machine learning\index{machine learning} research is to make good predictions. Machine learning thrives in the age of 'big data'\index{big data} because its methods make few assumptions about input variables and can handle huge datasets. Machine learning is conducive to tasks such as the prediction of future customer behavior, recommendation services (music, movies, what to buy next), face recognition, autonomous driving, text classification and predictive maintenance (infrastructure, industry). This chapter is based on a case study: modeling the occurrence of landslides. This application links to the applied nature of geocomputation, defined in Chapter \@ref(intro), and illustrates how machine learning\index{machine learning} borrows from the field of statistics\index{statistics} when the sole aim is prediction. Therefore, this chapter first introduces modeling and cross-validation\index{cross-validation} concepts with the help of a GLM \index{GLM} [@zuur_mixed_2009]. Building on this, the chapter implements a more typical machine learning\index{machine learning} algorithm\index{algorithm}, namely a Support Vector Machine (SVM)\index{SVM}. The models' **predictive performance** will be assessed using spatial cross-validation (CV)\index{cross-validation!spatial CV}, which accounts for the fact that geographic data is special. CV\index{cross-validation} determines a model's ability to generalize to new data, by splitting a dataset (repeatedly) into training and test sets. It uses the training data to fit the model and checks its performance when predicting against the test data. CV helps to detect overfitting\index{overfitting}, since models that predict the training data too closely (noise) will tend to perform poorly on the test data. Randomly splitting spatial data can lead to training points that are neighbors in space with test points. Due to spatial autocorrelation\index{autocorrelation!spatial}, test and training datasets would not be independent in this scenario, with the consequence that CV\index{cross-validation} fails to detect a possible overfitting\index{overfitting}. Spatial CV\index{cross-validation!spatial CV} alleviates this problem and is the **central** theme in this chapter. Hence, and to emphasize it again, this chapter is focusing on the **predictive performance** of models. It **does not** teach how to do predictive mapping. This will be the topic of Chapter \@ref(eco). ## Case study: Landslide susceptibility {#case-landslide} This case study is based on a dataset of landslide locations in Southern Ecuador, illustrated in Figure \@ref(fig:lsl-map) and described in detail in @muenchow_geomorphic_2012. A subset of the dataset used in that paper is provided in the **spDataLarge**\index{spDataLarge (package)} package, which can be loaded as follows: ```{r 12-spatial-cv-2} data("lsl", "study_mask", package = "spDataLarge") ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) ``` The above code loads three objects: a `data.frame` named `lsl`, an `sf` object named `study_mask` and a `SpatRaster` (see Section \@ref(raster-classes)) named `ta` containing terrain attribute rasters. `lsl` contains a factor column `lslpts` where `TRUE` corresponds to an observed landslide 'initiation point', with the coordinates stored in columns `x` and `y`.^[ The landslide initiation point is located in the scarp of a landslide polygon. See @muenchow_geomorphic_2012 for further details. ] There are 175 landslide and 175 non-landslide points, as shown by `summary(lsl$lslpts)`. The 175 non-landslide points were sampled randomly from the study area, with the restriction that they must fall outside a small buffer around the landslide polygons. ```{r lsl-map, echo=FALSE, out.width="70%", fig.cap="Landslide initiation points (red) and points unaffected by landsliding (blue) in Southern Ecuador.", fig.scap="Landslide initiation points."} # library(tmap) # data("lsl", package = "spDataLarge") # ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = "EPSG:32717") # hs = terra::shade(slope = ta$slope * pi / 180, # terra::terrain(ta$elev, v = "aspect", unit = "radians")) # # so far tmaptools does not support terra objects # bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.0001, 1), # ylim = c(-0.0001, 1), relative = TRUE) # map = tm_shape(hs, bbox = bbx) + # tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, # labels.rot = c(0, 90), lines = FALSE) + # tm_raster(col.scale = tm_scale(values = gray(0:100 / 100), n = 100), col.legend = tm_legend_hide()) + # tm_shape(ta$elev) + # tm_raster(col_alpha = 0.6, col.scale = tm_scale(values = hcl.colors(25, "Geyser")), col.legend = tm_legend_hide()) + # tm_shape(lsl_sf) + # tm_symbols(fill = "lslpts", size = 0.5, col = "white", # fill.scale = tm_scale(values = c("#0071A6", "#C73000")), fill.legend = tm_legend(title = "Landslide: ")) + # tm_layout(inner.margins = rep(0, 4), legend.bg.color = "white", legend.position = tm_pos_in()) # tmap::tmap_save(map, filename = "images/lsl-map-1.png", width = 11, # height = 11, units = "cm") knitr::include_graphics("images/lsl-map-1.png") ``` \index{hillshade} The first three rows of `lsl`, rounded to two significant digits, can be found in Table \@ref(tab:lslsummary). ```{r lslsummary, echo=FALSE, warning=FALSE} lsl_table = lsl |> mutate(across(.cols = -any_of(c("x", "y", "lslpts")), ~signif(., 2))) knitr::kable(lsl_table[c(1, 2, 350), ], caption = "Structure of the lsl dataset.", caption.short = "`lsl` dataset.", booktabs = TRUE) |> kableExtra::kable_styling(latex_options = "scale_down") ``` To model landslide susceptibility, we need some predictors. Since terrain attributes are frequently associated with landsliding [@muenchow_geomorphic_2012], we have already extracted following terrain attributes from `ta` to `lsl`: - `slope`: slope angle (°) - `cplan`: plan curvature (rad m^−1^) expressing the convergence or divergence of a slope and thus water flow - `cprof`: profile curvature (rad m^-1^) as a measure of flow acceleration, also known as downslope change in slope angle - `elev`: elevation (m a.s.l.) as the representation of different altitudinal zones of vegetation and precipitation in the study area - `log10_carea`: the decadic logarithm of the catchment area (log10 m^2^) representing the amount of water flowing toward a location It might be a worthwhile exercise to compute the terrain attributes with the help of R-GIS bridges (see Chapter \@ref(gis)) and extract them to the landslide points (see Exercise section at the end of this chapter). ## Conventional modeling approach in R {#conventional-model} Before introducing the **mlr3**\index{mlr3 (package)} package, an umbrella package providing a unified interface to dozens of learning algorithms (Section \@ref(spatial-cv-with-mlr3)), it is worth taking a look at the conventional modeling interface in R\index{R}. This introduction to supervised statistical learning\index{statistical learning} provides the basis for doing spatial CV\index{cross-validation!spatial CV}, and contributes to a better grasp on the **mlr3**\index{mlr3 (package)} approach presented subsequently. Supervised learning involves predicting a response variable as a function of predictors (Section \@ref(intro-cv)). In R\index{R}, modeling functions are usually specified using formulas (see `?formula` for more details on R formulas). The following command specifies and runs a generalized linear model\index{GLM}: ```{r 12-spatial-cv-6} fit = glm(lslpts ~ slope + cplan + cprof + elev + log10_carea, family = binomial(), data = lsl) ``` It is worth understanding each of the three input arguments: - A formula, which specifies landslide occurrence (`lslpts`) as a function of the predictors - A family, which specifies the type of model, in this case `binomial` because the response is binary (see `?family`) - The data frame which contains the response and the predictors (as columns) The results of this model can be printed as follows (`summary(fit)` provides a more detailed account of the results): ```{r 12-spatial-cv-7} class(fit) fit ``` The model object `fit`, of class `glm`, contains the coefficients defining the fitted relationship between response and predictors. It can also be used for prediction. This is done with the generic `predict()` method, which in this case calls the function `predict.glm()`. Setting `type` to `response` returns the predicted probabilities (of landslide occurrence) for each observation in `lsl`, as illustrated below (see `?predict.glm`). ```{r 12-spatial-cv-8} pred_glm = predict(object = fit, type = "response") head(pred_glm) ``` Spatial distribution maps can be made by applying the coefficients to the predictor rasters. This can be done manually or with `terra::predict()`. In addition to a model object (`fit`), the latter function also expects a `SpatRaster` with the predictors (raster layers) named as in the model's input data frame (Figure \@ref(fig:lsl-susc)). ```{r 12-spatial-cv-9, eval=FALSE} # making the prediction pred = terra::predict(ta, model = fit, type = "response") ``` ```{r lsl-susc, echo=FALSE, out.width="70%",fig.cap="Spatial distribution mapping of landslide susceptibility using a GLM.", fig.scap = "Spatial distribution of landslide susceptibility.", warning=FALSE} # attach study mask for the natural part of the study area # data("lsl", "study_mask", package = "spDataLarge") # ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # study_mask = terra::vect(study_mask) # lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) # hs = terra::shade(ta$slope * pi / 180, # terra::terrain(ta$elev, v = "aspect", unit = "radians")) # bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.0001, 1), # ylim = c(-0.0001, 1), relative = TRUE) # map2 = tm_shape(hs, bbox = bbx) + # tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, # labels.rot = c(0, 90), lines = FALSE) + # tm_raster(col.scale = tm_scale(values = "white"), col.legend = tm_legend_hide()) + # tm_shape(terra::mask(hs, study_mask), bbox = bbx) + # tm_raster(col.scale = tm_scale(values = gray(0:100 / 100), n = 100), col.legend = tm_legend_hide()) + # tm_shape(terra::mask(pred, study_mask)) + # tm_raster(col_alpha = 0.5, col.scale = tm_scale(values = "Reds", n = 6), # col.legend = tm_legend(title = "Susceptibility")) + # tm_layout(legend.position = c("LEFT", "BOTTOM"), # legend.title.size = 0.8, # inner.margins = rep(0, 4)) # tmap::tmap_save(map2, filename = "images/lsl-susc-1.png", width = 13, # height = 11, units = "cm") knitr::include_graphics("images/lsl-susc-1.png") ``` Here, when making predictions, we neglect spatial autocorrelation\index{autocorrelation!spatial} since we assume that on average the predictive accuracy remains the same with or without spatial autocorrelation structures. However, it is possible to include spatial autocorrelation\index{autocorrelation!spatial} structures into models as well as into predictions. Though, this is beyond the scope of this book, we give the interested reader some pointers where to look it up: 1. The predictions of regression kriging combines the predictions of a regression with the kriging of the regression's residuals [@goovaerts_geostatistics_1997; @hengl_practical_2007; @bivand_applied_2013]. 2. One can also add a spatial correlation (dependency) structure to a generalized least squares model [`nlme::gls()`, @zuur_mixed_2009; @zuur_beginners_2017]. 3. One can also use mixed-effect modeling approaches. Basically, a random effect imposes a dependency structure on the response variable which in turn allows for observations of one class to be more similar to each other than to those of another class [@zuur_mixed_2009]. Classes can be, for example, bee hives, owl nests, vegetation transects or an altitudinal stratification. This mixed modeling approach assumes normal and independent distributed random intercepts. This can even be extended by using a random intercept that is normal and spatially dependent. For this, however, you will have to resort most likely to Bayesian modeling approaches since frequentist software tools are rather limited in this respect especially for more complex models [@blangiardo_spatial_2015; @zuur_beginners_2017]. Spatial distribution mapping is one very important outcome of a model (Figure \@ref(fig:lsl-susc)). Even more important is how good the underlying model is at making them since a prediction map is useless if the model's predictive performance is bad. One of the most popular measures to assess the predictive performance of a binomial model is the Area Under the Receiver Operator Characteristic Curve (AUROC)\index{AUROC}. This is a value between 0.5 and 1.0, with 0.5 indicating a model that is no better than random and 1.0 indicating perfect prediction of the two classes. Thus, the higher the AUROC\index{AUROC}, the better the model's predictive power. The following code chunk computes the AUROC\index{AUROC} value of the model with `roc()`, which takes the response and the predicted values as inputs. `auc()` returns the area under the curve. ```{r 12-spatial-cv-10, message=FALSE, eval=FALSE} pROC::auc(pROC::roc(lsl$lslpts, fitted(fit))) #> Area under the curve: 0.8216 ``` An AUROC\index{AUROC} value of 0.82 represents a good fit. However, this is an overoptimistic estimation since we have computed it on the complete dataset. To derive a biased-reduced assessment, we have to use cross-validation\index{cross-validation} and in the case of spatial data should make use of spatial CV\index{cross-validation!spatial CV}. ## Introduction to (spatial) cross-validation {#intro-cv} Cross-validation\index{cross-validation} belongs to the family of resampling methods\index{resampling} [@james_introduction_2013]. The basic idea is to split (repeatedly) a dataset into training and test sets whereby the training data is used to fit a model which then is applied to the test set. Comparing the predicted values with the known response values from the test set (using a performance measure such as the AUROC\index{AUROC} in the binomial case) gives a bias-reduced assessment of the model's capability to generalize the learned relationship to independent data. For example, a 100-repeated 5-fold cross-validation means to randomly split the data into five partitions (folds) with each fold being used once as a test set (see upper row of Figure \@ref(fig:partitioning)). This guarantees that each observation is used once in one of the test sets, and requires the fitting of five models. Subsequently, this procedure is repeated 100 times. Of course, the data splitting will differ in each repetition. Overall, this sums up to 500 models, whereas the mean performance measure (AUROC\index{AUROC}) of all models is the model's overall predictive power. However, geographic data is special. As we will see in Chapter \@ref(transport), the 'first law' of geography states that points close to each other are, generally, more similar than points further away [@miller_tobler_2004]. This means these points are not statistically independent because training and test points in conventional CV\index{cross-validation} are often too close to each other (see first row of Figure \@ref(fig:partitioning)). 'Training' observations near the 'test' observations can provide a kind of 'sneak preview': information that should be unavailable to the training dataset. To alleviate this problem, 'spatial partitioning' is used to split the observations into spatially disjointed subsets (using the observations' coordinates in a *k*-means clustering\index{clustering!kmeans}; @brenning_spatial_2012; second row of Figure \@ref(fig:partitioning)). This partitioning strategy is the **only** difference between spatial and conventional CV. As a result, spatial CV leads to a bias-reduced assessment of a model's predictive performance, and hence helps to avoid overfitting\index{overfitting}. ```{r partitioning, fig.cap="Spatial visualization of selected test and training observations for cross-validation of one repetition. Random (upper row) and spatial partitioning (lower row).", echo=FALSE, fig.scap="Spatial visualization of selected test and training observations."} knitr::include_graphics("images/12_partitioning.png") ``` ## Spatial CV with **mlr3** \index{mlr3 (package)} There are dozens of packages for statistical learning\index{statistical learning}, as described for example in the [CRAN machine learning task view](https://CRAN.R-project.org/view=MachineLearning). Getting acquainted with each of these packages, including how to undertake cross-validation and hyperparameter\index{hyperparameter} tuning, can be a time-consuming process. Comparing model results from different packages can be even more laborious. The **mlr3** package and ecosystem was developed to address these issues. It acts as a 'meta-package', providing a unified interface to popular supervised and unsupervised statistical learning techniques including classification, regression\index{regression}, survival analysis and clustering\index{clustering} [@lang_mlr3_2019; @bischl_applied_2024]. The standardized **mlr3** interface is based on eight 'building blocks'. As illustrated in Figure \@ref(fig:building-blocks), these have a clear order. (ref:building-blocks) Basic building blocks of the mlr3 package [@bischl_applied_2024]. Permission to reuse this figure was kindly granted. ```{r building-blocks, echo=FALSE, fig.height=4, fig.width=4, fig.cap="(ref:building-blocks)", fig.scap="Basic building blocks of the mlr3 package."} knitr::include_graphics("images/12_ml_abstraction_crop.png") ``` The **mlr3** modeling process consists of three main stages. First, a **task** specifies the data (including response and predictor variables) and the model type (such as regression\index{regression} or classification\index{classification}). Second, a **learner** defines the specific learning algorithm that is applied to the created task. Third, the **resampling** approach assesses the predictive performance of the model, i.e., its ability to generalize to new data (see also Section \@ref(intro-cv)). ### Generalized linear model {#glm} To use a GLM\index{GLM} in **mlr3**\index{mlr3 (package)}, we must create a **task** containing the landslide data. Since the response is binary (two-category variable) and has a spatial dimension, we create a classification\index{classification} task with `as_task_classif_st()` of the **mlr3spatiotempcv** package [@schratz_mlr3spatiotempcv_2021, for non-spatial tasks, use `mlr3::as_task_classif()` or `mlr3::as_task_regr()` for regression\index{regression} tasks, see `?Task` for other task types].^[The **mlr3** ecosystem makes use of **data.table** and **R6** classes. And though you might use **mlr3** without knowing the specifics of **data.table** or **R6**, it might be rather helpful. To learn more about **data.table**, please refer to https://rdatatable.gitlab.io/data.table/. To learn more about **R6**, we recommend [Chapter 14](https://adv-r.hadley.nz/fp.html) of the *Advanced R* book [@wickham_advanced_2019].] The first essential argument of these `as_task_` functions is `x`. `x` expects that the input data includes the response and predictor variables. The `target` argument indicates the name of a response variable (in our case this is `lslpts`) and `positive` determines which of the two factor levels of the response variable indicate the landslide initiation point (in our case this is `TRUE`). All other variables of the `lsl` dataset will serve as predictors. For spatial CV, we need to provide a few extra arguments. The `coordinate_names` argument expects the names of the coordinate columns (see Section \@ref(intro-cv) and Figure \@ref(fig:partitioning)). Additionally, we should indicate the used CRS (`crs`) and decide if we want to use the coordinates as predictors in the modeling (`coords_as_features`). ```{r 12-spatial-cv-11, eval=TRUE} # 1. create task task = mlr3spatiotempcv::as_task_classif_st( mlr3::as_data_backend(lsl), target = "lslpts", id = "ecuador_lsl", positive = "TRUE", coordinate_names = c("x", "y"), crs = "EPSG:32717", coords_as_features = FALSE ) ``` Note that `mlr3spatiotempcv::as_task_classif_st()` also accepts an `sf`-object as input for the `backend` parameter. In this case, you might only want to additionally specify the `coords_as_features` argument. We did not convert `lsl` into an `sf`-object because `as_task_classif_st()` would just turn it back into a non-spatial `data.table` object in the background. For a short data exploration, the `autoplot()` function of the **mlr3viz** package might come in handy since it plots the response against all predictors and all predictors against all predictors (not shown). ```{r autoplot, eval=FALSE} # plot response against each predictor mlr3viz::autoplot(task, type = "duo") # plot all variables against each other mlr3viz::autoplot(task, type = "pairs") ``` Having created a task, we need to choose a **learner** that determines the statistical learning\index{statistical learning} method to use. All classification\index{classification} **learners** start with `classif.` and all regression\index{regression} learners with `regr.` (see `?Learner` for details). `mlr3extralearners::list_mlr3learners()`, which requires **mlr3proba** to be installed, lists all available learners and from which package **mlr3** imports them (Table \@ref(tab:lrns)). To find out about learners that are able to model a binary response variable, we can run: ```{r 12-spatial-cv-12, eval=FALSE} install.packages("mlr3proba", repos = "https://mlr-org.r-universe.dev") mlr3extralearners::list_mlr3learners( filter = list(class = "classif", properties = "twoclass"), select = c("id", "mlr3_package", "required_packages") ) |> head() ``` ```{r lrns, echo=FALSE} # lrns_df = mlr3extralearners::list_mlr3learners( # filter = list(class = "classif", properties = "twoclass"), # select = c("id", "mlr3_package", "required_packages")) |> # head() # dput(lrns_df) lrns_df = structure(list(Class = c("classif.adaboostm1", "classif.binomial", "classif.featureless", "classif.fnn", "classif.gausspr", "classif.IBk" ), Name = c("ada Boosting M1", "Binomial Regression", "Featureless classifier", "Fast k-Nearest Neighbour", "Gaussian Processes", "k-Nearest Neighbours" ), `Short name` = c("adaboostm1", "binomial", "featureless", "fnn", "gausspr", "ibk"), Package = c("RWeka", "stats", "mlr", "FNN", "kernlab", "RWeka")), row.names = c(NA, 6L), class = "data.frame") knitr::kable(lrns_df, caption = paste("Sample of available learners for binomial", "tasks in the mlr3 package."), caption.short = "Sample of available learners.", booktabs = TRUE) ``` This yields all learners able to model two-class problems (landslide yes or no). We opt for the binomial classification\index{classification} method used in Section \@ref(conventional-model) and implemented as `classif.log_reg` in **mlr3learners**. Additionally, we need to specify the `predict.type` which determines the type of the prediction with `prob` resulting in the predicted probability for landslide occurrence between 0 and 1 (this corresponds to `type = response` in `predict.glm()`). ```{r 12-spatial-cv-13, eval=TRUE} # 2. specify learner learner = mlr3::lrn("classif.log_reg", predict_type = "prob") ``` To access the help page of the learner and find out from which package it was taken, we can run: ```{r 12-spatial-cv-14, eval=FALSE} learner$help() ``` The setup steps for modeling with **mlr3**\index{mlr3 (package)} may seem tedious. But remember, this single interface provides access to the 130+ learners shown by `mlr3extralearners::list_mlr3learners()`; it would be far more tedious to learn the interface for each learner! Further advantages are simple parallelization of resampling techniques and the ability to tune machine learning hyperparameters\index{hyperparameter} (see Section \@ref(svm)). Most importantly, (spatial) resampling in **mlr3spatiotempcv** [@schratz_mlr3spatiotempcv_2021] is straightforward, requiring only two more steps: specifying a resampling method and running it. We will use a 100-repeated 5-fold spatial CV\index{cross-validation!spatial CV}: five partitions will be chosen based on the provided coordinates in our `task` and the partitioning will be repeated 100 times:[^13] [^13]: Note that package **sperrorest** initially implemented spatial cross-validation in R [@brenning_spatial_2012]. In the meantime, its functionality was integrated into the **mlr3** ecosystem which is the reason why we are using **mlr3** [@schratz_hyperparameter_2019]. The **tidymodels** framework is another umbrella package for streamlined modeling in R; however, it only recently integrated support for spatial cross-validation via **spatialsample**, which so far only supports one spatial resampling method. ```{r 12-spatial-cv-18, eval=TRUE} # 3. specify resampling resampling = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) ``` To execute the spatial resampling, we run `resample()` using the previously specified task, learner, and resampling strategy. This takes some time (around 15 seconds on a modern laptop) because it computes 500 resampling partitions and 500 models. Again, we choose the AUROC as performance measure. To retrieve it, we use the `score()` method of the resampling result output object (`score_spcv_glm`). This returns a `data.table` object with 500 rows -- one for each model. ```{r 12-spatial-cv-19, eval=FALSE} # reduce verbosity lgr::get_logger("mlr3")$set_threshold("warn") # run spatial cross-validation and save it to resample result glm (rr_glm) rr_spcv_glm = mlr3::resample(task = task, learner = learner, resampling = resampling) # compute the AUROC as a data.table score_spcv_glm = rr_spcv_glm$score(measure = mlr3::msr("classif.auc")) # keep only the columns you need score_spcv_glm = dplyr::select(score_spcv_glm, task_id, learner_id, resampling_id, classif.auc) ``` The output of the preceding code chunk is a bias-reduced assessment of the model's predictive performance. We have saved it as `extdata/12-bmr_score.rds` in the book's GitHub repository. If required, you can read it in as follows: ```{r 12-spatial-cv-21} score = readRDS("extdata/12-bmr_score.rds") score_spcv_glm = dplyr::filter(score, learner_id == "classif.log_reg", resampling_id == "repeated_spcv_coords") ``` To compute the mean AUROC over all 500 models, we run: ```{r 12-spatial-cv-22} mean(score_spcv_glm$classif.auc) |> round(2) ``` To put these results in perspective, let us compare them with AUROC\index{AUROC} values from a 100-repeated 5-fold non-spatial cross-validation (Figure \@ref(fig:boxplot-cv); the code for the non-spatial cross-validation\index{cross-validation} is not shown here but will be explored in the Exercise section). As expected (see Section \@ref(intro-cv)), the spatially cross-validated result yields lower AUROC values on average than the conventional cross-validation approach, underlining the over-optimistic predictive performance of the latter due to its spatial autocorrelation\index{autocorrelation!spatial}. ```{r boxplot-cv, echo=FALSE, message=FALSE, out.width="75%", fig.cap="Boxplot showing the difference in GLM AUROC values on spatial and conventional 100-repeated 5-fold cross-validation.", fig.scap="Boxplot showing AUROC values."} library(ggplot2) # rename the levels of resampling_id score[, resampling_id := as.factor(resampling_id) |> forcats::fct_recode("conventional CV" = "repeated_cv", "spatial CV" = "repeated_spcv_coords") |> forcats::fct_rev()] # create the boxplot ggplot2::ggplot(data = score[learner_id == "classif.log_reg"], mapping = ggplot2::aes(x = resampling_id, y = classif.auc)) + ggplot2::geom_boxplot(fill = c("lightblue2", "mistyrose2")) + ggplot2::theme_bw() + ggplot2::labs(y = "AUROC", x = "") ``` ### Spatial tuning of machine-learning hyperparameters {#svm} Section \@ref(intro-cv) introduced machine learning\index{machine learning} as part of statistical learning\index{statistical learning}. To recap, we adhere to the following definition of machine learning by [Jason Brownlee](https://machinelearningmastery.com/linear-regression-for-machine-learning/): > Machine learning, more specifically the field of predictive modeling, is primarily concerned with minimizing the error of a model or making the most accurate predictions possible, at the expense of explainability. In applied machine learning we will borrow, reuse and steal algorithms from many different fields, including statistics and use them towards these ends. In Section \@ref(glm) a GLM was used to predict landslide susceptibility. This section introduces support vector machines (SVMs)\index{SVM} for the same purpose. Random forest\index{random forest} models might be more popular than SVMs; however, the positive effect of tuning hyperparameters\index{hyperparameter} on model performance is much more pronounced in the case of SVMs [@probst_hyperparameters_2018]. Since (spatial) hyperparameter tuning is the major aim of this section, we will use an SVM. For those wishing to apply a random forest model, we recommend to read this chapter, and then proceed to Chapter \@ref(eco) in which we will apply the currently covered concepts and techniques to make spatial distribution maps based on a random forest model. SVMs\index{SVM} search for the best possible 'hyperplanes' to separate classes (in a classification\index{classification} case) and estimate 'kernels' with specific hyperparameters\index{hyperparameter} to create non-linear boundaries between classes [@james_introduction_2013]. Machine learning algorithms often feature hyperparameters\index{hyperparameter} and parameters. Parameters can be estimated from the data, while hyperparameters\index{hyperparameter} are set before the learning begins (see also the [machine mastery blog](https://machinelearningmastery.com/difference-between-a-parameter-and-a-hyperparameter/) and the [hyperparameter optimization chapter](https://mlr3book.mlr-org.com/chapters/chapter4/hyperparameter_optimization.html) of the mlr3 book). The optimal hyperparameter\index{hyperparameter} configuration is usually found within a specific search space and determined with the help of cross-validation methods. This is called hyperparameter\index{hyperparameter} tuning and the main topic of this section. Some SVM implementations such as that provided by **kernlab** allow hyperparameters to be tuned automatically, usually based on random sampling (see upper row of Figure \@ref(fig:partitioning)). This works for non-spatial data but is of less use for spatial data where 'spatial tuning' should be undertaken. Before defining spatial tuning, we will set up the **mlr3**\index{mlr3 (package)} building blocks, introduced in Section \@ref(glm), for the SVM. The classification\index{classification} task remains the same, hence, we can simply reuse the `task` object created in Section \@ref(glm). Learners implementing SVM can be found using the `list_mlr3learners()` command of the **mlr3extralearners**. ```{r 12-spatial-cv-23, eval=TRUE, echo=TRUE} mlr3_learners = mlr3extralearners::list_mlr3learners() mlr3_learners |> dplyr::filter(class == "classif" & grepl("svm", id)) |> dplyr::select(id, class, mlr3_package, required_packages) ``` Of the options, we will use `ksvm()` from the **kernlab** package [@karatzoglou_kernlab_2004]. To allow for non-linear relationships, we use the popular radial basis function (or Gaussian) kernel (`"rbfdot" `) which is also the default of `ksvm()`. Setting the `type` argument to `"C-svc"` makes sure that `ksvm()` is solving a classification task. To make sure that the tuning does not stop because of one failing model, we additionally define a fallback learner (for more information please refer to https://mlr3book.mlr-org.com/chapters/chapter10/advanced_technical_aspects_of_mlr3.html#sec-fallback). ```{r 12-spatial-cv-24} lrn_ksvm = mlr3::lrn("classif.ksvm", predict_type = "prob", kernel = "rbfdot", type = "C-svc") lrn_ksvm$encapsulate(method = "try", fallback = lrn("classif.featureless", predict_type = "prob")) ``` The next stage is to specify a resampling strategy. Again we will use a 100-repeated 5-fold spatial CV\index{cross-validation!spatial CV}. ```{r 12-spatial-cv-25} # performance estimation level perf_level = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) ``` Note that this is the exact same code as used for the resampling for the GLM\index{GLM} in Section \@ref(glm); we have simply repeated it here as a reminder. So far, the process has been identical to that described in Section \@ref(glm). The next step is new, however: to tune the hyperparameters\index{hyperparameter}. Using the same data for the performance assessment and the tuning would potentially lead to overoptimistic results [@cawley_overfitting_2010]. This can be avoided using nested spatial CV\index{cross-validation!spatial CV}. ```{r inner-outer, echo=FALSE, fig.cap="Schematic of hyperparameter tuning and performance estimation levels in CV. (Figure was taken from Schratz et al. (2019). Permission to reuse it was kindly granted.)", fig.scap="Schematic of hyperparameter tuning."} knitr::include_graphics("images/12_cv.png") ``` This means that we split each fold again into five spatially disjoint subfolds which are used to determine the optimal hyperparameters\index{hyperparameter} (`tune_level` object in the code chunk below; see Figure \@ref(fig:inner-outer) for a visual representation). The random selection of values C and Sigma is additionally restricted to a predefined tuning space (`search_space` object). The range of the tuning space was chosen with values recommended in the literature [@schratz_hyperparameter_2019]. To find the optimal hyperparameter combination, we fit 50 models (`terminator` object in the code chunk below) in each of these subfolds with randomly selected values for the hyperparameters C and Sigma. ```{r 12-spatial-cv-26, eval=TRUE} # five spatially disjoint partitions tune_level = mlr3::rsmp("spcv_coords", folds = 5) # define the outer limits of the randomly selected hyperparameters search_space = paradox::ps( C = paradox::p_dbl(lower = -12, upper = 15, trafo = function(x) 2^x), sigma = paradox::p_dbl(lower = -15, upper = 6, trafo = function(x) 2^x) ) # use 50 randomly selected hyperparameters terminator = mlr3tuning::trm("evals", n_evals = 50) tuner = mlr3tuning::tnr("random_search") ``` The next stage is to modify the learner `lrn_ksvm` in accordance with all the characteristics defining the hyperparameter tuning with `auto_tuner()`. ```{r 12-spatial-cv-27, eval=TRUE} at_ksvm = mlr3tuning::auto_tuner( learner = lrn_ksvm, resampling = tune_level, measure = mlr3::msr("classif.auc"), search_space = search_space, terminator = terminator, tuner = tuner ) # again we need to set a fallback model at_ksvm$encapsulate( method = "try", fallback = lrn("classif.featureless", predict_type = "prob" ) ) ``` The tuning is now set up to fit 250 models to determine optimal hyperparameters for one fold. Repeating this for each fold, we end up with 1,250 (250 \* 5) models for each repetition. Repeated 100 times means fitting a total of 125,000 models to identify optimal hyperparameters (Figure \@ref(fig:partitioning)). These are used in the performance estimation, which requires the fitting of another 500 models (5 folds \* 100 repetitions; see Figure \@ref(fig:partitioning)). To make the performance estimation processing chain even clearer, let us write down the commands we have given to the computer: 1. Performance level (upper left part of Figure \@ref(fig:inner-outer)): split the dataset into five spatially disjoint (outer) subfolds. 1. Tuning level (lower left part of Figure \@ref(fig:inner-outer)): use the first fold of the performance level and split it again spatially into five (inner) subfolds for the hyperparameter tuning. Use the 50 randomly selected hyperparameters\index{hyperparameter} in each of these inner subfolds, i.e., fit 250 models. 1. Performance estimation: use the best hyperparameter combination from the previous step (tuning level) and apply it to the first outer fold in the performance level to estimate the performance (AUROC\index{AUROC}). 1. Repeat steps 2 and 3 for the remaining four outer folds. 1. Repeat steps 2 to 4, 100 times. The process of hyperparameter tuning and performance estimation is computationally intensive. To decrease model runtime, **mlr3** offers the possibility to use parallelization\index{parallelization} with the help of the **future** package. Since we are about to run a nested cross-validation, we can decide if we would like to parallelize the inner or the outer loop (see lower left part of Figure \@ref(fig:inner-outer)). Since the former will run 125,000 models, whereas the latter only runs 500, it is quite obvious that we should parallelize the inner loop. To set up the parallelization of the inner loop, we run: ```{r future, eval=FALSE} library(future) # execute the outer loop sequentially and parallelize the inner loop future::plan(list("sequential", "multisession"), workers = floor(availableCores() / 2)) ``` Additionally, we instructed **future** to only use half instead of all available cores (default), a setting that allows possible other users to work on the same high performance computing cluster in case one is used. Now we are set up for computing the nested spatial CV. Specifying the `resample()` parameters follows the exact same procedure as presented when using a GLM\index{GLM}, the only difference being the `store_models` and `encapsulate` arguments. Setting the former to `TRUE` would allow the extraction of the hyperparameter\index{hyperparameter} tuning results which is important if we plan follow-up analyses on the tuning. The latter ensures that the processing continues even if one of the models throws an error. This avoids the process stopping just because of one failed model, which is desirable on large model runs. Once the processing is completed, one can have a look at the failed models. After the processing, it is good practice to explicitly stop the parallelization\index{parallelization} with `future:::ClusterRegistry("stop")`. Finally, we save the output object (`result`) to disk in case we would like to use it in another R session. Before running the subsequent code, be aware that it is time-consuming since it will run the spatial cross-validation with 125,500 models. It can easily run for half a day on a modern laptop. Note that runtime depends on many aspects: CPU speed, the selected algorithm, the selected number of cores and the dataset. ```{r 12-spatial-cv-30, eval=FALSE} progressr::with_progress(expr = { rr_spcv_svm = mlr3::resample(task = task, learner = at_ksvm, # outer resampling (performance level) resampling = perf_level, store_models = FALSE, encapsulate = "evaluate") }) # stop parallelization future:::ClusterRegistry("stop") # compute the AUROC values score_spcv_svm = rr_spcv_svm$score(measure = mlr3::msr("classif.auc")) # keep only the columns you need score_spcv_svm = dplyr::select(score_spcv_svm, task_id, learner_id, resampling_id, classif.auc) ``` In case you do not want to run the code locally, we have saved [score_svm](https://github.com/geocompx/geocompr/blob/main/extdata/12-bmr_score.rds) in the book's GitHub repository. They can be loaded as follows: ```{r 12-spatial-cv-31} score = readRDS("extdata/12-bmr_score.rds") score_spcv_svm = dplyr::filter(score, learner_id == "classif.ksvm.tuned", resampling_id == "repeated_spcv_coords") ``` Let's have a look at the final AUROC\index{AUROC}: the model's ability to discriminate the two classes. ```{r 12-spatial-cv-33} # final mean AUROC round(mean(score_spcv_svm$classif.auc), 2) ``` It appears that the GLM\index{GLM} (aggregated AUROC\index{AUROC} was `r score[resampling_id == "repeated_spcv_coords" & learner_id == "classif.log_reg", round(mean(classif.auc), 2)]`) is slightly better than the SVM\index{SVM} in this specific case. To guarantee an absolute fair comparison, one should also make sure that the two models use the exact same partitions -- something we have not shown here but have silently used in the background (see `code/12_cv.R` in the book's GitHub repository for more information). To do so, **mlr3** offers the functions `benchmark_grid()` and `benchmark()` [see also https://mlr3book.mlr-org.com/chapters/chapter3/evaluation_and_benchmarking.html#sec-benchmarking, @bischl_applied_2024]. We will explore these functions in more detail in the Exercises. Please note also that using more than 50 iterations in the random search of the SVM would probably yield hyperparameters\index{hyperparameter} that result in models with a better AUROC [@schratz_hyperparameter_2019]. On the other hand, increasing the number of random search iterations would also increase the total number of models and thus runtime. So far spatial CV\index{cross-validation!spatial CV} has been used to assess the ability of learning algorithms to generalize to unseen data. For predictive mapping purposes, one would tune the hyperparameters\index{hyperparameter} on the complete dataset. This will be covered in Chapter \@ref(eco). ## Conclusions Resampling methods are an important part of a data scientist's toolbox [@james_introduction_2013]. This chapter used cross-validation\index{cross-validation} to assess predictive performance of various models. As described in Section \@ref(intro-cv), observations with spatial coordinates may not be statistically independent due to spatial autocorrelation\index{autocorrelation!spatial}, violating a fundamental assumption of cross-validation. Spatial CV\index{cross-validation!spatial CV} addresses this issue by reducing bias introduced by spatial autocorrelation\index{autocorrelation!spatial}. The **mlr3**\index{mlr3 (package)} package facilitates (spatial) resampling\index{resampling} techniques in combination with the most popular statistical learning\index{statistical learning} techniques including linear regression\index{regression!linear}, semi-parametric models such as generalized additive models\index{generalized additive model} and machine learning\index{machine learning} techniques such as random forests\index{random forest}, SVMs\index{SVM}, and boosted regression trees [@bischl_mlr:_2016;@schratz_hyperparameter_2019]. Machine learning algorithms often require hyperparameter\index{hyperparameter} inputs, the optimal 'tuning' of which can require thousands of model runs which require large computational resources, consuming much time, RAM and/or cores. **mlr3** tackles this issue by enabling parallelization\index{parallelization}. Machine learning overall, and its use to understand spatial data, is a large field and this chapter has provided the basics, but there is more to learn. We recommend the following resources in this direction: - The **mlr3 book** (@bischl_applied_2024; https://mlr3book.mlr-org.com/) and especially the [chapter on the handling of spatiotemporal data](https://mlr3book.mlr-org.com/chapters/chapter13/beyond_regression_and_classification.html#spatiotemp-cv) - An academic paper on hyperparameter\index{hyperparameter} tuning [@schratz_hyperparameter_2019] - An academic paper on how to use **mlr3spatiotempcv** [@schratz_mlr3spatiotempcv_2021] - In case of spatiotemporal data, one should account for spatial\index{autocorrelation!spatial} and temporal\index{autocorrelation!temporal} autocorrelation when doing CV\index{cross-validation} [@meyer_improving_2018] ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_12-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 13-transport.Rmd ================================================ # (PART) Applications {.unnumbered} # Transportation {#transport} ```{r, include=FALSE} source("code/before_script.R") ``` ```{r transport-setup, include=FALSE} knitr::opts_chunk$set(warning = FALSE) ``` ## Prerequisites {.unnumbered} - This chapter uses the following packages:[^13-transport-1] [^13-transport-1]: The **nabor** package must also be installed, although it does not need to be attached. ```{r 13-transport-1, message=FALSE, results='hide'} library(sf) library(dplyr) library(spDataLarge) library(stplanr) # for processing geographic transport data library(tmap) # map-making (see Chapter 9) library(ggplot2) # data visualization package library(sfnetworks) # spatial network classes and functions ``` ## Introduction In few other sectors is geographic space more tangible than transportation. The effort of moving (overcoming distance) is central to the 'first law' of geography, defined by Waldo Tobler in 1970 as follows [@tobler_computer_1970]: > Everything is related to everything else, but near things are more related than distant things. This 'law' is the basis for spatial autocorrelation\index{autocorrelation!spatial} and other key geographic concepts. It applies to phenomena as diverse as friendship networks and ecological diversity and can be explained by the costs of transport --- in terms of time, energy and money --- which constitute the 'friction of distance'. From this perspective, transport technologies are disruptive, changing spatial relationships between geographic entities including mobile humans and goods: "the purpose of transportation is to overcome space" [@rodrigue_geography_2013]. Transport is an inherently spatial activity, involving moving from an origin point 'A' to a destination point 'B', through infinite localities in between. It is therefore unsurprising that transport researchers have long turned to geographic and computational methods to understand movement patterns, and how interventions can improve their performance [@lovelace_open_2021]. This chapter introduces the geographic analysis of transport systems at different geographic levels: - **Areal units**\index{areal units}: transport patterns can be understood with reference to zonal aggregates, such as the main mode of travel (by car, bike or foot, for example), and average distance of trips made by people living in a particular zone, covered in Section \@ref(transport-zones) - **Desire lines**\index{desire lines}: straight lines that represent 'origin-destination' data that records how many people travel (or could travel) between places (points or zones) in geographic space, the topic of Section \@ref(desire-lines) - **Nodes**\index{node}: these are points in the transport system that can represent common origins and destinations and public transport stations such as bus stops and rail stations, the topic of Section \@ref(nodes) - **Routes**\index{routes}: these are lines representing a path along the route network along the desire lines and between nodes. Routes (which can be represented as single linestrings or multiple short *segments*) and the *routing engines* that generate them, are covered in Section \@ref(routes) - **Route networks**\index{network}: these represent the system of roads, paths and other linear features in an area and are covered in Section \@ref(route-networks). They can be represented as geographic features (typically short segments of road that add up to create a full network) or structured as an interconnected graph, with the level of traffic on different segments referred to as 'flow' by transport modelers [@hollander_transport_2016] Another key level is **agents**, mobile entities like you and me and vehicles that enable us to move such as bikes and buses. These can be represented computationally in software such as [MATSim](http://www.matsim.org/) and [A/B Street](https://github.com/a-b-street/abstreet), which represent the dynamics of transport systems using an agent-based modeling (ABM)\index{agent-based modeling} framework, usually at high levels of spatial and temporal resolution [@horni_multi-agent_2016]. ABM is a powerful approach to transport research with great potential for integration with R's spatial classes [@thiele_r_2014; @lovelace_spatial_2016], but is outside the scope of this chapter. Beyond geographic levels and agents, the basic unit of analysis in many transport models is the **trip**, a single purpose journey from an origin 'A' to a destination 'B' [@hollander_transport_2016]. Trips join-up the different levels of transport systems and can be represented simplistically as geographic *desire lines* connecting *zone* centroids\index{centroid} (*nodes*) or as routes that follow the transport *route network*. In this context, *agents*\index{agent-based modeling} are usually point entities that move within the transport network. Transport systems are dynamic [@xie_evolving_2011]. While the focus of this chapter is the *geographic* analysis of a transport systems, it provides insights into how the approach can be used to simulate scenarios of change, in Section \@ref(prioritizing-new-infrastructure). The purpose of geographic transport modeling can be interpreted as simplifying the complexity of these spatiotemporal systems in ways that capture their essence. Selecting appropriate levels of geographic analysis can help simplify this complexity without losing its most important features and variables, enabling better decision-making and more effective interventions [@hollander_transport_2016]. Typically, models are designed to tackle a particular problem, such as how to improve safety or the environmental performance of transport systems. For this reason, this chapter is based around a policy scenario, introduced in the next section, that asks: how to increase cycling in the city of Bristol? Chapter \@ref(location) demonstrates a related application of geocomputation: prioritizing the location of new bike shops. There is a link between the chapters: new and effectively-located cycling infrastructure can get people cycling, boosting demand for bike shops and local economic activity. This highlights an important feature of transport systems: they are closely linked to broader phenomena and land-use patterns. ## A case study of Bristol {#bris-case} The case study used for this chapter is located in Bristol, a city in the west of England, around 30 km east of the Welsh capital Cardiff. An overview of the region's transport network is illustrated in Figure \@ref(fig:bristol), which shows a diversity of transport infrastructure, for cycling, public transport, and private motor vehicles. ```{r 13-transport-2, echo=FALSE, eval=FALSE} # code that generated the input data - see also ?bristol_ways # source("https://github.com/geocompx/geocompr/raw/main/code/13-transport-data-gen.R") # view input data # summary(bristol_ways) # summary(bristol_ttwa) # summary(bristol_region) library(tmap) region_all = rbind(bristol_region, bristol_ttwa) tmap_mode("view") tm_shape(region_all[1, ], bbox = region_all) + tm_fill("yellow", col_alpha = 0.5) + tm_shape(bristol_ways) + tm_lines(col = "highway", lwd = 2.1, col.scale = tm_scale(values = "-Set1")) + tm_scalebar() + tm_shape(region_all) + tm_borders(col = "black") + tm_basemap(server = leaflet::providers$Esri.WorldTopoMap) ``` ```{r bristol, echo=FALSE, fig.cap="Bristol's transport network represented by colored lines for active (green), public (railways, blue) and private motor (red) modes of travel. Black border lines represent the inner city boundary (highlighted in yellow) and the larger Travel To Work Area (TTWA).", fig.scap="Bristol's transport network."} knitr::include_graphics("images/13_bristol.png") ``` Bristol is the tenth largest city council in England, with a population of half a million people, although its travel catchment area\index{catchment area} is larger (see Section \@ref(transport-zones)). It has a vibrant economy with aerospace, media, financial service and tourism companies, alongside two major universities. Bristol shows a high average income per person but also contains areas of severe deprivation [@bristol_city_council_deprivation_2015]. In terms of transport, Bristol is well served by rail and road links, and it has a relatively high level of active travel. According to the [Active People Survey](https://www.gov.uk/government/statistical-data-sets/how-often-and-time-spent-walking-and-cycling-at-local-authority-level-cw010#table-cw0103), 19% of its citizens cycle and 88% walk at least once per month (the national average is 15% and 81%, respectively). 8% of the population said they cycled to work in the 2011 census, compared with only 3% nationwide. ```{r 13-transport-3, eval=FALSE, echo=FALSE} if (!require(readODS)) { install.packages("readODS") } u = "https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/536823/local-area-walking-and-cycling-in-england-2015.zip" download.file(u, "local-area-walking-and-cycling-in-england-2015.zip") unzip("local-area-walking-and-cycling-in-england-2015.zip") View(readODS::read_ods("Table index.ods")) cw0103 = readODS::read_ods("cw0103.ods") View(cw0103) ``` Like many cities, Bristol has major congestion, air quality and physical inactivity problems. Cycling can tackle all of these issues efficiently: it has a greater potential to replace car trips than walking, with typical [speeds](https://en.wikipedia.org/wiki/Bicycle_performance) of 15-20 km/h vs. 4-6 km/h for walking. For this reason, Bristol's [Transport Strategy](https://www.bristol.gov.uk/council-and-mayor/policies-plans-and-strategies/bristol-transport-strategy) has ambitious plans for cycling. To highlight the importance of policy considerations in transportation research, this chapter is guided by the need to provide evidence for people (transport planners, politicians and other stakeholders) tasked with getting people out of cars and onto more sustainable modes --- walking and cycling in particular. The broader aim is to demonstrate how geocomputation can support evidence-based transport planning. In this chapter you will learn how to: - Describe the geographical patterns of transport behavior in cities - Identify key public transport nodes supporting multi-modal trips - Analyze travel 'desire lines' to find where many people drive short distances - Identify cycle route locations that will encourage less car driving and more cycling To get the wheels rolling on the practical aspects of this chapter, the next section begins by loading zonal data on travel patterns. These zone-level datasets are small but often vital for gaining a basic understanding of a settlement's overall transport system. ## Transport zones Although transport systems are primarily based on linear features and nodes --- including pathways and stations --- it often makes sense to start with areal data, to break continuous space into tangible units [@hollander_transport_2016]. In addition to the boundary defining the study area (Bristol in this case), two zone types are of particular interest to transport researchers: origin and destination zones. Often, the same geographic units are used for origins and destinations. However, different zoning systems, such as '[Workplace Zones](https://www.data.gov.uk/dataset/022e455a-b5cc-4247-bfc5-1aaf9da78379/workplace-zones-a-new-geography-for-workplace-statistics)', may be appropriate to represent the increased density of trip destinations in areas with many 'trip attractors' such as schools and shops [@office_for_national_statistics_workplace_2014]. The simplest way to define a study area is often the first matching boundary returned by OpenStreetMap\index{OpenStreetMap}. This can be done with a command such as `osmdata::getbb("Bristol", format_out = "sf_polygon", limit = 1)`. This returns an `sf` object (or a list of `sf` objects if `limit = 1` is not specified) representing the bounds of the largest matching city region, either a rectangular polygon of the bounding box or a detailed polygonal boundary.[^13-transport-2] For Bristol, a detailed polygon is returned, as represented by the `bristol_region` object in the **spDataLarge** package. See the inner blue boundary in Figure \@ref(fig:bristol): there are a couple of issues with this approach: [^13-transport-2]: In cases where the first match does not provide the right name, the country or region should be specified, for example `Bristol Tennessee` for a Bristol located in America. - The first boundary returned by OSM may not be the official boundary used by local authorities - Even if OSM returns the official boundary, this may be inappropriate for transport research because they bear little relation to where people travel Travel To Work Areas (TTWAs) address these issues by creating a zoning system analogous to hydrological watersheds. TTWAs were first defined as contiguous zones within which 75% of the population travels to work [@coombes_efficient_1986], and this is the definition used in this chapter. Because Bristol is a major employer attracting travel from surrounding towns, its TTWA is substantially larger than the city bounds (see Figure \@ref(fig:bristol)). The polygon representing this transport-orientated boundary is stored in the object `bristol_ttwa`, provided by the **spDataLarge** package loaded at the beginning of this chapter. The origin and destination zones used in this chapter are the same: officially defined zones of intermediate geographic resolution (their [official](https://www.ons.gov.uk/peoplepopulationandcommunity/populationandmigration/populationestimates/bulletins/annualsmallareapopulationestimates/2014-10-23) name is Middle layer Super Output Areas or MSOAs). Each houses around 8,000 people. Such administrative zones can provide vital context to transport analysis, such as the type of people who might benefit most from particular interventions (e.g., @moreno-monroy_public_2017). The geographic resolution of these zones is important: small zones with high geographic resolution are usually preferable, but their high number in large regions can have consequences for processing. This is especially true for origin-destination (OD) analysis in which the number of possibilities increases as a non-linear function of the number of zones [@hollander_transport_2016]. ```{block 13-transport-4, type='rmdnote'} Another issue with small zones is related to anonymity rules. To make it impossible to infer the identity of individuals in zones, detailed socio-demographic variables are often only available at a low geographic resolution. Breakdowns of travel mode by age and sex, for example, are available at the Local Authority level in the UK, but not at the much higher Output Area level, each of which contains around 100 households. For further details, see www.ons.gov.uk/methodology/geography. ``` The 102 zones used in this chapter are stored in `bristol_zones`, as illustrated in Figure \@ref(fig:zones). Note the zones get smaller in densely populated areas: each houses a similar number of people. `bristol_zones` contains no attribute data on transport, however, only the name and code of each zone: ```{r 13-transport-5} names(bristol_zones) ``` To add travel data, we will perform an *attribute join*\index{attribute!join}, a common task described in Section \@ref(vector-attribute-joining). We will use travel data from the UK's 2011 census question on travel to work, data stored in `bristol_od`, which was provided by the [ons.gov.uk](https://www.ons.gov.uk/help/localstatistics) data portal. `bristol_od` is an OD dataset on travel to work between zones from the UK's 2011 Census (see Section \@ref(desire-lines)). The first column is the ID of the zone of origin and the second column is the zone of destination. `bristol_od` has more rows than `bristol_zones`, representing travel *between* zones rather than the zones themselves: ```{r 13-transport-6} nrow(bristol_od) nrow(bristol_zones) ``` The results of the previous code chunk shows that there are more than 10 OD pairs for every zone, meaning we will need to aggregate the origin-destination data before it is joined with `bristol_zones`, as illustrated below (OD data is described in Section \@ref(desire-lines)). ```{r 13-transport-7} zones_attr = bristol_od |> group_by(o) |> summarize(across(where(is.numeric), sum)) |> dplyr::rename(geo_code = o) ``` The preceding chunk: - Grouped the data by zone of origin (contained in the column `o`) - Aggregated the variables in the `bristol_od` dataset *if* they were numeric, to find the total number of people living in each zone by mode of transport[^13-transport-3] - Renamed the grouping variable `o` so it matches the ID column `geo_code` in the `bristol_zones` object [^13-transport-3]: The `_if` affix requires a `TRUE`/`FALSE` question to be asked of the variables, in this case 'is it numeric?' and only variables returning true are summarized. The resulting object `zones_attr` is a data frame with rows representing zones and an ID variable. We can verify that the IDs match those in the `zones` dataset using the `%in%` operator as follows: ```{r 13-transport-8} summary(zones_attr$geo_code %in% bristol_zones$geo_code) ``` The results show that all 102 zones are present in the new object and that `zone_attr` is in a form that can be joined onto the zones.[^13-transport-4] This is done using the joining function `left_join()` (note that `inner_join()` would produce here the same result): \index{join!inner} \index{join!left} [^13-transport-4]: It would also be important to check that IDs match in the opposite direction on real data. This could be done by changing the order of the IDs in the `summary()` command --- `summary(bristol_zones$geo_code %in% zones_attr$geo_code)` --- or by using `setdiff()` as follows: `setdiff(bristol_zones$geo_code, zones_attr$geo_code)`. ```{r 13-transport-9} zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") sum(zones_joined$all) names(zones_joined) ``` The result is `zones_joined`, which contains new columns representing the total number of trips originating in each zone in the study area (almost 1/4 of a million) and their mode of travel (by bicycle, foot, car and train). The geographic distribution of trip origins is illustrated in the left-hand panel in Figure \@ref(fig:zones). This shows that most zones have between 0 and 4,000 trips originating from them in the study area. More trips are made by people living near the center of Bristol and fewer on the outskirts. Why is this? Remember that we are only dealing with trips within the study region: low trip numbers in the outskirts of the region can be explained by the fact that many people in these peripheral zones will travel to other regions outside of the study area. Trips outside the study region can be included in a regional model by a special destination ID covering any trips that go to a zone not represented in the model [@hollander_transport_2016]. The data in `bristol_od`, however, simply ignores such trips: it is an 'intra-zonal' model. In the same way that OD datasets can be aggregated to the zone of origin, they can also be aggregated to provide information about destination zones. People tend to gravitate towards central places. This explains why the spatial distribution represented in the right panel in Figure \@ref(fig:zones) is relatively uneven, with the most common destination zones concentrated in Bristol city center. The result is `zones_od`, which contains a new column reporting the number of trip destinations by any mode, and it is created as follows: ```{r 13-transport-10} zones_destinations = bristol_od |> group_by(d) |> summarize(across(where(is.numeric), sum)) |> select(geo_code = d, all_dest = all) zones_od = inner_join(zones_joined, zones_destinations, by = "geo_code") ``` A simplified version of Figure \@ref(fig:zones) is created with the code below (see `13-zones.R` in the [`code`](https://github.com/geocompx/geocompr/tree/main/code) folder of the book's GitHub repository to reproduce the figure and Section \@ref(faceted-maps) for details on faceted maps with **tmap**\index{tmap (package)}): ```{r 13-transport-11, eval=FALSE} qtm(zones_od, c("all", "all_dest")) + tm_layout(panel.labels = c("Origin", "Destination")) ``` ```{r zones, echo=FALSE, fig.cap="Number of trips (commuters) living and working in the region. The left map shows zone of origin of commute trips; the right map shows zone of destination (generated by the script `13-zones.R`).", message=FALSE, fig.scap="Number of trips (commuters) living and working in the region."} # file.edit("code/13-zones.R") source("code/13-zones.R", print.eval = TRUE) ``` ## Desire lines Desire lines\index{desire lines} connect origins and destinations, representing where people *desire* to go, typically between zones. They represent the quickest 'bee line' or 'crow flies' route between A and B that would be taken, if it were not for obstacles such as buildings and windy roads getting in the way (we will see how to convert desire lines into routes in the next section). Typically, desire lines are represented geographically as starting and ending in the geographic (or population weighted) centroid of each zone. This is the type of desire line that we will create and use in this section, although it is worth being aware of 'jittering' techniques that enable multiple start and end points to increase the spatial coverage and accuracy of analyses building on OD data [@lovelace_jittering_2022b]. We have already loaded data representing desire lines in the dataset `bristol_od`. This data frame represents the number of people traveling between the zone represented in `o` and `d`, as illustrated in Table \@ref(tab:od). To arrange the OD data by all trips and then filter-out only the top 5, type (please refer to Chapter \@ref(attr) for a detailed description of non-spatial attribute operations): ```{r 13-transport-12} od_top5 = bristol_od |> slice_max(all, n = 5) ``` ```{r od, echo=FALSE} od_top5 |> knitr::kable( caption = paste("Sample of the top 5 origin-destination pairs in the", "Bristol OD data frame, representing travel desire", "lines between zones in the study area."), caption.short = "Sample of the origin-destination data.", booktabs = TRUE) ``` The resulting table provides a snapshot of Bristolian travel patterns in terms of commuting (travel to work). It demonstrates that walking is the most popular mode of transport among the top 5 OD pairs, that zone `E02003043` is a popular destination (Bristol city center, the destination of all the top 5 OD pairs), and that the *intrazonal* trips, from one part of zone `E02003043` to another (first row of Table \@ref(tab:od)), constitute the most traveled OD pair in the dataset. But from a policy perspective, the raw data presented in Table \@ref(tab:od) is of limited use: aside from the fact that it contains only a tiny portion of the 2,910 OD pairs, it tells us little about *where* policy measures are needed, or *what proportion* of trips are made by walking and cycling. The following command calculates the percentage of each desire line that is made by these active modes: ```{r 13-transport-13} bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 ``` There are two main types of OD pairs: *interzonal* and *intrazonal*. Interzonal OD pairs represent travel between zones in which the destination is different from the origin. Intrazonal OD pairs represent travel within the same zone (see the top row of Table \@ref(tab:od)). The following code chunk splits `od_bristol` into these two types: ```{r 13-transport-14} od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) ``` The next step is to convert the interzonal OD pairs into an `sf` object representing desire lines that can be plotted on a map with the **stplanr**\index{stplanr (package)} function `od2line()`.[^13-transport-5] [^13-transport-5]: `od2line()` works by matching the IDs in the first two columns of the `bristol_od` object to the `zone_code` ID column in the geographic `zones_od` object. Note that the operation emits a warning because `od2line()` works by allocating the start and end points of each origin-destination pair to the *centroid*\index{centroid} of its zone of origin and destination. For real-world use one would use centroid values generated from projected data or, preferably, use *population-weighted* centroids [@lovelace_propensity_2017]. ```{r 13-transport-15, warning=FALSE} desire_lines = od2line(od_inter, zones_od) ``` An illustration of the results is presented in Figure \@ref(fig:desire), a simplified version of which is created with the following command (see the code in `13-desire.R` to reproduce the figure exactly and Chapter \@ref(adv-map) for details on visualization with **tmap**\index{tmap (package)}): ```{r 13-transport-16, eval=FALSE} qtm(desire_lines, lines.lwd = "all") ``` ```{r desire, echo=FALSE, warning=FALSE, message=FALSE, fig.cap="Desire lines representing trip patterns in Bristol, with width representing number of trips and color representing the percentage of trips made by active modes (walking and cycling). The four black lines represent the interzonal OD pairs in Table 13.1.", fig.asp=0.8, fig.scap="Desire lines representing trip patterns in Bristol."} source("code/13-desire.R", print.eval = TRUE) ``` The map shows that the city center dominates transport patterns in the region, suggesting policies should be prioritized there, although a number of peripheral sub-centers can also be seen. Desire lines are important generalized components of transport systems. More concrete components include nodes, which have specific destinations (rather than hypothetical straight lines represented in desire lines). Nodes are covered in the next section. ## Nodes {#nodes} Nodes in geographic transport datasets are points among the predominantly linear features that comprise transport networks. Broadly are two main types of transport nodes: 1. Nodes not directly on the network\index{network} such as zone centroids\index{centroid} or individual origins and destinations such as houses and workplaces 2. Nodes that are a part of transport networks. Technically, a node can be located at any point on a transport network but in practice they are often special kinds of vertex such as intersections between pathways (junctions) and points for entering or exiting a transport network such as bus stops and train stations[^13-transport-6] [^13-transport-6]: The function [`st_network_blend()`](https://luukvdmeer.github.io/sfnetworks/reference/st_network_blend.html) from the **sfnetworks** package allows new nodes to be created on the network based on proximity to arbitrary points on or off the network. Transport networks can be represented as graphs\index{graph}, in which each segment is connected (via edges representing geographic lines) to one or more other edges\index{edge} in the network. Nodes outside the network can be added with "centroid connectors", new route segments to nearby nodes on the network [@hollander_transport_2016].[^13-transport-7] Every node\index{node} in the network is then connected by one or more 'edges' that represent individual segments on the network. We will see how transport networks can be represented as graphs in Section \@ref(route-networks). [^13-transport-7]: The location of these connectors should be chosen carefully because they can lead to over-estimates of traffic volumes in their immediate surroundings [@jafari_investigation_2015]. Public transport stops are particularly important nodes that can be represented as either type of node: a bus stop that is part of a road, or a large rail station that is represented by its pedestrian entry point hundreds of meters from railway tracks. We will use railway stations to illustrate public transport nodes, in relation to the research question of increasing cycling in Bristol. These stations are provided by **spDataLarge** in `bristol_stations`. A common barrier preventing people from switching away from cars for commuting to work is that the distance from home to work is too far to walk or cycle. Public transport can reduce this barrier by providing a fast and high-volume option for common routes into cities. From an active travel perspective, public transport 'legs' of longer journeys divide trips into three: - The origin leg, typically from residential areas to public transport stations - The public transport leg, which typically goes from the station nearest a trip's origin to the station nearest its destination - The destination leg, from the station of alighting to the destination Building on the analysis conducted in Section \@ref(desire-lines), public transport nodes can be used to construct three-part desire lines for trips that can be taken by bus and (the mode used in this example) rail. The first stage is to identify the desire lines with most public transport travel, which in our case is easy because our previously created dataset `desire_lines` already contains a variable describing the number of trips by train (the public transport potential could also be estimated using public transport routing services such as [OpenTripPlanner](http://www.opentripplanner.org/)). To make the approach easier to follow, we will select only the top three desire lines\index{desire lines} in terms of rails use: ```{r 13-transport-20} desire_rail = top_n(desire_lines, n = 3, wt = train) ``` The challenge now is to 'break-up' each of these lines into three pieces, representing travel via public transport nodes. This can be done by converting a desire line into a multilinestring object consisting of three line geometries representing origin, public transport and destination legs of the trip. This operation can be divided into three stages: matrix creation (of origins, destinations and the 'via' points representing rail stations), identification of nearest neighbors\index{nearest neighbor} and conversion to multilinestrings\index{vector!multilinestrings}. These are undertaken by `line_via()`. This **stplanr**\index{stplanr (package)} function takes input lines and points and returns a copy of the desire lines --- see the [`?line_via()`](https://docs.ropensci.org/stplanr/reference/line_via.html) for details on how this works. The output is the same as the input line, except it has new geometry columns representing the journey via public transport nodes, as demonstrated below: ```{r 13-transport-21} ncol(desire_rail) desire_rail = line_via(desire_rail, bristol_stations) ncol(desire_rail) ``` As illustrated in Figure \@ref(fig:stations), the initial `desire_rail` lines now have three additional geometry list columns\index{list column} representing travel from home to the origin station, from there to the destination, and finally from the destination station to the destination. In this case, the destination leg is very short (walking distance), but the origin legs may be sufficiently far to justify investment in cycling infrastructure to encourage people to cycle to the stations on the outward leg of peoples' journey to work in the residential areas surrounding the three origin stations in Figure \@ref(fig:stations). ```{r stations, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Station nodes (red dots) used as intermediary points that convert straight desire lines with high rail usage (thin green lines) into three legs: to the origin station (orange) via public transport (blue) and to the destination (pink, not visible because it is short).", fig.scap="Station nodes."} # zone_cents = st_centroid(zones_od) zone_cents = st_centroid(zones_od) zone_cents_rail = zone_cents[desire_rail, ] bb = tmaptools::bb(desire_rail, ext = 1.1) desire_rail_plot = rbind( st_sf(data.frame(Geometry = "Desire line (original)"), geometry = desire_rail$geometry), st_sf(data.frame(Geometry = "Leg 1 (origin to station)"), geometry = desire_rail$leg_orig), st_sf(data.frame(Geometry = "Leg 2 (station to station)"), geometry = desire_rail$leg_via), st_sf(data.frame(Geometry = "Leg 3 (station to destination)"), geometry = desire_rail$leg_dest) ) desire_rail_plot = desire_rail_plot |> mutate(lty = case_when(Geometry == "Desire line (original)" ~ 2, TRUE ~ 1)) |> mutate(size = case_when(Geometry == "Desire line (original)" ~ 1, TRUE ~ 2)) bristol_rail_points = rbind( st_sf(data.frame( Node = "Origin and destination locations", col = "black" ), geometry = zone_cents_rail$geometry), st_sf(data.frame( Node = "Public transport node", col = "red" ), geometry = bristol_stations$geometry) ) tm_shape(zones_od) + tm_fill(fill_alpha = 0.2, lwd = 0.1) + tm_shape(desire_rail_plot, bbox = bb, is.main = TRUE) + tm_lines(col = "Geometry", col.scale = tm_scale(values = "Set2"), col.legend = tm_legend(position = tm_pos_in("left", "top")), lwd = 2, lty = "lty", lty.scale = tm_scale_categorical(), lty.legend = tm_legend_hide()) + tm_shape(bristol_rail_points) + tm_symbols(fill = "col", size = 0.75) + tm_scalebar() ``` ## Routes \index{routes} From a geographical perspective, routes are desire lines\index{desire lines} that are no longer straight: the origin and destination points are the same as in the desire line representation of travel, but the pathway to get from A to B is more complex. The geometries of routes are typically (but not always) determined by the transport network. While desire lines contain only two vertices (their beginning and end points), routes can contain any number of vertices, representing points between A and B joined by straight lines: the definition of a linestring geometry. Routes covering large distances or following intricate networks can have thousands of vertices; routes on grid-based or simplified road networks tend to have fewer. Routes are generated from desire lines or, more commonly, matrices containing coordinate pairs representing desire lines. This routing process is done by a range of broadly-defined *routing engines*: software and web services that return geometries and attributes describing how to get from origins to destinations. Routing engines can be classified based on *where* they run relative to R: - In-memory routing using R packages that enable route calculation (described in Section \@ref(memengine)) - Locally hosted routing engines external to R that can be called from R (Section \@ref(localengine)) - Remotely hosted routing engines by external entities that provide a web API that can be called from R (Section \@ref(remoteengine)) Before describing each, it is worth outlining other ways of categorizing routing engines. Routing engines can be multi-modal, meaning that they can calculate trips composed of more than one mode of transport, or not. Multi-modal routing engines can return results consisting of multiple *legs*, each one made by a different mode of transport. The optimal route from a residential area to a commercial area could involve (1) walking to the nearest bus stop, (2) catching the bus to the nearest node to the destination, and (3) walking to the destination, given a set of input parameters. The transition points between these three legs are commonly referred to as 'ingress' and 'egress', meaning getting on/off a public transport vehicle. Multi-modal routing engines such as R5 are more sophisticated and have larger input data requirements than 'uni-modal' routing engines such as the OpenStreetMap Routing Machine (OSRM), described in Section \@ref(localengine). A major strength of multi-modal engines is their ability to represent 'transit' (public transport) trips by trains, buses, etc. Multi-model routing engines require input datasets representing public transport networks, typically in General Transit Feed Specification ([GTFS](https://developers.google.com/transit/gtfs)) files, which can be processed with functions in the [**tidytransit**](https://r-transit.github.io/tidytransit/index.html) and [**gtfstools**](https://ipeagit.github.io/gtfstools/) packages (other packages and tools for working with GTFS files are available). Single mode routing engines may be sufficient for projects focused on specific (non-public) modes of transport. Another way of classifying routing engines (or settings) is by the geographic level of the outputs: routes, legs and segments. ### Routes, legs and segments {#route-legs-segments} Routing engines can generate outputs at three geographic levels of routes, legs and segments: - **Route** level outputs contain a single feature (typically a multilinestring and associated row in the data frame representation) per origin-destination pair, meaning a single row of data per trip - **Leg** level outputs contain a single feature and associated attributes each *mode* within each origin-destination (OD) pair, as described in Section \@ref(nodes). For trips only involving one mode (for example driving, from home to work, ignoring the short walk to the car), the leg is the same as the route: the car journey. For trips involving public transport, legs provide key information. The **r5r** function `detailed_itineraries()` returns legs which, confusingly, are sometimes referred to as 'segments' - Segment-level outputs provide the most detailed information about routes, with records for each small section of the transport network. Typically segments are similar in length, or identical to, ways in OpenStreetMap. The **cyclestreets** function `journey()` returns data at the segment-level which can be aggregated by grouping by origin- and destination-level data returned by the `route()` function in **stplanr** Most routing engines return route-level by default, although multi-modal engines generally provide outputs at the leg level (one feature per continuous movement by a single mode of transport). Segment-level outputs have the advantage of providing more detail. The **cyclestreets** package returns multiple 'quietness' levels per route, enabling identification of the 'weakest link' in cycle networks. Disadvantages of segment-level outputs include increased file sizes and complexities associated with the extra detail. Route-level results can be converted into segment-level results using the function `stplanr::overline()` [@morgan_travel_2020]. When working with segment- or leg-level data, route-level statistics can be returned by grouping by columns representing trip start and end points and summarizing/aggregating columns containing segment-level data. ### In-memory routing with R {#memengine} Routing engines in R enable route networks stored as R objects *in memory* to be used as the basis of route calculation. Options include [**sfnetworks**](https://luukvdmeer.github.io/sfnetworks/)\index{sfnetworks (package)}, [**dodgr**](https://urbananalyst.github.io/dodgr/) and [**cppRouting**](https://github.com/vlarmet/cppRouting) packages, each of which provide its own class system to represent route networks, the topic of the next section. While fast and flexible, native R routing options are generally harder to set up than dedicated routing engines for realistic route calculation. Routing is a hard problem and many hundreds of hours have been put into open source routing engines that can be downloaded and hosted locally. On the other hand, R-based routing engines may be well suited to model experiments and the statistical analysis of the impacts of changes on the network. Changing route network characteristics (or weights associated with different route segment types), recalculating routes, and analyzing results under many scenarios in a single language have benefits for research applications. ### Locally hosted dedicated routing engines {#localengine} **Locally hosted** routing engines include OpenTripPlanner, [Valhalla](https://github.com/valhalla/valhalla), and R5 (which are multi-modal), and the OpenStreetMap Routing Machine (OSRM) (which is 'uni-modal'). These can be accessed from R with the packages **opentripplanner**, [**valhallr**](https://github.com/chris31415926535/valhallr), **r5r** and [**osrm**](https://github.com/riatelab/osrm) [@morgan_opentripplanner_2019; @pereira_r5r_2021]. Locally hosted routing engines run on the user's computer but in a process separate from R. They benefit from speed of execution and control over the weighting profile for different modes of transport. Disadvantages include the difficulty of representing complex networks locally, lack of predefined routing profiles, temporal dynamics (e.g. traffic), and the need to install specialized software. ### Remotely hosted dedicated routing engines {#remoteengine} **Remotely hosted**\index{routing} routing engines use a web API\index{API} to send queries about origins and destinations and return results. Routing services based on open source routing engines, such as OSRM's publicly available service, work the same when called from R as locally hosted instances, simply requiring arguments specifying 'base URLs' to be updated. However, the fact that external routing services are hosted on a dedicated machine (usually funded by a commercial company with incentives to generate accurate routes) can give them advantages, including: - Provision of routing services worldwide (or usually at least over a large region) - Established routing services are usually updated regularly and can often respond to traffic levels - Routing services usually run on dedicated hardware and software including systems such as load balancers to ensure consistent performance Disadvantages of remote routing services include speed when batch jobs are not possible (they often rely on data transfer over the internet on a route-by-route basis), price (the Google routing API, for example, limits the number of free queries) and licensing issues. [**googleway**](http://symbolixau.github.io/googleway/) and [**mapbox**](https://walker-data.com/mapboxapi/articles/navigation.html) packages demonstrate this approach by providing access to routing services from Google and Mapbox, respectively\index{API}. Free (but rate limited) routing service include [OSRM](http://project-osrm.org/) and [openrouteservice.org](https://openrouteservice.org/) which can be accessed from R with the [**osrm**](https://rgeomatic.hypotheses.org/category/osrm) and [**openrouteservice**](https://github.com/GIScience/openrouteservice-r) packages, the latter of which is not on CRAN. There are also more specific routing services such as that provided by [CycleStreets.net](https://www.cyclestreets.net/), a cycle journey planner and not-for-profit transport technology company "for cyclists, by cyclists". While R users can access CycleStreets routes via the package [**cyclestreets**](https://rpackage.cyclestreets.net/), many routing services lack R interfaces, representing a substantial opportunity for package development: building an R package to provide an interface to a web API can be a rewarding experience. ### Contraction hierarchies and traffic assigment Contraction hierarchies and traffic assignment are advanced but important topics in transport modeling that are worth being aware of, especially if you want your code to scale to large networks. Calculating many routes is computationally resource intensive and can take hours, leading to the development of several algorithms to speed up routing calculations. **Contraction hierarchies** is a well-known algorithm that can lead to a substantial (1000x+ in some cases) speed up in routing tasks, depending on network size. Contraction hierarchies are used behind the scenes in the routing engines mentioned in the previous sections. Traffic assignment is a problem that is closely related to routing: in practice, the shortest path between two points is not always the fastest, especially if there is congestion. The process takes OD datasets, of the kind described in Section \@ref(desire-lines), and assigns traffic to each segment in the network, generating route networks of the kind described in Section \@ref(route-networks). An established solution is Wardrop’s principle of user equilibrium which shows that, to be realistic, congestion should be considered when estimating flows on the network, with reference to a mathematically defined relationship between cost and flow [@wardrop_theoretical_1952]. This optimization problem can be solved by iterative algorithms which are implemented in the [**cppRouting**](https://github.com/vlarmet/cppRouting) package, which also implements contraction hierarchies for fast routing. ### Routing: A worked example Instead of routing\index{routing} *all* desire lines generated in Section \@ref(desire-lines), we focus on a subset that is highly policy-relevant. Running a computationally intensive operation on a subset before trying to process the whole dataset is often sensible, and this applies to routing. Routing can be time- and memory-consuming, resulting in large objects, due to the detailed geometries and extra attributes of route objects. We will therefore filter the desire lines before calculating routes in this section. Cycling is most beneficial when it replaces car trips. Short trips (around 5 km, which can be cycled in 15 minutes at a speed of 20 km/hr) have a relatively high probability of being cycled, and the maximum distance increases when trips are made by electric bike [@lovelace_propensity_2017]. These considerations inform the following code chunk which filters the desire lines and returns the object `desire_lines_short` representing OD pairs between which many (100+) short (2.5 to 5 km Euclidean distance) trips are driven: ```{r 13-transport-17, message=FALSE} desire_lines$distance_km = as.numeric(st_length(desire_lines)) / 1000 desire_lines_short = desire_lines |> filter(car_driver >= 100, distance_km <= 5, distance_km >= 2.5) ``` In the code above, `st_length()` calculated the length of each desire line, as described in Section \@ref(distance-relations). The `filter()` function from **dplyr** filtered the `desire_lines` dataset based on the criteria outlined above\index{filter operation|see{attribute!subsetting}}, as described in Section \@ref(vector-attribute-subsetting). The next stage is to convert these desire lines into routes. This is done using the publicly available OSRM service with the **stplanr** functions `route()` and `route_osrm()`\index{stplanr (package)} in the code chunk below: ```{r 13-transport-18, message=FALSE} routes_short = route(l = desire_lines_short, route_fun = route_osrm, osrm.profile = "car") ``` The output is `routes_short`, an `sf` object representing routes on the transport network\index{network} that are suitable for cycling (according to the OSRM routing engine at least), one for each desire line. Note: calls to external routing engines such as in the command above only work with an internet connection (and sometimes an API key stored in an environment variable, although not in this case). In addition to the columns contained in the `desire_lines` object, the new route dataset contains `distance` (referring to route distance this time) and `duration` columns (in seconds), which provide potentially useful extra information on the nature of each route. We will plot desire lines along which many short car journeys take place alongside cycling routes. Making the width of the routes proportional to the number of car journeys that could potentially be replaced provides an effective way to prioritize interventions on the road network [@lovelace_propensity_2017]. Figure \@ref(fig:routes) shows routes along which people drive short distances (see the github.com/geocompx for the source code).[^13-transport-8] [^13-transport-8]: Note that the red routes and black desire lines do not start at exactly the same points. This is because zone centroids rarely lie on the route network; instead the routes originate from the transport network node nearest the centroid. Note also that routes are assumed to originate in the zone centroids, a simplifying assumption which is used in transport models to reduce the computational resources needed to calculate the shortest path between all combinations of possible origins and destinations [@hollander_transport_2016]. ```{r routes, warning=FALSE, fig.cap="Routes along which many (100+) short (<5km Euclidean distance) car journeys are made (red) overlaying desire lines representing the same trips (black) and zone centroids (dots).", fig.scap="Routes along which many car journeys are made.", echo=FALSE} # TODO (low priority, RL): add a facetted plot showing network cleaning/overline functions # TODO (low priority, RL, 2022-10): add points representing Bradley Stoke and Bristol to the map # waldo::compare( # sf::st_crs(desire_lines_short), # sf::st_crs(routes_short) # ) routes_plot_data = rbind( desire_lines_short |> transmute(Entity = "Desire lines") |> sf::st_set_crs("EPSG:4326"), routes_short |> transmute(Entity = "Routes") |> sf::st_set_crs("EPSG:4326") ) zone_cents_routes = zone_cents[desire_lines_short, ] tm_shape(zones_od) + tm_fill(fill_alpha = 0.2, lwd = 0.1) + tm_shape(desire_lines_short, is.main = TRUE) + tm_lines(lty = 2) + tm_shape(routes_short) + tm_lines(col = "red") + tm_add_legend(title = "Entity", labels = c("Desire lines", "Routes"), type = "lines", col = c("black", "red"), lty = c(2, 1), position = tm_pos_in("left", "top")) + tm_shape(zone_cents_routes) + tm_symbols(fill = "black", size = 0.5) + tm_scalebar() ``` Visualizing the results in an interactive map shows that many short car trips take place in and around Bradley Stoke, around 10 km North of central Bristol. It is easy to find explanations for the area's high level of car-dependency: according to [Wikipedia](https://en.wikipedia.org/wiki/Bradley_Stoke), Bradley Stoke is "Europe's largest new town built with private investment", suggesting limited public transport provision. Furthermore, the town is surrounded by large (cycling unfriendly) road structures, including the M4 and M5 motorways [@tallon_bristol_2007]. There are many benefits of converting travel desire lines\index{desire lines} into routes. It is important to remember that we cannot be sure how many (if any) trips will follow the exact routes calculated by routing engines. However, route and street/way/segment-level results can be highly policy-relevant. Route segment results can enable the prioritization of investment where it is most needed, according to available data [@lovelace_propensity_2017]. ## Route networks \index{network} While routes generally contain data on travel *behavior*, at the same geographic level as desire lines and OD pairs, route network datasets usually represent the physical transport network. Each *segment* in a route network roughly corresponds to a continuous section of street between junctions and appears only once, although the average length of segments depends on the data source (segments in the OSM-derived `bristol_ways` dataset used in this section have an average length of just over 200 m, with a standard deviation of nearly 500 m). Variability in segment lengths can be explained by the fact that in some rural locations junctions are far apart, while in dense urban areas there are crossings and other segment breaks every few meters. Route networks can be an input into, or an output of, transport data analysis projects, or both. Any transport research that involves route calculation requires a route network dataset in the internal or external routing engines (in the latter case the route network data is not necessarily imported into R). However, route networks are also important outputs in many transport research projects: summarizing data such as the potential number of trips made on particular segments and represented as a route network, can help prioritize investment where it is most needed. \index{network} To demonstrate how to create route networks as an output derived from route-level data, imagine a simple scenario of mode shift. Imagine that 50% of car trips between 0 to 3 km in route distance are replaced by cycling, a percentage that drops by 10 percentage points for every additional kilometer of route distance so that 20% of car trips of 6 km are replaced by cycling and no car trips that are eight km or longer are replaced by cycling. This is of course an unrealistic scenario [@lovelace_propensity_2017], but is a useful starting point. In this case, we can model mode shift from cars to bikes as follows: ```{r uptakefun} uptake = function(x) { case_when( x <= 3 ~ 0.5, x >= 8 ~ 0, TRUE ~ (8 - x) / (8 - 3) * 0.5 ) } routes_short_scenario = routes_short |> mutate(uptake = uptake(distance / 1000)) |> mutate(bicycle = bicycle + car_driver * uptake, car_driver = car_driver * (1 - uptake)) sum(routes_short_scenario$bicycle) - sum(routes_short$bicycle) ``` Having created a scenario in which approximately 4000 trips have switched from driving to cycling, we can now model where this updated modeled cycling activity will take place. For this, we will use the function `overline()` from the **stplanr** package. The function breaks linestrings at junctions (where two or more linestring geometries meet), and calculates aggregate statistics for each unique route segment [@morgan_travel_2020], taking an object containing routes and the names of the attributes to summarize as the first and second argument: ```{r rnet1} route_network_scenario = overline(routes_short_scenario, attrib = "bicycle") ``` The outputs of the two preceding code chunks are summarized in Figure \@ref(fig:rnetvis) below. ```{r rnetvis, out.width="49%", fig.show='hold', fig.cap="The percentage of car trips switching to cycling as a function of distance (left) and route network level results of this function (right).", echo=FALSE, fig.height=9.5} routes_short_scenario |> ggplot() + geom_line(aes(distance / 1000, uptake), color = "red", linewidth = 3) + labs(x = "Route distance (km)", y = NULL, title = "Percent trips switching from driving to cycling") + scale_y_continuous(labels = scales::percent) tm_shape(zones_od) + tm_fill(fill_alpha = 0.2, lwd = 0.1) + tm_shape(route_network_scenario, is.main = TRUE) + tm_lines(lwd = "bicycle", lwd.scale = tm_scale(values.scale = 1.5), lwd.legend = tm_legend(title = "Number of bike trips per day\n(modeled, one direction)", position = tm_pos_in("left", "top")), col = "red") ``` Transport networks with records at the segment-level, typically with attributes such as road type and width, constitute a common type of route network. Such route network datasets are available worldwide from OpenStreetMap, and can be downloaded with packages such as **osmdata**\index{osmdata (package)} and **osmextract**\index{osmextract (package)}. To save time downloading and preparing OSM\index{OpenStreetMap}, we will use the `bristol_ways` object from the **spDataLarge** package, an `sf` object with LINESTRING geometries and attributes representing a sample of the transport network in the case study region (see `?bristol_ways` for details), as shown in the output below: ```{r 13-transport-22} summary(bristol_ways) ``` The output shows that `bristol_ways` represents just over 6,000 segments on the transport network\index{network}. This and other geographic networks can be represented as mathematical graphs\index{graph}, with nodes\index{node} on the network, connected by edges\index{edge}. A number of R packages have been developed for dealing with such graphs, notably **igraph**\index{igraph (package)}. You can manually convert a route network into an `igraph` object, but the geographic attributes will be lost. To overcome this limitation of **igraph**, the **sfnetworks**\index{sfnetworks (package)} package was developed [@R-sfnetworks]. It represents networks simultaneously as graphs *and* geographic lines and has a 'tidy' syntax, as demonstrated in the following example. ```{r 13-transport-23} bristol_ways$lengths = st_length(bristol_ways) ways_sfn = as_sfnetwork(bristol_ways) class(ways_sfn) ``` ```{r 13-transport-23-2, eval=FALSE} ways_sfn #> # A sfnetwork with 5728 nodes and 4915 edges #> # A directed multigraph with 1013 components with spatially explicit edges #> # Node Data: 5,728 × 1 (active) #> # Edge Data: 4,915 × 7 #> from to highway maxspeed ref geometry lengths #> [m] #> 1 1 2 road B3130 (-2.61 51.4, -2.61 51.4, -2.61 51.… 218. #> # … ``` The output of the previous code chunk (with the final output shortened to contain only the most important eight lines due to space considerations) shows that `ways_sfn` is a composite object, containing both nodes and edges in graph and spatial form. `ways_sfn` is of class `sfnetwork`, which builds on the `igraph` class from the **igraph** package. In the example below, the 'edge betweenness'\index{edge}, meaning the number of shortest paths\index{shortest route} passing through each edge, is calculated (see `?igraph::betweenness` for further details). The output of the edge betweenness calculation is shown in Figure \@ref(fig:wayssln), which has the cycle route network dataset calculated with the `overline()` function as an overlay for comparison. The results demonstrate that each graph edge represents a segment: the segments near the center of the road network have the highest betweenness values, whereas segments closer to central Bristol have higher cycling potential, based on these simplistic datasets. ```{r wayssln-gen} ways_centrality = ways_sfn |> activate("edges") |> mutate(betweenness = tidygraph::centrality_edge_betweenness(lengths)) ``` ```{r wayssln, fig.cap="Route network datasets. The gray lines represent a simplified road network, with segment thickness proportional to betweenness. The green lines represent potential cycling flows (one way) calculated with the code above.", fig.scap="A small route network.", echo=FALSE} bb_wayssln = tmaptools::bb(route_network_scenario, xlim = c(0.1, 0.9), ylim = c(0.1, 0.6), relative = TRUE) tm_shape(zones_od) + tm_fill(fill_alpha = 0.2, lwd = 0.1) + tm_shape(ways_centrality |> st_as_sf(), bb = bb_wayssln, is.main = TRUE) + tm_lines(lwd = "betweenness", lwd.scale = tm_scale(n = 2, values.scale = 2), lwd.legend = tm_legend(title = "Betweenness"), col = "#630032", col_alpha = 0.75) + tm_shape(route_network_scenario) + tm_lines(lwd = "bicycle", lwd.scale = tm_scale(n = 2, values.scale = 2), lwd.legend = tm_legend(title = "Number of bike trips (modeled, one direction)"), col = "darkgreen", col_alpha = 0.75) + tm_scalebar() ``` ```{r 13-transport-24, eval=FALSE, echo=FALSE} # not producing groups of routes so removing for now... # m = igraph::clusters(ways_sfn@g) # igraph::V(ways_sfn@g)$m = m$membership # gdf = igraph::as_long_data_frame(ways_sfn@g) ``` One can also find the shortest route\index{shortest route} between origins and destinations using this graph representation of the route network with the **sfnetworks** package. The methods presented in this section are relatively simple compared with what is possible. The possibilities opened-up by **sfnetworks** cannot be fully covered in this section, but it does provide a strong starting point for further exploration and research into the area. A final point is that the example dataset we used above is relatively small. It may also be worth considering how the work could adapt to larger networks: testing methods on a subset of the data, and ensuring you have enough RAM will help, although it's also worth exploring other tools that can do transport network analysis that are optimized for large networks, such as R5 [@alessandretti_multimodal_2022]. ## Prioritizing new infrastructure This section demonstrates how geocomputation can create policy-relevant outcomes in the field of transport planning. We will identify promising locations for investment in sustainable transport infrastructure, using a simple approach for educational purposes. An advantage of the data-driven approach outlined in this chapter is its modularity: each aspect can be useful on its own, and feed into wider analyses. The steps that got us to this stage included identifying short but car-dependent commuting routes (generated from desire lines) in Section \@ref(routes) and analysis of route network characteristics with the **sfnetworks** package in Section \@ref(route-networks). The final code chunk of this chapter combines these strands of analysis, by overlaying estimates of cycling potential from the previous section on top of a new dataset representing areas within a short distance of cycling infrastructure. This new dataset is created in the code chunk below which: (1) filters out the cycleway entities from the `bristol_ways` object representing the transport network,(2) 'unions' the individual LINESTRING entities of the cycleways into a single multilinestring object (for speed of buffering),and (3) creates a 100 m buffer around them to create a polygon. ```{r 13-transport-25} existing_cycleways_buffer = bristol_ways |> filter(highway == "cycleway") |> # 1) filter out cycleways st_union() |> # 2) unite geometries st_buffer(dist = 100) # 3) create buffer ``` The next stage is to create a dataset representing points on the network where there is high cycling potential but little provision for cycling. ```{r, echo=FALSE, eval=FALSE} waldo::compare( sf::st_crs(route_network_scenario), sf::st_crs(existing_cycleways_buffer) ) ``` ```{r 13-transport-26, eval=FALSE} route_network_no_infra = st_difference( route_network_scenario, route_network_scenario |> st_set_crs(st_crs(existing_cycleways_buffer)), existing_cycleways_buffer ) ``` ```{r 13-transport-26-workaround, echo=FALSE} # TODO: remove this hidden chunk when rocker project updates PROJ version route_network_no_infra = st_difference( # route_network_scenario, # Temporary workaround, see https://github.com/geocompx/geocompr/issues/863: route_network_scenario |> st_set_crs(st_crs(existing_cycleways_buffer)), existing_cycleways_buffer ) ``` The results of the preceding code chunks are shown in Figure \@ref(fig:cycleways), which shows routes with high levels of car-dependency and high cycling potential but no cycleways. ```{r 13-transport-28, eval=FALSE} tmap_mode("view") qtm(route_network_no_infra, basemaps = leaflet::providers$Esri.WorldTopoMap, lines.lwd = 5) ``` ```{r cycleways, echo=FALSE, message=FALSE, fig.cap="Potential routes along which to prioritize cycle infrastructure in Bristol to reduce car-dependency. The static map provides an overview of the overlay between existing infrastructure and routes with high car-bike switching potential (left). The screenshot the interactive map generated from the `qtm()` function highlights Whiteladies Road as somewhere that would benefit from a new cycleway (right).", out.width="50%", fig.show='hold', fig.scap="Routes along which to prioritize cycle infrastructure.", fig.height=9} # Previous verson: # source("code/13-cycleways.R") tm_shape(existing_cycleways_buffer, bbox = bristol_region) + tm_polygons(fill = "lightgreen") + tm_shape(route_network_scenario) + tm_lines(lwd = "bicycle", lwd.scale = tm_scale(values.scale = 3), lwd.legend = tm_legend(title = "Number of bike trips (modeled, one direction)"), position = tm_pos_out("center", "bottom")) knitr::include_graphics("images/bristol_cycleways_zoomed.png") ``` The method has some limitations: in reality, people do not travel to zone centroids or always use the shortest route\index{shortest route} algorithm for a particular mode. However, the results demonstrate how geographic data analysis can be used to highlight places where new investment in cycleways could be particularly beneficial, despite the simplicity of the approach. The analysis would need to be substantially expanded --- including with larger input datasets --- to inform transport planning design in practice. ## Future directions of travel This chapter provided a taste of the possibilities of using geocomputation for transport research, and it explored some key geographic elements that make-up a city's transport system with open data and reproducible code. The results could help plan where investment is needed. Transport systems operate at multiple interacting levels, meaning that geocomputational methods have great potential to generate insights into how they work, and the likely impacts of different interventions. There is much more that could be done in this area: it would be possible to build on the foundations presented in this chapter in many directions. Transport is the fastest growing source of greenhouse gas emissions in many countries, and it is set to become "the largest GHG emitting sector, especially in developed countries" (see [EURACTIV.com](https://www.euractiv.com/section/agriculture-food/opinion/transport-needs-to-do-a-lot-more-to-fight-climate-change/)). Transport-related emissions are unequally distributed across society but (unlike food and heating) are not essential for well-being. There is great potential for the sector to rapidly decarbonize through demand reduction, electrification of the vehicle fleet and the uptake of active travel modes such as walking and cycling. New technologies can reduce car-dependency by enabling more car sharing. 'Micro-mobility' systems such as dockless bike and e-scooter schemes are also emerging, creating valuable datasets in the General Bikeshare Feed Specification (GBFS) format, which can be imported and processed with the [**gbfs**](https://github.com/simonpcouch/gbfs) package. These and other changes will have large impacts on accessibility, the ability of people to reach employment and service locations that they need, something that can be quantified currently and under scenarios of change with packages such as [**accessibility**](https://ipeagit.github.io/accessibility/) packages. Further exploration of such 'transport futures' at local, regional and national levels could yield important new insights. Methodologically, the foundations presented in this chapter could be extended by including more variables in the analysis. Characteristics of the route such as speed limits, busyness and the provision of protected cycling and walking paths could be linked to 'mode-split' (the proportion of trips made by different modes of transport). By aggregating OpenStreetMap\index{OpenStreetMap} data using buffers and geographic data methods presented in Chapters \@ref(attr) and \@ref(spatial-operations), for example, it would be possible to detect the presence of green space in close proximity to transport routes. Using R's\index{R} statistical modeling capabilities, this could then be used to predict current and future levels of cycling, for example. This type of analysis underlies the Propensity to Cycle Tool (PCT), a publicly accessible (see [www.pct.bike](https://www.pct.bike/)) mapping tool developed in R\index{R} that is being used to prioritize investment in cycling across England [@lovelace_propensity_2017]. Similar tools could be used to encourage evidence-based transport policies related to other topics such as air pollution and public transport access around the world. ## Exercises {#ex-transport} ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_13-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 14-location.Rmd ================================================ # Geomarketing {#location} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} - This chapter requires the following packages (**tmaptools** must also be installed): ```{r 14-location-1, message=FALSE} library(sf) library(dplyr) library(purrr) library(terra) library(osmdata) library(spDataLarge) library(z22) ``` As a convenience to the reader and to ensure easy reproducibility, we have made available the downloaded data in the **spDataLarge** package. ## Introduction This chapter demonstrates how the skills learned in Parts I and II can be applied to a particular domain: geomarketing\index{geomarketing} (sometimes also referred to as location analysis\index{location analysis} or location intelligence). This is a broad field of research and commercial application. A typical example of geomarketing is where to locate a new shop. The aim here is to attract most visitors and, ultimately, make the most profit. There are also many non-commercial applications that can use the technique for public benefit, for example, where to locate new health services [@tomintz_geography_2008]. People are fundamental to location analysis\index{location analysis}, in particular where they are likely to spend their time and other resources. Interestingly, ecological concepts and models are quite similar to those used for store location analysis. Animals and plants can best meet their needs in certain 'optimal' locations, based on variables that change over space (@muenchow_review_2018; see also Chapter \@ref(eco)). This is one of the great strengths of geocomputation and GIScience in general: concepts and methods are transferable to other fields. Polar bears, for example, prefer northern latitudes where temperatures are lower and food (seals and sea lions) is plentiful. Similarly, humans tend to congregate in certain places, creating economic niches (and high land prices) analogous to the ecological niche of the Arctic. The main task of location analysis is to find out, based on available data, where such 'optimal locations' are for specific services. Typical research questions include: - Where do target groups live and which areas do they frequent? - Where are competing stores or services located? - How many people can easily reach specific stores? - Do existing services over- or under-utilize the market potential? - What is the market share of a company in a specific area? This chapter demonstrates how geocomputation can answer such questions based on a hypothetical case study and real data. ## Case study: bike shops in Germany {#case-study} Imagine you are starting a chain of bike shops in Germany. The stores should be placed in urban areas with as many potential customers as possible. Additionally, a hypothetical survey (invented for this chapter, not for commercial use!) suggests that single young males (aged 20 to 40) are most likely to buy your products: this is the *target audience*. You are in the lucky position to have sufficient capital to open a number of shops. But where should they be placed? Consulting companies (employing geomarketing\index{geomarketing} analysts) would happily charge high rates to answer such questions. Luckily, we can do so ourselves with the help of open data\index{open data} and open source software\index{open source software}. The following sections will demonstrate how the techniques learned during the first chapters of the book can be applied to undertake common steps in service location analysis: - Tidy the input data from the German census (Section \@ref(tidy-the-input-data)) - Convert the tabulated census data into raster\index{raster} objects (Section \@ref(create-census-rasters)) - Identify metropolitan areas with high population densities (Section \@ref(define-metropolitan-areas)) - Download detailed geographic data (from OpenStreetMap\index{OpenStreetMap}, with **osmdata**\index{osmdata (package)}) for these areas (Section \@ref(points-of-interest)) - Create rasters\index{raster} for scoring the relative desirability of different locations using map algebra\index{map algebra} (Section \@ref(create-census-rasters)) Although we have applied these steps to a specific case study, they could be generalized to many scenarios of store location or public service provision. ## Tidy the input data The German government provides gridded census data at either 1 km or 100 m resolution. The **z22** package [@R-z22] provides convenient access to this data. We load four variables at 1 km resolution: population count, mean age, household size, and proportion of women. ```{r 14-location-2} # Load Census 2022 data using z22 package pop = z22::z22_data("population", res = "1km", year = 2022, as = "df") |> rename(pop = cat_0) # Women data only available from Census 2011 women = z22::z22_data("women", year = 2011, res = "1km", as = "df") |> rename(women = cat_0) mean_age = z22::z22_data("age_avg", res = "1km", year = 2022, as = "df") |> rename(mean_age = cat_0) hh_size = z22::z22_data("household_size_avg", res = "1km", year = 2022, as = "df") |> rename(hh_size = cat_0) census_de = pop |> left_join(women, by = c("x", "y")) |> left_join(mean_age, by = c("x", "y")) |> left_join(hh_size, by = c("x", "y")) |> relocate(pop, .after = y) ``` Note that the proportion of women is not available in Census 2022, so we use data from Census 2011 for this variable. Unlike Census 2011 which provided data as categorical class codes, Census 2022 provides actual continuous values: exact population counts, mean age in years, and average household size as decimals. The `women` variable from Census 2011 contains the percentage of female inhabitants. Please note that `census_de` is also available from the **spDataLarge** package: ```{r attach-census, eval=TRUE} data("census_de", package = "spDataLarge") ``` The next step recodes unknown values to `NA`. The `women` variable from Census 2011 uses `-1` and `-9` for unknown values; recoding these to `NA` ensures proper handling in raster computations and plotting. ```{r 14-location-4} # Recode unknown values (-1, -9) to NA input_tidy = census_de |> mutate(across(c(pop, women, mean_age, hh_size), ~ifelse(.x < 0, NA, .x))) ``` ```{r census-desc, echo=FALSE} tab = dplyr::tribble( ~"class", ~"pop", ~"women", ~"age", ~"hh", 1, "0-250", "0-40%", "0-40", "1-1.5", 2, "250-500", "40-47%", "40-42", "1.5-2", 3, "500-2000", "47-53%", "42-44", "2-2.5", 4, "2000-4000", "53-60%", "44-47", "2.5-3", 5, "4000-8000", ">60%", ">47", ">3", 6, ">8000", "", "", "" ) cap = paste("Classification of census variables for Figure \\@ref(fig:census-stack).", "Class values for population represent inhabitant counts per grid cell.", "For the demographic variables (women, mean age, household size),", "classes 1-5 correspond to weights 3-0 used in the location analysis.") knitr::kable(tab, col.names = c("Class", "Population", "Women (%)", "Mean age", "Household size"), caption = cap, caption.short = "Classification of census variables.", align = "clcccc", booktabs = TRUE) ``` ## Create census rasters After the preprocessing, the data can be converted into a `SpatRaster` object (see Sections \@ref(raster-classes) and \@ref(raster-subsetting)) with the help of the `rast()` function. When setting its `type` argument to `xyz`, the `x` and `y` columns of the input data frame should correspond to coordinates on a regular grid. All the remaining columns (here: `pop`, `women`, `mean_age`, `hh_size`) will serve as values of the raster layers (Figure \@ref(fig:census-stack); see also `code/14-location-figures.R` in our GitHub repository). Note that column order matters: the first two columns must be x and y coordinates, so use `select()` or `relocate()` to reorder columns if needed. ```{r 14-location-5, cache.lazy=FALSE} input_ras = rast(input_tidy, type = "xyz", crs = "EPSG:3035") ``` ```{r 14-location-6} input_ras ``` ```{block2 14-location-7, type='rmdnote'} Note that we are using an equal-area projection (EPSG:3035; Lambert Equal Area Europe), i.e., a projected CRS\index{CRS!projected} where each grid cell has the same area, here 1000 * 1000 square meters. Since we are using mainly densities such as the number of inhabitants or the portion of women per grid cell, it is of utmost importance that the area of each grid cell is the same to avoid 'comparing apples and oranges'. Be careful with geographic CRS\index{CRS!geographic} where grid cell areas constantly decrease in poleward directions (see also Section \@ref(crs-intro) and Chapter \@ref(reproj-geo-data)). ``` ```{r census-stack, echo=FALSE, fig.cap="Gridded German census data of 2022 (women data from 2011). See Table 14.1 for reclassification thresholds.", fig.scap="Gridded German census data."} knitr::include_graphics("images/14_census_stack.png") ``` The next stage is to reclassify the demographic variables (women, mean age, household size) into weights in accordance with the survey mentioned in Section \@ref(case-study), using the **terra** function `classify()`, which was introduced in Section \@ref(local-operations)\index{map algebra!local operations}. Since Census 2022 provides actual continuous values rather than class codes, we can use the population counts directly without conversion. This gives us more precise data for delineating metropolitan areas (see Section \@ref(define-metropolitan-areas)). For the remaining demographic variables, we reclassify continuous values into weights (0-3) based on our target audience criteria (see Table \@ref(tab:census-desc)). Areas with 0-40% female population receive weight 3 because the target demographic is predominantly male. Similarly, areas with younger mean age and smaller household sizes receive higher weights. ```{r 14-location-8} # Reclassification matrices for continuous values (from, to, weight) # Women: percentage of female inhabitants rcl_women = matrix(c( 0, 40, 3, # 0-40% female -> weight 3 40, 47, 2, # 40-47% -> weight 2 47, 53, 1, # 47-53% -> weight 1 53, 60, 0, # 53-60% -> weight 0 60, 100, 0 # >60% -> weight 0 ), ncol = 3, byrow = TRUE) # Mean age: continuous years rcl_age = matrix(c( 0, 40, 3, # Mean age <40 -> weight 3 40, 42, 2, # 40-42 -> weight 2 42, 44, 1, # 42-44 -> weight 1 44, 47, 0, # 44-47 -> weight 0 47, 120, 0 # >47 -> weight 0 ), ncol = 3, byrow = TRUE) # Household size: average persons per household rcl_hh = matrix(c( 0, 1.5, 3, # 1-1.5 persons -> weight 3 1.5, 2.0, 2, # 1.5-2 -> weight 2 2.0, 2.5, 1, # 2-2.5 -> weight 1 2.5, 3.0, 0, # 2.5-3 -> weight 0 3.0, 100, 0 # >3 -> weight 0 ), ncol = 3, byrow = TRUE) rcl = list(rcl_women, rcl_age, rcl_hh) ``` Note that we only reclassify women, mean age, and household size into weights --- not population. The `for`-loop\index{loop!for} applies each reclassification matrix to the corresponding raster layer. We keep the population layer separate as actual counts for use in metropolitan area identification. ```{r 14-location-9, cache.lazy=FALSE} # Separate population (used as counts for metro detection) from variables to reclassify pop_ras = input_ras$pop # Reclassify women, mean_age, hh_size into weights demo_vars = c("women", "mean_age", "hh_size") reclass = input_ras[[demo_vars]] for (i in seq_len(nlyr(reclass))) { reclass[[i]] = classify(x = reclass[[i]], rcl = rcl[[i]], right = NA) } names(reclass) = demo_vars ``` ```{r 14-location-10, eval=FALSE} reclass # full output not shown #> ... #> names : women, mean_age, hh_size #> min values : 0, 0, 0 #> max values : 3, 3, 3 ``` ## Define metropolitan areas We deliberately define metropolitan areas as pixels of 20 km^2^ inhabited by more than 500,000 people. Pixels at this coarse resolution can rapidly be created using `aggregate()`\index{aggregation}, as introduced in Section \@ref(aggregation-and-disaggregation). The command below uses the argument `fact = 20` to reduce the resolution of the result 20-fold (recall the original raster resolution was 1 km^2^). Since we have actual population counts from Census 2022 (rather than class-midpoint estimates), we can aggregate them directly. ```{r 14-location-11, warning=FALSE, cache=TRUE, cache.lazy=FALSE} pop_agg = aggregate(pop_ras, fact = 20, fun = sum, na.rm = TRUE) summary(pop_agg) ``` The next stage is to keep only cells with more than half a million people. ```{r 14-location-12, warning=FALSE, cache.lazy=FALSE, cache=TRUE} pop_agg = pop_agg[pop_agg > 500000, drop = FALSE] ``` Plotting this reveals several metropolitan regions (Figure \@ref(fig:metro-areas)). Each region consists of one or more raster cells. It would be nice if we could join all cells belonging to one region. **terra**'s\index{terra (package)} `patches()` command does exactly that. Subsequently, `as.polygons()` converts the raster object into spatial polygons, and `st_as_sf()` converts it into an `sf` object. ```{r 14-location-13, warning=FALSE, message=FALSE, cache.lazy=FALSE} metros = pop_agg |> patches(directions = 8) |> as.polygons() |> st_as_sf() ``` ```{r metro-areas, echo=FALSE, out.width= "70%", fig.cap="The aggregated population raster (resolution: 20 km) with the identified metropolitan areas (golden polygons) and the corresponding names.", fig.scap="The aggregated population raster."} knitr::include_graphics("images/14_metro_areas.png") ``` The resulting metropolitan areas suitable for bike shops (Figure \@ref(fig:metro-areas); see also `code/14-location-figures.R` for creating the figure) are still missing a name. A reverse geocoding\index{geocoding} approach can settle this problem: given a coordinate, it finds the corresponding address. Consequently, extracting the centroid\index{centroid} coordinate of each metropolitan area can serve as an input for a reverse geocoding API\index{API}. This is exactly what the `rev_geocode_OSM()` function of the **tmaptools** package expects. Setting additionally `as.data.frame` to `TRUE` will give back a `data.frame` with several columns referring to the location including the street name, house number and city. However, here, we are only interested in the name of the city. ```{r 14-location-17, warning=FALSE, message=FALSE, eval=FALSE} metro_names = sf::st_centroid(metros, of_largest_polygon = TRUE) |> tmaptools::rev_geocode_OSM(as.data.frame = TRUE) |> select(city, town, state) # smaller cities are returned in column town. To have all names in one column, # we move the town name to the city column in case it is NA metro_names = dplyr::mutate(metro_names, city = ifelse(is.na(city), town, city)) ``` To make sure that the reader uses the exact same results, we have put them into **spDataLarge** as the object `metro_names`. ```{r metro-names, echo=FALSE} data("metro_names", package = "spDataLarge") knitr::kable(select(metro_names, City = city, State = state), caption = "Result of the reverse geocoding.", caption.short = "Result of the reverse geocoding.", booktabs = TRUE) ``` Overall, we are satisfied with the `City` column serving as metropolitan names (Table \@ref(tab:metro-names)) apart from two exceptions: Velbert belongs to the greater region of Düsseldorf, and Langenhagen belongs to Hannover. Hence, we replace these names accordingly (Figure \@ref(fig:metro-areas)). Umlauts like `ü` might lead to trouble further on, for example when determining the bounding box of a metropolitan area with `opq()` (see further below), which is why we avoid them. ```{r 14-location-19} metro_names = metro_names$city |> as.character() |> (\(x) ifelse(x == "Velbert", "Düsseldorf", x))() |> (\(x) ifelse(x == "Langenhagen", "Hannover", x))() |> gsub("ü", "ue", x = _) ``` ## Points of interest \index{point of interest} The **osmdata**\index{osmdata (package)} package provides easy-to-use access to OSM\index{OpenStreetMap} data (see also Section \@ref(retrieving-data)). Instead of downloading shops for the whole of Germany, we restrict the query to the defined metropolitan areas, reducing computational load and providing shop locations only in areas of interest. The subsequent code chunk does this using a number of functions including: - `map()`\index{loop!map} (the **tidyverse** equivalent of `lapply()`\index{loop!lapply}), which iterates through all metropolitan names which subsequently define the bounding box\index{bounding box} in the OSM\index{OpenStreetMap} query function `opq()` (see Section \@ref(retrieving-data)) - `add_osm_feature()` to specify OSM\index{OpenStreetMap} elements with a key value of `shop` (see [wiki.openstreetmap.org](https://wiki.openstreetmap.org/wiki/Map_Features) for a list of common key:value pairs) - `osmdata_sf()`, which converts the OSM\index{OpenStreetMap} data into spatial objects (of class `sf`) - `while()`\index{loop!while}, which tries two more times to download the data if the download failed the first time^[The OSM-download will sometimes fail at the first attempt. ] Before running this code, please consider it will download almost two GB of data. To save time and resources, we have put the output named `shops` into **spDataLarge**. To make it available in your environment, run `data("shops", package = "spDataLarge")`. ```{r 14-location-20, eval=FALSE, message=FALSE} shops = purrr::map(metro_names, function(x) { message("Downloading shops of: ", x, "\n") # give the server a bit time Sys.sleep(sample(seq(10, 15, 0.1), 1)) query = osmdata::opq(x) |> osmdata::add_osm_feature(key = "shop") points = osmdata::osmdata_sf(query) # request the same data again if nothing has been downloaded iter = 2 while (nrow(points$osm_points) == 0 && iter > 0) { points = osmdata_sf(query) iter = iter - 1 } # return only the point features points$osm_points }) ``` It is highly unlikely that there are no shops in any of our defined metropolitan areas. The following `if` condition simply checks if there is at least one shop for each region. If not, we recommend to try to download the shops again for this/these specific region/s. ```{r 14-location-21, eval=FALSE} # checking if we have downloaded shops for each metropolitan area ind = purrr::map_dbl(shops, nrow) == 0 if (any(ind)) { message("There are/is still (a) metropolitan area/s without any features:\n", paste(metro_names[ind], collapse = ", "), "\nPlease fix it!") } ``` To make sure that each list element (an `sf`\index{sf} data frame) comes with the same columns^[This is not a given since OSM contributors are not equally meticulous when collecting data.], we only keep the `osm_id` and the `shop` columns with the help of the `map_dfr` loop which additionally combines all shops into one large `sf`\index{sf} object. ```{r 14-location-22, eval=FALSE} # select only specific columns shops = purrr::map_dfr(shops, select, osm_id, shop) ``` Note: `shops` is provided in the `spDataLarge` and can be accessed as follows: ```{r attach-shops} data("shops", package = "spDataLarge") ``` The only thing left to do is to convert the spatial point object into a raster (see Section \@ref(rasterization)). The `sf` object, `shops`, is converted into a raster\index{raster} having the same parameters (dimensions, resolution, CRS\index{CRS}) as the `reclass` object. Importantly, the `length()` function is used here to count the number of shops in each cell. The result of the subsequent code chunk is therefore an estimate of shop density (shops/km^2^). `st_transform()`\index{sf!st\_transform} is used before `rasterize()`\index{raster!rasterize} to ensure the CRS\index{CRS} of both inputs match. ```{r tmmppp21, echo=FALSE, message=FALSE, warning=FALSE} shops = sf::st_transform(shops, st_crs(reclass)) # create point of interest (poi) raster poi = rasterize(x = vect(shops), y = reclass, field = "osm_id", fun = "length") ``` ```{r 14-location-25, message=FALSE, warning=FALSE, eval=FALSE} shops = sf::st_transform(shops, st_crs(reclass)) # create poi raster poi = rasterize(x = shops, y = reclass, field = "osm_id", fun = "length") ``` As with the other raster layers (population, women, mean age, household size) the `poi` raster is reclassified into four classes (see Section \@ref(create-census-rasters)). Defining class intervals is an arbitrary undertaking to a certain degree. One can use equal breaks, quantile breaks, fixed values or others. Here, we choose the Fisher-Jenks natural breaks approach which minimizes within-class variance, the result of which provides an input for the reclassification matrix. ```{r 14-location-26, message=FALSE, warning=FALSE, cache.lazy=FALSE} # construct reclassification matrix int = classInt::classIntervals(values(poi), n = 4, style = "fisher") int = round(int$brks) rcl_poi = matrix(c(int[1], rep(int[-c(1, length(int))], each = 2), int[length(int)] + 1), ncol = 2, byrow = TRUE) rcl_poi = cbind(rcl_poi, 0:3) # reclassify poi = classify(poi, rcl = rcl_poi, right = NA) names(poi) = "poi" ``` ## Identify suitable locations The only step that remains before combining all the layers is to add `poi` to the `reclass` raster stack. Note that we have already kept population separate from the demographic weights, as it was used only for delineating metropolitan areas. The reasoning is: First, we have already identified areas where the population density is above average compared to the rest of Germany. Second, though it is advantageous to have many potential customers within a specific catchment area\index{catchment area}, the sheer number alone might not actually represent the desired target group. For instance, residential tower blocks are areas with a high population density but not necessarily with a high purchasing power for expensive cycle components. ```{r 14-location-27, cache.lazy=FALSE} # add poi raster to demographic weights reclass = c(reclass, poi) ``` In common with other data science projects, data retrieval and 'tidying' have consumed much of the overall workload so far. With clean data, the final step --- calculating a final score by summing all raster\index{raster} layers --- can be accomplished in a single line of code. ```{r 14-location-28, cache.lazy=FALSE} # calculate the total score result = sum(reclass) ``` For instance, a score of at least 9 might be a suitable threshold indicating raster cells where a bike shop could be placed (Figure \@ref(fig:bikeshop-berlin); see also `code/14-location-figures.R`). ```{r bikeshop-berlin, echo=FALSE, eval=TRUE, fig.cap="Suitable areas (i.e., raster cells with a score >= 9) in accordance with our hypothetical survey for bike stores in Berlin.", fig.scap="Suitable areas for bike stores.", warning=FALSE, cache.lazy=FALSE} if (knitr::is_latex_output()) { knitr::include_graphics("images/bikeshop-berlin-1.png") } else if (knitr::is_html_output()) { library(leaflet) # have a look at suitable bike shop locations in Berlin berlin = metros[metro_names == "Berlin", ] berlin_raster = crop(result, vect(berlin)) # summary(berlin_raster) # berlin_raster berlin_raster = berlin_raster[berlin_raster >= 9, drop = FALSE] leaflet::leaflet() |> leaflet::addTiles() |> # addRasterImage so far only supports raster objects leaflet::addRasterImage(raster::raster(berlin_raster), colors = "darkgreen", opacity = 0.8) |> leaflet::addLegend("bottomright", colors = c("darkgreen"), labels = c("potential locations"), title = "Legend") } ``` ## Discussion and next steps The presented approach is a typical example of the normative usage of a GIS\index{GIS} [@longley_geographic_2015]. We combined survey data with expert-based knowledge and assumptions (definition of metropolitan areas, defining class intervals, definition of a final score threshold). This approach is less suitable for scientific research than applied analysis that provides an evidence-based indication of areas suitable for bike shops that should be compared with other sources of information. A number of changes to the approach could improve the analysis: - We used equal weights when calculating the final scores but other factors, such as the household size, could be as important as the portion of women or the mean age - We used all points of interest\index{point of interest} but only those related to bike shops, such as do-it-yourself, hardware, bicycle, fishing, hunting, motorcycles, outdoor and sports shops (see the range of shop values available on the [OSM Wiki](https://wiki.openstreetmap.org/wiki/Map_Features#Shop)) may have yielded more refined results - Data at a higher resolution may improve the output (see Exercises) - We have used only a limited set of variables and data from other sources, such as the [INSPIRE geoportal](https://inspire-geoportal.ec.europa.eu/) or data on cycle paths from OpenStreetMap, may enrich the analysis (see also Section \@ref(retrieving-data)) - Interactions remained unconsidered, such as a possible relationship between the portion of men and single households In short, the analysis could be extended in multiple directions. Nevertheless, it should have given you a first impression and understanding of how to obtain and deal with spatial data in R\index{R} within a geomarketing\index{geomarketing} context. Finally, we have to point out that the presented analysis would be merely the first step of finding suitable locations. So far we have identified areas, 1 by 1 km in size, representing potentially suitable locations for a bike shop in accordance with our survey. Subsequent steps in the analysis could be taken: - Find an optimal location based on number of inhabitants within a specific catchment area\index{catchment area}. For example, the shop should be reachable for as many people as possible within 15 minutes of traveling bike distance (catchment area\index{catchment area} routing\index{routing}). Thereby, we should account for the fact that the further away the people are from the shop, the more unlikely it becomes that they actually visit it (distance decay function) - Also it would be a good idea to take into account competitors. That is, if there already is a bike shop in the vicinity of the chosen location, possible customers (or sales potential) should be distributed between the competitors [@huff_probabilistic_1963; @wieland_market_2017] - We need to find suitable and affordable real estate, e.g., in terms of accessibility, availability of parking spots, desired frequency of passers-by, having big windows, etc. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_14-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 15-eco.Rmd ================================================ # Ecology {#eco} ```{r, include=FALSE} source("code/before_script.R") ``` ## Prerequisites {-} This chapter assumes you have a strong grasp of geographic data analysis and processing, covered in Chapters \@ref(spatial-class) to \@ref(geometry-operations). The chapter makes use of bridges to GIS software, and spatial cross-validation, covered in Chapters \@ref(gis) and \@ref(spatial-cv), respectively. The chapter uses the following packages: ```{r 15-eco-1, message=FALSE} library(sf) library(terra) library(dplyr) library(data.table) # fast data frame manipulation (used by mlr3) library(mlr3) # machine learning (see Chapter 12) library(mlr3spatiotempcv) # spatiotemporal resampling library(mlr3tuning) # hyperparameter tuning package library(mlr3learners) # interface to most important machine learning pkgs library(paradox) # defining hyperparameter spaces library(ranger) # random forest package library(qgisprocess) # bridge to QGIS (Chapter 10) library(tree) # decision tree package library(vegan) # community ecology package ``` ## Introduction This chapter models the floristic gradient of fog oases to reveal distinctive vegetation belts that are clearly controlled by water availability. The case study provides an opportunity to bring together and extend concepts presented in previous chapters to further enhance your skills at using R for geocomputation. Fog oases, locally called *lomas*, are vegetation formations found on mountains along the coastal deserts of Peru and Chile. Similar ecosystems can be found elsewhere, including in the deserts of Namibia and along the coasts of Yemen and Oman [@galletti_land_2016]. Despite the arid conditions and low levels of precipitation of around 30-50 mm per year on average, fog deposition increases the amount of water available to plants during austral winter, resulting in green southern-facing mountain slopes along the coastal strip of Peru. The fog, which develops below the temperature inversion caused by the cold Humboldt current in austral winter, provides the name for this habitat. Every few years, the El Niño phenomenon brings torrential rainfall to this sun-baked environment, providing tree seedlings a chance to develop roots long enough to survive the following arid conditions [@dillon_lomas_2003]. Unfortunately, fog oases are heavily endangered, primarily due to agriculture and anthropogenic climate change. Evidence on the composition and spatial distribution of the native flora can support efforts to protect remaining fragments of fog oases [@muenchow_predictive_2013; @muenchow_soil_2013]. In this chapter you will analyze the composition and the spatial distribution of vascular plants (here referring mostly to flowering plants) on the southern slope of Mt. Mongón, a *lomas* mountain near Casma on the central northern coast of Peru (Figure \@ref(fig:study-area-mongon)). During a field study to Mt. Mongón, all vascular plants living in 100 randomly sampled 4 by 4 m^2^ plots in the austral winter of 2011 were recorded [@muenchow_predictive_2013]. The sampling coincided with a strong La Niña event that year, as shown in data published by the National Oceanic and Atmospheric Administration ([NOAA](https://origin.cpc.ncep.noaa.gov/products/analysis_monitoring/ensostuff/ONI_v5.php)). This led to even higher levels of aridity than usual in the coastal desert and increased fog activity on the southern slopes of Peruvian *lomas* mountains. ```{r study-area-mongon, echo=FALSE, fig.cap="The Mt. Mongón study area. Figure taken from Muenchow, Schratz, and Brenning (2017).", out.width="60%", fig.scap="The Mt. Mongón study area."} knitr::include_graphics("images/15_study_area_mongon.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/38989956-6eae7c9a-43d0-11e8-8f25-3dd3594f7e74.png") ``` This chapter also demonstrates how to apply techniques covered in previous chapters to an important applied field: ecology. Specifically, we will: - Load in needed data and compute environmental predictors (Section \@ref(data-and-data-preparation)) - Extract the main floristic gradient from our species composition matrix with the help of a dimension-reducing technique (ordinations\index{ordination}; Section \@ref(nmds)) - Model the first ordination axis, i.e., the floristic gradient, as a function of environmental predictors such as altitude, slope, catchment area\index{catchment area} and NDVI\index{NDVI} (Section \@ref(modeling-the-floristic-gradient)). For this, we will make use of a random forest model\index{random forest} --- a very popular machine learning\index{machine learning} algorithm [@breiman_random_2001]. To guarantee an optimal prediction, it is advisable to tune beforehand the hyperparameters\index{hyperparameter} with the help of spatial cross-validation\index{cross-validation!spatial CV} (see Section \@ref(svm)) - Make a spatial distribution map of the floristic composition anywhere in the study area (Section \@ref(predictive-mapping)) ## Data and data preparation All the data needed for the subsequent analyses is available via the **spDataLarge** package. ```{r 15-eco-2} data("study_area", "random_points", "comm", package = "spDataLarge") dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) ``` `study_area` is a polygon representing the outline of the study area, and `random_points` is an `sf` object containing the 100 randomly chosen sites. `comm` is a community matrix of the wide data format [@wickham_tidy_2014] where the rows represent the visited sites in the field and the columns the observed species.^[In statistics, this is also called a contingency table or cross-table.] ```{r 15-eco-3, eval=FALSE} # sites 35 to 40 and corresponding occurrences of the first five species in the # community matrix comm[35:40, 1:5] #> Alon_meri Alst_line Alte_hali Alte_porr Anth_eccr #> 35 0 0 0 0.0 1.000 #> 36 0 0 1 0.0 0.500 #> 37 0 0 0 0.0 0.125 #> 38 0 0 0 0.0 3.000 #> 39 0 0 0 0.0 2.000 #> 40 0 0 0 0.2 0.125 ``` The values represent species cover per site, and were recorded as the area covered by a species in proportion to the site area (%; please note that one site can have >100% due to overlapping cover between individual plants). The rownames of `comm` correspond to the `id` column of `random_points`. `dem` is the digital elevation model\index{digital elevation model} (DEM) for the study area, and `ndvi` is the Normalized Difference Vegetation Index (NDVI) computed from the red and near-infrared channels of a Landsat scene (see Section \@ref(local-operations) and `?spDataLarge::ndvi.tif`). Visualizing the data helps to get more familiar with it, as shown in Figure \@ref(fig:sa-mongon) where the `dem` is overplotted by the `random_points` and the `study_area`. \index{hillshade} ```{r sa-mongon, echo=FALSE, message=FALSE, fig.cap="Study mask (polygon), location of the sampling sites (black points) and DEM in the background.", fig.scap="Study mask, location of the sampling sites."} # hs = terra::shade(terra::terrain(dem, v = "slope", unit = "radians"), # terra::terrain(dem, v = "aspect", unit = "radians"), # 10, 200) # library(tmap) # tm = tm_shape(hs) + # tm_grid(n.x = 3, n.y = 3) + # tm_raster(col.scale = tm_scale_continuous(values = rev(hcl.colors(99, "Grays"))), # col.legend = tm_legend_hide()) + # tm_shape(dem) + # tm_raster(col_alpha = 0.5, # col.scale = tm_scale_continuous(n = 11, values = terrain.colors(50)), # col.legend = tm_legend(title = "m asl", reverse = TRUE)) + # tm_shape(study_area) + # tm_borders() + # tm_shape(random_points) + # tm_symbols(col = "black", size = 0.2) + # tm_layout(legend.position = tm_pos_auto_out()) # tmap_save(tm, "images/15_sa_mongon_sampling.png", # width = 12, height = 7, units = "cm") knitr::include_graphics("images/15_sa_mongon_sampling.png") ``` The next step is to compute variables which are not only needed for the modeling and predictive mapping (see Section \@ref(predictive-mapping)) but also for aligning the non-metric multidimensional scaling (NMDS)\index{NMDS} axes with the main gradient in the study area, altitude and humidity, respectively (see Section \@ref(nmds)). Specifically, we compute catchment slope and catchment area\index{catchment area} from a digital elevation model\index{digital elevation model} using R-GIS bridges (see Chapter \@ref(gis)). Curvatures might also represent valuable predictors, and in the Exercise section you can find out how they would impact the modeling result. To compute catchment area\index{catchment area} and catchment slope, we can make use of the `sagang:sagawetnessindex` function.^[Admittedly, it is a bit unsatisfying that the only way of knowing that `sagawetnessindex` computes the desired terrain attributes is to be familiar with SAGA\index{SAGA}.] `qgis_show_help()` returns all function\index{function} parameters and default values of a specific geoalgorithm\index{geoalgorithm}. Here, we present only a selection of the complete output. ```{r 15-eco-5, eval=FALSE} # if not already done, enable the saga next generation plugin qgisprocess::qgis_enable_plugins("processing_saga_nextgen") # show help qgisprocess::qgis_show_help("sagang:sagawetnessindex") #> Saga wetness index (sagang:sagawetnessindex) #> ... #> ---------------- #> Arguments #> ---------------- #> #> DEM: Elevation #> Argument type: raster #> Acceptable values: #> - Path to a raster layer #> ... #> SLOPE_TYPE: Type of Slope #> Argument type: enum #> Available values: #> - 0: [0] local slope #> - 1: [1] catchment slope #> ... #> AREA: Catchment area #> Argument type: rasterDestination #> Acceptable values: #> - Path for new raster layer #>... #> ---------------- #> Outputs #> ---------------- #> #> AREA: #> Catchment area #> SLOPE: #> Catchment slope #> ... ``` Subsequently, we can specify the needed parameters using R named arguments (see Section \@ref(rqgis)). Remember that we can use a path to a file on disk or a `SpatRaster` living in R's\index{R} global environment to specify the input raster `DEM` (see Section \@ref(rqgis)). Specifying 1 as the `SLOPE_TYPE` makes sure that the algorithm will return the catchment slope. The resulting rasters\index{raster} are saved to temporary files with an `.sdat` extension which is the native SAGA\index{SAGA} raster format. ```{r 15-eco-6, eval=FALSE} # environmental predictors: catchment slope and catchment area ep = qgisprocess::qgis_run_algorithm( alg = "sagang:sagawetnessindex", DEM = dem, SLOPE_TYPE = 1, SLOPE = tempfile(fileext = ".sdat"), AREA = tempfile(fileext = ".sdat"), .quiet = TRUE) ``` This returns a list named `ep` containing the paths to the computed output rasters. Let's read-in catchment area as well as catchment slope into a multi-layer `SpatRaster` object (see Section \@ref(raster-classes)). Additionally, we will add two more raster objects to it, namely `dem` and `ndvi`. ```{r 15-eco-7, eval=FALSE} # read in catchment area and catchment slope ep = ep[c("AREA", "SLOPE")] |> unlist() |> rast() names(ep) = c("carea", "cslope") # assign better names origin(ep) = origin(dem) # make sure rasters have the same origin ep = c(dem, ndvi, ep) # add dem and ndvi to the multi-layer SpatRaster object ``` Additionally, the catchment area\index{catchment area} values are highly skewed to the right (`hist(ep$carea)`). A log10-transformation makes the distribution more normal. ```{r 15-eco-8, eval=FALSE} ep$carea = log10(ep$carea) ``` As a convenience to the reader, we have added `ep` to **spDataLarge**: ```{r 15-eco-9, cache.lazy=FALSE} ep = rast(system.file("raster/ep.tif", package = "spDataLarge")) ``` Finally, we can extract the terrain attributes to our field observations (see also Section \@ref(raster-extraction)). ```{r 15-eco-10, cache=TRUE, cache.lazy=FALSE, message=FALSE, warning=FALSE} ep_rp = terra::extract(ep, random_points, ID = FALSE) random_points = cbind(random_points, ep_rp) ``` ## Reducing dimensionality {#nmds} Ordinations\index{ordination} are a popular tool in vegetation science to extract the main information, frequently corresponding to ecological gradients, from large species-plot matrices mostly filled with 0s. However, they are also used in remote sensing\index{remote sensing}, the soil sciences, geomarketing\index{geomarketing} and many other fields. If you are unfamiliar with ordination\index{ordination} techniques or in need of a refresher, have a look at [Michael W. Palmer's webpage](https://ordination.okstate.edu/overview.htm) for a short introduction to popular ordination techniques in ecology and at @borcard_numerical_2011 for a deeper look on how to apply these techniques in R. **vegan**'s\index{vegan (package)} package documentation is also a very helpful resource (`vignette(package = "vegan")`). Principal component analysis (PCA\index{PCA}) is probably the most famous ordination\index{ordination} technique. It is a great tool to reduce dimensionality if one can expect linear relationships between variables, and if the joint absence of a variable in two plots (observations) can be considered a similarity. This is barely the case with vegetation data. For one, the presence of a plant often follows a unimodal, i.e., a non-linear, relationship along a gradient (e.g., humidity, temperature or salinity) with a peak at the most favorable conditions and declining ends towards the unfavorable conditions. Secondly, the joint absence of a species in two plots is hardly an indication for similarity. Suppose a plant species is absent from the driest (e.g., an extreme desert) and the moistest locations (e.g., a tree savanna) of our sampling. Then we really should refrain from counting this as a similarity because it is very likely that the only thing these two completely different environmental settings have in common in terms of floristic composition is the shared absence of species (except for rare ubiquitous species). NMDS\index{NMDS} is one popular dimension-reducing technique used in ecology [@vonwehrden_pluralism_2009]. NMDS\index{NMDS} reduces the rank-based differences between distances between objects in the original matrix and distances between the ordinated objects. The difference is expressed as stress. The lower the stress value, the better the ordination, i.e., the low-dimensional representation of the original matrix. Stress values lower than 10 represent an excellent fit, stress values of around 15 are still good, and values greater than 20 represent a poor fit [@mccune_analysis_2002]. In R, `metaMDS()` of the **vegan**\index{vegan (package)} package can execute a NMDS. As input, it expects a community matrix with the sites as rows and the species as columns. Often ordinations\index{ordination} using presence-absence data yield better results (in terms of explained variance) though the prize is, of course, a less informative input matrix (see also Exercises). `decostand()` converts numerical observations into presences and absences with 1 indicating the occurrence of a species and 0 the absence of a species. Ordination techniques such as NMDS\index{NMDS} require at least one observation per site. Hence, we need to dismiss all sites in which no species were found. ```{r 15-eco-11} # presence-absence matrix pa = vegan::decostand(comm, "pa") # 100 rows (sites), 69 columns (species) # keep only sites in which at least one species was found pa = pa[rowSums(pa) != 0, ] # 84 rows, 69 columns ``` The resulting matrix serves as input for the NMDS\index{NMDS}. `k` specifies the number of output axes, here, set to 4.^[ One way of choosing `k` is to try `k` values between 1 and 6 and then using the result which yields the best stress value [@mccune_analysis_2002]. ] NMDS\index{NMDS} is an iterative procedure trying to make the ordinated space more similar to the input matrix in each step. To make sure that the algorithm converges, we set the number of steps to 500 using the `try` parameter. ```{r 15-eco-12, eval=FALSE, message=FALSE} set.seed(25072018) nmds = vegan::metaMDS(comm = pa, k = 4, try = 500) nmds$stress #> ... #> Run 498 stress 0.08834745 #> ... Procrustes: rmse 0.004100446 max resid 0.03041186 #> Run 499 stress 0.08874805 #> ... Procrustes: rmse 0.01822361 max resid 0.08054538 #> Run 500 stress 0.08863627 #> ... Procrustes: rmse 0.01421176 max resid 0.04985418 #> *** Solution reached #> 0.08831395 ``` ```{r 15-eco-13, eval=FALSE, echo=FALSE} saveRDS(nmds, "extdata/15-nmds.rds") ``` ```{r 15-eco-14, include=FALSE} nmds = readRDS("extdata/15-nmds.rds") ``` A stress value of 9 represents a very good result, which means that the reduced ordination space represents the large majority of the variance of the input matrix. Overall, NMDS\index{NMDS} puts objects that are more similar (in terms of species composition) closer together in ordination space. However, as opposed to most other ordination\index{ordination} techniques, the axes are arbitrary and not necessarily ordered by importance [@borcard_numerical_2011]. However, we already know that humidity represents the main gradient in the study area [@muenchow_predictive_2013;@muenchow_rqgis:_2017]. Since humidity is highly correlated with elevation, we rotate the NMDS axes\index{NMDS} in accordance with elevation (see also `?MDSrotate` for more details on rotating NMDS axes). Plotting the result reveals that the first axis is, as intended, clearly associated with altitude (Figure \@ref(fig:xy-nmds)). ```{r xy-nmds-code, fig.cap="Plotting the first NMDS axis against altitude.", fig.scap = "First NMDS axis against altitude plot.", fig.asp=1, out.width="60%", eval=FALSE} elev = dplyr::filter(random_points, id %in% rownames(pa)) |> dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = vegan::MDSrotate(nmds, elev) # extracting the first two axes sc = vegan::scores(rotnmds, choices = 1:2, display = "sites") # plotting the first axis against altitude plot(y = sc[, 1], x = elev, xlab = "elevation in m", ylab = "First NMDS axis", cex.lab = 0.8, cex.axis = 0.8) ``` ```{r xy-nmds, fig.cap="Plotting the first NMDS axis against altitude.", fig.scap = "First NMDS axis against altitude plot.", fig.asp=1, out.width="60%", message=FALSE, echo=FALSE} elev = dplyr::filter(random_points, id %in% rownames(pa)) |> dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = vegan::MDSrotate(nmds, elev) # extracting the first two axes sc = vegan::scores(rotnmds, choices = 1:2, display = "sites") knitr::include_graphics("images/15_xy_nmds.png") ``` ```{r 15-eco-15, eval=FALSE, echo=FALSE} # scores and rotated scores in one figure p1 = xyplot(scores(rotnmds, display = "sites")[, 2] ~ scores(rotnmds, display = "sites")[, 1], pch = 16, col = "lightblue", xlim = c(-3, 2), ylim = c(-2, 2), xlab = list("Dimension 1", cex = 0.8), ylab = list("Dimension 2", cex = 0.8), scales = list(x = list(relation = "same", cex = 0.8), y = list(relation = "same", cex = 0.8), # ticks on top are suppressed tck = c(1, 0), # plots axes labels only in row and column 1 and 4 alternating = c(1, 0, 0, 1), draw = TRUE), # we have to use the same colors in the legend as used for the plot # points par.settings = simpleTheme(col = c("lightblue", "salmon"), pch = 16, cex = 0.9), # also the legend point size should be somewhat smaller auto.key = list(x = 0.7, y = 0.9, text = c("unrotated", "rotated"), between = 0.5, cex = 0.9), panel = function(x, y, ...) { # Plot the points panel.points(x, y, cex = 0.6, ...) panel.points(x = scores(nmds, display = "sites")[, 1], y = scores(nmds, display = "sites")[, 2], col = "salmon", pch = 16, cex = 0.6) panel.arrows(x0 = scores(nmds, display = "sites")[, 1], y0 = scores(nmds, display = "sites")[, 2], x1 = x, y1 = y, length = 0.04, lwd = 0.4) }) plot(scores(nmds, choices = 1:2, display = "sites")) points(scores(rotnmds, choices = 1:2, display = "sites"), col = "lightblue", pch = 16) sc = scores(nmds, choices = 1:2, display = "sites") |> as.data.frame() sc$id = rownames(sc) |> as.numeric() rp = inner_join(select(sc, id), st_drop_geometry(random_points)) fit_1 = envfit(nmds, select(rp, dem)) fit_2 = envfit(rotnmds, select(rp, dem)) par(mfrow = c(1, 2)) plot(nmds, display = "sites") plot(fit_1) plot(rotnmds, display = "sites") plot(fit_2) ``` The scores of the first NMDS\index{NMDS} axis represent the different vegetation formations, i.e., the floristic gradient, appearing along the slope of Mt. Mongón. To spatially visualize them, we can model the NMDS\index{NMDS} scores with the previously created predictors (Section \@ref(data-and-data-preparation)), and use the resulting model for predictive mapping (see Section \@ref(modeling-the-floristic-gradient)). ## Modeling the floristic gradient To predict the floristic gradient spatially, we use a random forest\index{random forest} model. Random forest\index{random forest} models are frequently applied in environmental and ecological modeling, and often provide the best results in terms of predictive performance [@hengl_random_2018;@schratz_hyperparameter_2019]. Here, we shortly introduce decision trees and bagging, since they form the basis of random forests\index{random forest}. We refer the reader to @james_introduction_2013 for a more detailed description of random forests\index{random forest} and related techniques. To introduce decision trees by example, we first construct a response-predictor matrix by joining the rotated NMDS\index{NMDS} scores to the field observations (`random_points`). We will also use the resulting data frame for the **mlr3**\index{mlr3 (package)} modeling later on. ```{r 15-eco-16, message=FALSE, eval=TRUE} # construct response-predictor matrix # id- and response variable rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) # join the predictors (dem, ndvi and terrain attributes) rp = inner_join(random_points, rp, by = "id") ``` Decision trees split the predictor space into a number of regions. To illustrate this, we apply a decision tree to our data using the scores of the first NMDS\index{NMDS} axis as the response (`sc`) and altitude (`dem`) as the only predictor. ```{r 15-eco-17, eval=FALSE} tree_mo = tree::tree(sc ~ dem, data = rp) plot(tree_mo) text(tree_mo, pretty = 0) ``` ```{r tree, echo=FALSE, fig.cap="Simple example of a decision tree with three internal nodes and four terminal nodes.", out.width="60%", fig.scap="Simple example of a decision tree."} # tree_mo = tree::tree(sc ~ dem, data = rp) # png("images/15_tree.png", width = 1100, height = 700, units = "px", res = 300) # par(mar = rep(1, 4)) # plot(tree_mo) # text(tree_mo, pretty = 0) # dev.off() knitr::include_graphics("images/15_tree.png") ``` The resulting tree consists of three internal nodes and four terminal nodes (Figure \@ref(fig:tree)). The first internal node at the top of the tree assigns all observations which are below 328.5 m to the left and all other observations to the right branch. The observations falling into the left branch have a mean NMDS\index{NMDS} score of -1.198. Overall, we can interpret the tree as follows: the higher the elevation, the higher the NMDS\index{NMDS} score becomes. This means that the simple decision tree has already revealed four distinct floristic assemblages. For a more in-depth interpretation, please refer to Section \@ref(predictive-mapping). Decision trees have a tendency to overfit\index{overfitting}, that is, they mirror too closely the input data including its noise which in turn leads to bad predictive performances [Section \@ref(intro-cv), @james_introduction_2013]. Bootstrap aggregation (bagging) is an ensemble technique that can help to overcome this problem. Ensemble techniques simply combine the predictions of multiple models. Thus, bagging takes repeated samples from the same input data and averages the predictions. This reduces the variance and overfitting\index{overfitting} with the result of a much better predictive accuracy compared to decision trees. Finally, random forests\index{random forest} extend and improve bagging by decorrelating trees which is desirable since averaging the predictions of highly correlated trees shows a higher variance and thus lower reliability than averaging predictions of decorrelated trees [@james_introduction_2013]. To achieve this, random forests\index{random forest} use bagging, but in contrast to the traditional bagging where each tree is allowed to use all available predictors, random forests only use a random sample of all available predictors. ### **mlr3** building blocks The code in this section largely follows the steps we have introduced in Section \@ref(svm). The only differences are the following: 1. The response variable is numeric, hence a regression\index{regression} task will replace the classification\index{classification} task of Section \@ref(svm) 1. Instead of the AUROC\index{AUROC} which can only be used for categorical response variables, we will use the root mean squared error (RMSE\index{RMSE}) as the performance measure 1. We use a Random Forest\index{random forest} model instead of a Support Vector Machine\index{SVM} which naturally goes along with different hyperparameters\index{hyperparameter} 1. We are leaving the assessment of a bias-reduced performance measure as an exercise to the reader (see Exercises). Instead, we show how to tune hyperparameters\index{hyperparameter} for (spatial) predictions Remember that 125,500 models were necessary to retrieve bias-reduced performance estimates when using 100-repeated 5-fold spatial cross-validation\index{cross-validation!spatial CV} and a random search of 50 iterations in Section \@ref(svm). In the hyperparameter\index{hyperparameter} tuning level, we found the best hyperparameter combination which in turn was used in the outer performance level for predicting the test data of a specific spatial partition (see also Figure \@ref(fig:inner-outer)). This was done for five spatial partitions, and repeated a 100 times yielding in total 500 optimal hyperparameter combinations. Which one should we use for making spatial distribution maps? The answer is simple: none at all. Remember, the tuning was done to retrieve a bias-reduced performance estimate, not to do the best possible spatial prediction. For the latter, one estimates the best hyperparameter\index{hyperparameter} combination from the complete dataset. This means, the inner hyperparameter\index{hyperparameter} tuning level is no longer needed, which makes perfect sense since we are applying our model to new data (unvisited field observations) for which the true outcomes are unavailable, hence testing is impossible in any case. Therefore, we tune the hyperparameters\index{hyperparameter} for a good spatial prediction on the complete dataset via a 5-fold spatial CV\index{cross-validation!spatial CV} with one repetition. Having already constructed the input variables (`rp`), we are all set for specifying the **mlr3**\index{mlr3 (package)} building blocks (task, learner, and resampling). For specifying a spatial task, we use again the **mlr3spatiotempcv** package [@schratz_mlr3spatiotempcv_2021, Section \@ref(spatial-cv-with-mlr3)], and since our response (`sc`) is numeric, we use a regression\index{regression} task. ```{r 15-eco-20} # create task task = mlr3spatiotempcv::as_task_regr_st( select(rp, -id, -spri), target = "sc", id = "mongon" ) ``` Using an `sf` object as the backend automatically provides the geometry information needed for the spatial partitioning later on. Additionally, we got rid of the columns `id` and `spri`, since these variables should not be used as predictors in the modeling. Next, we go on to construct a random forest\index{random forest} learner from the **ranger** package [@wright_ranger_2017]. ```{r 15-eco-21} lrn_rf = lrn("regr.ranger", predict_type = "response") ``` As opposed to, for example, Support Vector Machines\index{SVM} (see Section \@ref(svm)), random forests often already show good performances when used with the default values of their hyperparameters (which may be one reason for their popularity). Still, tuning often moderately improves model results, and thus is worth the effort [@probst_hyperparameters_2018]. In random forests\index{random forest}, the hyperparameters\index{hyperparameter} `mtry`, `min.node.size` and `sample.fraction` determine the degree of randomness, and should be tuned [@probst_hyperparameters_2018]. `mtry` indicates how many predictor variables should be used in each tree. If all predictors are used, then this corresponds in fact to bagging (see beginning of Section \@ref(modeling-the-floristic-gradient)). The `sample.fraction` parameter specifies the fraction of observations to be used in each tree. Smaller fractions lead to greater diversity, and thus less correlated trees which often is desirable (see above). The `min.node.size` parameter indicates the number of observations a terminal node should at least have (see also Figure \@ref(fig:tree)). Naturally, as trees and computing time become larger, the lower the `min.node.size`. Hyperparameter\index{hyperparameter} combinations will be selected randomly but should fall inside specific tuning limits (created with `paradox::ps()`). `mtry` should range between 1 and the number of predictors (`r ncol(task$data()) - 1`) , `sample.fraction` should range between 0.2 and 0.9 and `min.node.size` should range between 1 and 10 [@probst_hyperparameters_2018]. ```{r 15-eco-22} # specifying the search space search_space = paradox::ps( mtry = paradox::p_int(lower = 1, upper = ncol(task$data()) - 1), sample.fraction = paradox::p_dbl(lower = 0.2, upper = 0.9), min.node.size = paradox::p_int(lower = 1, upper = 10) ) ``` Having defined the search space, we are all set for specifying our tuning via the `AutoTuner()` function. Since we deal with geographic data, we will again make use of spatial cross-validation to tune the hyperparameters\index{hyperparameter} (see Sections \@ref(intro-cv) and \@ref(spatial-cv-with-mlr3)). Specifically, we will use a 5-fold spatial partitioning with only one repetition (`rsmp()`). In each of these spatial partitions, we run 50 models (`trm()`) while using randomly selected hyperparameter configurations (`tnr()`) within predefined limits (`seach_space`) to find the optimal hyperparameter\index{hyperparameter} combination [see also Section \@ref(svm) and https://mlr3book.mlr-org.com/chapters/chapter4/hyperparameter_optimization.html#sec-autotuner, @bischl_applied_2024]. The performance measure is the root mean squared error (RMSE\index{RMSE}). ```{r 15-eco-23} autotuner_rf = mlr3tuning::auto_tuner( learner = lrn_rf, resampling = mlr3::rsmp("spcv_coords", folds = 5), # spatial partitioning measure = mlr3::msr("regr.rmse"), # performance measure terminator = mlr3tuning::trm("evals", n_evals = 50), # specify 50 iterations search_space = search_space, # predefined hyperparameter search space tuner = mlr3tuning::tnr("random_search") # specify random search ) ``` Calling the `train()`-method of the `AutoTuner`-object finally runs the hyperparameter\index{hyperparameter} tuning, and will find the optimal hyperparameter\index{hyperparameter} combination for the specified parameters. ```{r 15-eco-24, eval=FALSE, cache=TRUE, cache.lazy=FALSE} # hyperparameter tuning set.seed(19012026) autotuner_rf$train(task) ``` ```{r 15-eco-25, cache=TRUE, cache.lazy=FALSE, eval=FALSE, echo=FALSE} saveRDS(autotuner_rf, "extdata/15-tune.rds") ``` ```{r 15-eco-26, echo=FALSE, cache=TRUE, cache.lazy=FALSE} autotuner_rf = readRDS("extdata/15-tune.rds") ``` ```{r tuning-result, cache=TRUE, cache.lazy=FALSE} autotuner_rf$tuning_result ``` ### Predictive mapping The tuned hyperparameters\index{hyperparameter} can now be used for the prediction. To do so, we only need to run the `predict` method of our fitted `AutoTuner` object. ```{r 15-eco-27, cache=TRUE, cache.lazy=FALSE, warning=FALSE} # predicting using the best hyperparameter combination autotuner_rf$predict(task) ``` The `predict` method will apply the model to all observations used in the modeling. Given a multi-layer `SpatRaster` containing rasters named as the predictors used in the modeling, `terra::predict()` will also make spatial distribution maps, i.e., predict to new data. As an alternative, you can also use the dedicated **mlr3spatial** package for doing spatial predictions. ```{r 15-eco-28, cache=TRUE, cache.lazy=FALSE, eval=FALSE} pred = terra::predict(ep, model = autotuner_rf$learner$model$model, fun = predict) # doing the same using mlr3spatial # pred = mlr3spatial::predict_spatial(newdata = ep, learner = autotuner_rf) ``` ```{r rf-pred, echo=FALSE, eval=TRUE, fig.cap="Predictive mapping of the floristic gradient clearly revealing distinct vegetation belts.", fig.scap="Predictive mapping of the floristic gradient."} # hs = terra::shade(terra::terrain(dem, v = "slope", unit = "radians"), # terra::terrain(dem, v = "aspect", unit = "radians"), # 10, 200) |> # terra::mask(terra::vect(study_area)) # pred = terra::mask(pred, terra::vect(study_area)) |> # terra::trim() # # # remotes::install_github("r-tmap/tmap") # library(tmap) # pal = rev(hcl.colors(n = 15, "RdYlBu")) # tm = tm_shape(hs) + # tm_grid(n.x = 3, n.y = 3, lines = FALSE) + # tm_raster(col.scale = tm_scale_continuous(values = rev(hcl.colors(99, "Grays"))), # col.legend = tm_legend_hide()) + # tm_shape(pred, is.main = TRUE) + # tm_raster(col.scale = tm_scale_continuous(values = pal, midpoint = NA), # col.legend = tm_legend(title = "NMDS1", reverse = TRUE), # col_alpha = 0.8) + # tm_shape(study_area) + # tm_borders() + # tm_layout(legend.position = tm_pos_auto_out()) # tmap_save(tm, "images/15_rf_pred.png", # width = 10, height = 8, units = "cm") knitr::include_graphics("images/15_rf_pred.png") ``` In case, `terra::predict()` does not support a model algorithm, you can still make the predictions manually. ```{r 15-eco-29, cache=TRUE, cache.lazy=FALSE, eval=FALSE} newdata = as.data.frame(as.matrix(ep)) colSums(is.na(newdata)) # 0 NAs # but assuming there were 0s results in a more generic approach ind = rowSums(is.na(newdata)) == 0 tmp = autotuner_rf$predict_newdata(newdata = newdata[ind, ], task = task) newdata[ind, "pred"] = data.table::as.data.table(tmp)[["response"]] pred_2 = ep$dem # now fill the raster with the predicted values pred_2[] = newdata$pred # check if terra and our manual prediction is the same all(values(pred - pred_2) == 0) ``` The predictive mapping clearly reveals distinct vegetation belts (Figure \@ref(fig:rf-pred)). Please refer to @muenchow_soil_2013 for a detailed description of vegetation belts on *lomas* mountains. The blue color tones represent the so-called *Tillandsia* belt. *Tillandsia* is a highly adapted genus especially found in high quantities at the sandy and quite desertic foot of *lomas* mountains. The yellow color tones refer to a herbaceous vegetation belt with a much higher plant cover compared to the *Tillandsia* belt. The orange colors represent the bromeliad belt, which features the highest species richness and plant cover. It can be found directly beneath the temperature inversion (ca. 750-850 m asl) where humidity due to fog is highest. Water availability naturally decreases above the temperature inversion, and the landscape becomes desertic again with only a few succulent species (succulent belt; red colors). Interestingly, the spatial prediction clearly reveals that the bromeliad belt is interrupted, which is a very interesting finding we would have not detected without the predictive mapping. ## Conclusions In this chapter we have ordinated\index{ordination} the community matrix of the *lomas* Mt. Mongón with the help of a NMDS\index{NMDS} (Section \@ref(nmds)). The first axis, representing the main floristic gradient in the study area, was modeled as a function of environmental predictors which partly were derived through R-GIS\index{GIS} bridges (Section \@ref(data-and-data-preparation)). The **mlr3**\index{mlr3 (package)} package provided the building blocks to spatially tune the hyperparameters\index{hyperparameter} `mtry`, `sample.fraction` and `min.node.size` (Section \@ref(mlr3-building-blocks)). The tuned hyperparameters\index{hyperparameter} served as input for the final model which in turn was applied to the environmental predictors for a spatial representation of the floristic gradient (Section \@ref(predictive-mapping)). The result demonstrates spatially the astounding biodiversity in the middle of the desert. Since *lomas* mountains are heavily endangered, the prediction map can serve as basis for informed decision-making on delineating protection zones, and making the local population aware of the uniqueness found in their immediate neighborhood. In terms of methodology, a few additional points could be addressed: - It would be interesting to also model the second ordination\index{ordination} axis, and to subsequently find an innovative way of visualizing jointly the modeled scores of the two axes in one prediction map - If we were interested in interpreting the model in an ecologically meaningful way, we should probably use (semi-)parametric models [@muenchow_predictive_2013;@zuur_mixed_2009;@zuur_beginners_2017]. However, there are at least approaches that help to interpret machine learning models such as random forests\index{random forest} (see, e.g., [https://mlr-org.com/posts/2018-04-30-interpretable-machine-learning-iml-and-mlr/](https://mlr-org.com/posts/2018-04-30-interpretable-machine-learning-iml-and-mlr/)) - A sequential model-based optimization (SMBO) might be preferable to the random search for hyperparameter\index{hyperparameter} optimization used in this chapter [@probst_hyperparameters_2018] Finally, please note that random forest\index{random forest} and other machine learning\index{machine learning} models are frequently used in a setting with much more observations and predictors than used in this Chapter, and where it is unclear which variables and variable interactions contribute to explaining the response. Additionally, the relationships might be highly non-linear. In our use case, the relationship between response and predictors is pretty clear, there is only a slight amount of non-linearity and the number of observations and predictors is low. Hence, it might be worth trying a linear model\index{regression!linear}. A linear model is much easier to explain and understand than a random forest\index{random forest} model, and therefore preferred (law of parsimony). Additionally it is computationally less demanding (see Exercises). If the linear model cannot cope with the degree of non-linearity present in the data, one could also try a generalized additive model\index{generalized additive model} (GAM). The point here is that the toolbox of a data scientist consists of more than one tool, and it is your responsibility to select the tool best suited for the task or purpose at hand. Here, we wanted to introduce the reader to random forest\index{random forest} modeling and how to use the corresponding results for predictive mapping purposes. For this purpose, a well-studied dataset with known relationships between response and predictors, is appropriate. However, this does not imply that the random forest\index{random forest} model has returned the best result in terms of predictive performance. ## Exercises ```{r, echo=FALSE, results='asis'} res = knitr::knit_child('_15-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ``` ================================================ FILE: 16-synthesis.Rmd ================================================ # Conclusion {#conclusion} ```{r, include=FALSE} source("code/before_script.R") ``` ## Introduction Like the introduction, this concluding chapter contains a few code chunks. The aim is to synthesize the contents of the book, with reference to recurring themes/concepts, and to inspire future directions of application and development. The chapter has no prerequisites. However, you may get more out of it if you have read and attempted the exercises in Part I (Foundations), tried more advances approaches in Part II (Extensions), and considered how geocomputation can help you solve work, research or other problems, with reference to the chapters in Part III (Applications). The chapter is organized as follows. Section \@ref(package-choice) discusses the wide range of options for handling geographic data in R. Choice is a key feature of open source software; the section provides guidance on choosing between the various options. Section \@ref(gaps) describes gaps in the book's contents and explains why some areas of research were deliberately omitted, while others were emphasized. Next, Section \@ref(questions) provides advice on how to ask good questions when you get stuck, and how to search for solutions online. Section \@ref(next) answers the following question: having read this book, where to go next? Section \@ref(benefit) returns to the wider issues raised in Chapter \@ref(intro). In it we consider geocomputation as part of a wider 'open source approach' that ensures methods are publicly accessible, reproducible\index{reproducibility} and supported by collaborative communities. This final section of the book also provides some pointers on how to get involved. ## Package choice A feature of R\index{R}, and open source software in general, is that there are often multiple ways to achieve the same result. The code chunk below illustrates this by using three functions, covered in Chapters \@ref(attr) and \@ref(geometry-operations), to combine the 16 regions of New Zealand into a single geometry: ```{r 16-synnthesis-1} #| message: FALSE library(spData) nz_u1 = sf::st_union(nz) nz_u2 = aggregate(nz["Population"], list(rep(1, nrow(nz))), sum) nz_u3 = dplyr::summarise(nz, t = sum(Population)) identical(nz_u1, nz_u2$geometry) identical(nz_u1, nz_u3$geom) ``` Although the classes, attributes and column names of the resulting objects `nz_u1` to `nz_u3` differ, their geometries are identical, as verified using the base R function `identical()`.^[ The first operation, undertaken by the function `st_union()`\index{vector!union}, creates an object of class `sfc` (a simple feature column). The latter two operations create `sf` objects, each of which *contains* a simple feature column. Therefore, it is the geometries contained in simple feature columns, not the objects themselves, that are identical. ] Which to use? It depends: the former only processes the geometry data contained in `nz` so is faster, while the other options performed attribute operations, which may be useful for subsequent steps. Whether to use the base R function `aggregate()` or the **dplyr** function `summarise()` is a matter of preference, with the latter being more readable for many. The wider point is that there are often multiple options to choose from when working with geographic data in R, even within a single package. The range of options grows further when more R packages are considered: you could achieve the same result using the older **sp** package\index{sp (package)}, for example. However, based on our goal of providing good advice, we recommend using the more recent, more performant and future-proof **sf** package. The same applies for all packages showcased in this book, although it can be helpful (when not distracting) to be aware of alternatives and being able to justify your choice of software. A common choice, for which there is no simple answer, is between **tidyverse**\index{tidyverse (package)} and base R for geocomputation. The following code chunk, for example, shows **tidyverse** and base R ways to extract the `Name` column from the `nz` object, as described in Chapter \@ref(attr): ```{r 16-synnthesis-2, message=FALSE} library(dplyr) # attach a tidyverse package nz_name1 = nz["Name"] # base R approach nz_name2 = nz |> # tidyverse approach select(Name) identical(nz_name1$Name, nz_name2$Name) # check results ``` This raises the question: which to use? The answer is: it depends. Each approach has advantages: base R\index{R!base} tends to be stable, well-known, and has minimal dependencies, which is why it is often preferred for software (package) development. The tidyverse approach, on the other hand, is often preferred for interactive programming. Choosing between the two approaches is therefore a matter of preference and application. While this book covers commonly needed functions --- such as the base R `[` subsetting operator and the **dplyr** function `select()` demonstrated in the code chunk above --- there are many other functions for working with geographic data, from other packages, that have not been mentioned. Chapter \@ref(intro) mentions 20+ influential packages for working with geographic data, and only a handful of these are covered in the book. Hundreds of other packages are available for working with geographic data in R, and many more are developed each year. As of 2024, there are more than 160 packages mentioned in the Spatial [Task View](https://cran.r-project.org/web/views/) and countless functions for geographic data analysis are developed each year. ```{r 16-synnthesis-3, eval=FALSE, echo=FALSE} # aim: find number of packages in the spatial task view # how? see: # vignette("selectorgadget") stv_pkgs = xml2::read_html("https://cran.r-project.org/web/views/Spatial.html") pkgs = rvest::html_nodes(stv_pkgs, "#reading-and-writing-spatial-data---gis-software-connectors+ ul li , #geographic-metadata+ ul li , #raster-data+ ul li , #specific-geospatial-data-sources-of-interest+ ul li , #data-processing---general+ ul li , #data-cleaning+ ul li , #data-processing---specific+ ul li , #spatial-sampling+ ul li , #base-visualization-packages+ ul li , #thematic-cartography-packages+ ul li , #packages-based-on-web mapping-frameworks+ ul li , #building-cartograms+ ul li , p+ ul li , #spatial-data---general+ ul li") pkgs_char = rvest::html_text(pkgs) length(pkgs_char) ``` The rate of evolution in R's spatial ecosystem may be fast, but there are strategies to deal with the wide range of options. Our advice is to start by learning one approach *in depth* but to have a general understanding of the *breadth* of available options. This advice applies equally to solving geographic problems with R, as it does to other fields of knowledge and application. Section \@ref(next) covers developments in other languages. Of course, some packages perform better than others for the *same* task, in which case it's important to know which to use. In the book we have aimed to focus on packages that are future-proof (they will work long into the future), high performance (relative to other R packages), well maintained (with user and developer communities surrounding them) and complementary. There are still overlaps in the packages we have used, as illustrated by the diversity of packages for making maps, as highlighted in Chapter \@ref(adv-map), for example. Overlapping functionality can be good. A new package with similar (but not identical) functionality compared to an existing package can increase resilience, performance (partly driven by friendly competition and mutual learning between developers) and choice, both of which are key benefits of doing geocomputation with open source software. In this context, deciding which combination of **sf**, **tidyverse**, **terra** and other packages to use should be made with knowledge of alternatives. The **sp** ecosystem that **sf**\index{sf} superseded, for example, can do many of the things covered in this book and, due to its age, is built on by many other packages. At the time of writing in 2024, 463 packages `Depend` on or `Import` **sp**, up slightly from 452 in October 2018, showing that its data structures are widely used and have been extended in many directions. The equivalent numbers for **sf** are 69 in 2018 and 431 in 2024, highlighting that the package is future-proof and has a growing user base and developer community [@bivand_progress_2021]. Although best known for point pattern analysis, the **spatstat** package also supports raster\index{raster} and other vector geometries and provides powerful functionality for spatial statistics and more [@baddeley_spatstat_2005]. It may also be worth researching new alternatives that are under development if you have needs that are not met by established packages. ```{r 16-synnthesis-4, eval=FALSE, echo=FALSE} # aim: find number of packages that depend on sp, sf and spatstat sfdeps = devtools::revdep(pkg = "sf", dependencies = c("Depends", "Imports")) spatstatdeps = devtools::revdep(pkg = "spatstat", dependencies = c("Depends", "Imports")) spdeps = devtools::revdep(pkg = "sp", dependencies = c("Depends", "Imports")) length(sfdeps) # 431 length(spatstatdeps) # 34 length(spdeps) # 463 431 / 69 ``` ## Gaps and overlaps {#gaps} Geocomputation\index{geocomputation} is a big area, so there are inevitably gaps in this book. We have been selective, deliberately highlighting certain topics, techniques and packages, while omitting others. We have tried to emphasize topics that are most commonly needed in real-world applications such as geographic data operations, basics of coordinate reference systems, read/write data operations and visualization techniques. Some topics and themes appear repeatedly, with the aim of building essential skills for geocomputation, and showing you how to go further, into more advanced topics and specific applications. We deliberately omitted some topics that are covered in-depth elsewhere. Statistical modeling of spatial data such as point pattern analysis\index{point pattern analysis}, spatial interpolation\index{spatial interpolation} (e.g., kriging) and spatial regression\index{spatial regression}, for example, are mentioned in the context of machine learning in Chapter \@ref(spatial-cv) but not covered in detail. There are already excellent resources on these methods, including statistically orientated chapters in @pebesma_spatial_2023 and books on point pattern analysis [@baddeley_spatial_2015], Bayesian techniques applied to spatial data [@gomez-rubio_bayesian_2020; @moraga_spatial_2023], and books focused on particular applications such as health [@moraga_geospatial_2019] and [wildfire severity analysis](https://bookdown.org/mcwimberly/gdswr-book/application---wildfire-severity-analysis.html) [@wimberly_geographic_2023]. Other topics which received limited attention were remote sensing and using R alongside (rather than as a bridge to) dedicated GIS software. There are many resources on these topics, including a [discussion on remote sensing in R](https://github.com/r-spatial/discuss/issues/56), @wegmann_remote_2016 and the GIS-related teaching materials available from [Marburg University](https://geomoer.github.io/moer-info-page/courses.html). We focused on machine learning rather than spatial statistical inference\index{statistical inference} in Chapters \@ref(spatial-cv) and \@ref(eco) because of the abundance of quality resources on the topic. These resources include @zuur_mixed_2009, @zuur_beginners_2017 which focus on ecological use cases, and freely available teaching material and code on *Geostatistics & Open-source Statistical Computing* hosted at [css.cornell.edu/faculty/dgr2](https://css.cornell.edu/faculty/dgr2/teach/). [*R for Geographic Data Science*](https://sdesabbata.github.io/r-for-geographic-data-science/) provides an introduction to R for geographic data science and modeling. We have largely omitted geocomputation on 'big data'\index{big data} by which we mean datasets that do not fit on a high-spec laptop. This decision is justified by the fact that the majority of geographic datasets that are needed for common research or policy applications *do* fit on consumer hardware, large high-resolution remote sensing datasets being a notable exception (see Section \@ref(cloud)). It is possible to get more RAM on your computer or to temporarily 'rent' compute power available on platforms such as [GitHub Codespaces, which can be used to run the code in this book](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=84222786&machine=basicLinux32gb&devcontainer_path=.devcontainer.json&location=WestEurope). Furthermore, learning to solve problems on small datasets is a prerequisite to solving problems on huge datasets and the emphasis in this book is getting started, and the skills you learn here will be useful when you move to bigger datasets. Analysis of 'big data' often involves extracting a small amount of data from a database for a specific statistical analysis. Spatial databases, covered in Chapter \@ref(gis), can help with the analysis of datasets that do not fit in memory. 'Earth observation cloud back-ends' can be accessed from R with the **openeo** package (Section \@ref(openeo)). If you need to work with big geographic datasets, we also recommend exploring projects such as [Apache Sedona](https://sedona.apache.org/) and emerging file formats such as [GeoParquet](https://paleolimbot.github.io/geoarrow/). ## Getting help {#questions} Geocomputation is a large and challenging field, making issues and temporary blockers to work near inevitable. In many cases you may just 'get stuck' at a particular point in your data analysis workflow facing cryptic error messages that are hard to debug. Or you may get unexpected results with few clues about what is going on. This section provides pointers to help you overcome such problems, by clearly defining the problem, searching for existing knowledge on solutions and, if those approaches do not solve the problem, through the art of asking good questions. When you get stuck at a particular point, it is worth first taking a step back and working out which approach is most likely to solve the issue. Trying each of the following steps --- skipping steps already taken --- provides a structured approach to problem-solving: 1. Define exactly what you are trying to achieve, starting from first principles (and often a sketch, as outlined below) 2. Diagnose exactly where in your code the unexpected results arise, by running and exploring the outputs of individual lines of code and their individual components (you can run individual parts of a complex command by selecting them with a cursor and pressing Ctrl+Enter in RStudio, for example) 3. Read the documentation of the function that has been diagnosed as the 'point of failure' in the previous step. Simply understanding the required inputs to functions, and running the examples that are often provided at the bottom of help pages, can help solve a surprisingly large proportion of issues (run the command `?terra::rast` and scroll down to the examples that are worth reproducing when getting started with the function, for example) 4. If reading R's built-in documentation, as outlined in the previous step, does not help to solve the problem, it is probably time to do a broader search online to see if others have written about the issue you're seeing. See a list of places to search for help below 5. If all the previous steps above fail, and you cannot find a solution from your online searches, it may be time to compose a question with a reproducible example and post it in an appropriate place Steps 1 to 3 outlined above are fairly self-explanatory but, due to the vastness of the internet and multitude of search options, it is worth considering effective search strategies before deciding to compose a question. ### Searching for solutions online Search engines are a logical place to start for many issues. 'Googling it' can in some cases result in the discovery of blog posts, forum messages and other online content about the precise issue you're having. Simply typing in a clear description of the problem/question is a valid approach here, but it is important to be specific (e.g., with reference to function and package names and input dataset sources if the problem is dataset-specific). You can also make online searches more effective by including additional detail: - Use quotation marks to maximize the chances that 'hits' relate to the exact issue you're having by reducing the number of results returned. For example, if you try and fail to save a GeoJSON file in a location that already exists, you will get an error containing the message "GDAL Error 6: DeleteLayer() not supported by this dataset". A specific search query such as `"GDAL Error 6" sf` is more likely to yield a solution than searching for `GDAL Error 6` without the quotation marks - Set [time restraints](https://uk.pcmag.com/software-services/138320/21-google-search-tips-youll-want-to-learn), for example only returning content created within the last year can be useful when searching for help on an evolving package - Make use of additional [search engine features](https://www.makeuseof.com/tag/6-ways-to-search-by-date-on-google/), for example restricting searches to content hosted on CRAN with site:r-project.org ### Places to search for (and ask) for help {#help} In cases where online searches do not yield a solution, it is worth asking for help. There are many forums where you can do this, including: - R's Special Interest Group on Geographic data email list ([R-SIG-GEO](https://stat.ethz.ch/mailman/listinfo/r-sig-geo)) - The GIS Stackexchange website at [gis.stackexchange.com](https://gis.stackexchange.com/) - The large and general purpose programming Q&A site [stackoverflow.com](https://stackoverflow.com/) - Online forums associated with a particular entity, such as the [Posit Community](https://forum.posit.co/), the [rOpenSci Discuss](https://discuss.ropensci.org/) web forum and forums associated with particular software tools such as the [Stan](https://discourse.mc-stan.org/) forum - Software development platforms such as GitHub, which hosts issue trackers for the majority of R-spatial packages and also, increasingly, built-in discussion pages such as that created to encourage discussion (not just bug reporting) around the **sfnetworks** package (see [luukvdmeer/sfnetworks/discussions](https://github.com/luukvdmeer/sfnetworks/discussions/)) - Online chat rooms and forums associated with communities such as the [rOpenSci](https://ropensci.org/blog/2022/09/13/contributing-ropensci/) and the [geocompx](https://geocompx.org)\index{geocompx} community (which has a [Discord server](https://discord.com/invite/PMztXYgNxp) where you can ask questions), of which this book is a part ### Reproducible examples with **reprex** {#reprex} In terms of asking a good question, a clearly stated question supported by an accessible and fully reproducible example is key (see also https://r4ds.hadley.nz/workflow-help.html). It is also helpful, after showing the code that 'did not work' from the user's perspective, to explain what you would like to see. A very useful tool for creating reproducible examples is the **reprex** package\index{reproducibility}. To highlight unexpected behavior, you can write completely reproducible code that demonstrates the issue and then use the `reprex()` function to create a copy of your code that can be pasted into a forum or other online space. Imagine you are trying to create a map of the world with blue sea and green land. You could simply ask how to do this in one of the places outlined in the previous section. However, it is likely that you will get a better response if you provide a reproducible example of what you have tried so far. The following code creates a map of the world with blue sea and green land, but the land is not filled in: ```r library(sf) library(spData) plot(st_geometry(world), col = "green") ``` If you post this code in a forum, it is likely that you will get a more specific and useful response. For example, someone might respond with the following code, which demonstrably solves the problem, as illustrated in Figure \@ref(fig:16-synthesis-reprex): ```r library(sf) library(spData) # use the bg argument to fill in the land plot(st_geometry(world), col = "green", bg = "lightblue") ``` ```{r 16-synthesis-reprex, out.width="49%", fig.show="hold", fig.cap="A map of the world with green land, illustrating a question with a reproducible example (left) and the solution (right).", echo=FALSE, message=FALSE, warning=FALSE} library(sf) library(spData) plot(st_geometry(world), col = "green") plot(st_geometry(world), col = "green", bg = "lightblue") ``` Exercise for the reader: copy the above code, run the command `reprex::reprex()` (or paste the command into the `reprex()` function call) and paste the output into a forum or other online space. A strength of open source and collaborative approaches to geocomputation is that they generate a vast and ever evolving body on knowledge, of which this book is a part. Demonstrating your own efforts to solve a problem, and providing a reproducible example of the problem, is a way of contributing to this body of knowledge. ### Defining and sketching the problem In some cases, you may not be able to find a solution to your problem online, or you may not be able to formulate a question that can be answered by a search engine. The best starting point in such cases, or when developing a new geocomputational methodology, may be a pen and paper (or equivalent digital sketching tools such as [Excalidraw](https://excalidraw.com/) and [tldraw](https://www.tldraw.com/) which allow collaborative sketching and rapid sharing of ideas). During the most creative early stages of methodological development work, software *of any kind* can slow down your thoughts and direct them away from important abstract thoughts. Framing the question with mathematics is also highly recommended, with reference to a minimal example that you can sketch 'before and after' versions of numerically. If you have the skills and if the problem warrants it, describing the approach algebraically can in some cases help develop effective implementations. ## Where to go next? {#next} As indicated in Section \@ref(gaps), the book has covered only a fraction of the R's geographic ecosystem, and there is much more to discover. We have progressed quickly, from geographic data models in Chapter \@ref(spatial-class), to advanced applications in Chapter \@ref(eco). Consolidation of skills learned, discovery of new packages and approaches for handling geographic data, and application of the methods to new datasets and domains are suggested future directions. This section expands on this general advice by suggesting specific 'next steps', highlighted in **bold** below. In addition to learning about further geographic methods and applications with R\index{R}, for example with reference to the work cited in the previous section, deepening your understanding of **R itself** is a logical next step. R's fundamental classes such as `data.frame` and `matrix` are the foundation of **sf** and **terra** classes, so studying them will improve your understanding of geographic data. This can be done with reference to documents that are part of R, and which can be found with the command `help.start()` and additional resources on the subject such as those by @wickham_advanced_2019 and @chambers_extending_2016. Another software-related direction for future learning is **discovering geocomputation with other languages**. There are good reasons for learning R as a language for geocomputation, as described in Chapter \@ref(intro), but it is not the only option.^[ R's strengths are particularly relevant to our definition of geocomputation due to its emphasis on scientific reproducibility, widespread use in academic research and unparalleled support for statistical modeling of geographic data. Furthermore, we advocate learning one language for geocomputation in depth before delving into other languages/frameworks because of the costs associated with context switching, and R is an excellent starting point on your geocomputational journey. ] It would be possible to study *Geocomputation with: Python*\index{Python}, *C++*, *JavaScript*, *Scala*\index{Scala} or *Rust*\index{Rust} in equal depth. Each has evolving geospatial capabilities. [**rasterio**](https://github.com/rasterio/rasterio), for example, is a Python package with similar functionality as the **terra** package used in this book. See [*Geocomputation with Python*](https://py.geocompx.org/), for an introduction to geocomputation with Python. Dozens of geospatial libraries have been developed in C++\index{C++}, including well-known libraries such as GDAL\index{GDAL} and GEOS\index{GEOS}, and less well-known libraries such as the **[Orfeo Toolbox](https://github.com/orfeotoolbox/OTB)** for processing remote sensing (raster) data. [**Turf.js**](https://github.com/Turfjs/turf) is an example of the potential for doing geocomputation with JavaScript. [GeoTrellis](https://geotrellis.io/) provides functions for working with raster and vector data in the Java-based language Scala. And [WhiteBoxTools](https://github.com/jblindsay/whitebox-tools) provides an example of a rapidly evolving command line GIS implemented in Rust. \index{Rust} \index{WhiteboxTools} Each of these packages/libraries/languages has advantages for geocomputation and there are many more to discover, as documented in the curated list of open source geospatial resources [Awesome-Geospatial](https://github.com/sacridini/Awesome-Geospatial). There is more to geocomputation\index{geocomputation} than software, however. We can recommend **exploring and learning new research topics and methods** from academic and theoretical perspectives. Many methods that have been written about have yet to be implemented. Learning about geographic methods and potential applications can therefore be rewarding, before writing any code. An example of geographic methods that are increasingly implemented in R is sampling strategies for scientific applications. A next step in this case is to read-up on relevant articles in the area such as @brus_sampling_2018, which is accompanied by reproducible code and tutorial content hosted at [github.com/DickBrus/TutorialSampling4DSM](https://github.com/DickBrus/TutorialSampling4DSM). ## The open source approach {#benefit} This is a technical book, so it makes sense for the next steps, outlined in the previous section, to also be technical. However, there are wider issues worth considering in this final section, which returns to our definition of geocomputation\index{geocomputation}. One of the elements of the term introduced in Chapter \@ref(intro) was that geographic methods should have a positive impact. Of course, how to define and measure 'positive' is a subjective, philosophical question that is beyond the scope of this book. Regardless of your worldview, consideration of the impacts of geocomputational work is a useful exercise: the potential for positive impacts can provide a powerful motivation for future learning and, conversely, new methods can open-up many possible fields of application. These considerations lead to the conclusion that geocomputation is part of a wider 'open source approach'. Section \@ref(what-is-geocomputation) presented other terms that mean roughly the same thing as geocomputation, including geographic data science\index{data science} (GDS) and 'GIScience'. Both capture the essence of working with geographic data, but geocomputation has advantages: it concisely captures the 'computational' way of working with geographic data advocated in this book --- implemented in code and therefore encouraging reproducibility --- and builds on desirable ingredients of its early definition [@openshaw_geocomputation_2000]: - The *creative* use of geographic data - Application to *real-world problems* - Building 'scientific' tools - Reproducibility\index{reproducibility} We added the final ingredient: reproducibility was barely mentioned in early work on geocomputation, yet a strong case can be made for it being a vital component of the first two ingredients. Reproducibility\index{reproducibility}: - Encourages *creativity* by shifting the focus away from the basics (which are readily available through shared code) and toward applications - Discourages people from 'reinventing the wheel': there is no need to redo what others have done if their methods can be used by others - Makes research more conducive to real-world applications, by enabling anyone in any sector to apply one's methods in new areas If reproducibility is the defining asset of geocomputation (or command line GIS), it is worth considering what makes it reproducible. This brings us to the 'open source approach', which has three main components: - A command line interface\index{command line interface} (CLI), encouraging scripts recording geographic work to be shared and reproduced - Open source software, which can be inspected and potentially improved by anyone in the world - An active user and developer community, which collaborates and self-organizes to build complementary and modular tools Like the term geocomputation\index{geocomputation}, the open source approach is more than a technical entity. It is a community composed of people interacting daily with shared aims: to produce high-performance tools, free from commercial or legal restrictions, that are accessible for anyone to use. The open source approach to working with geographic data has advantages that transcend the technicalities of how the software works, encouraging learning, collaboration and an efficient division of labor. There are many ways to engage in this community, especially with the emergence of code hosting sites, such as GitHub, which encourage communication and collaboration. A good place to start is simply browsing through some of the source code, 'issues' and 'commits' in a geographic package of interest. A quick glance at the `r-spatial/sf` GitHub repository, which hosts the code underlying the **sf**\index{sf} package, shows that 100+ people have contributed to the codebase and documentation. Dozens more people have contributed by asking questions and by contributing to 'upstream' packages that **sf** uses. More than 1,500 issues have been closed on its [issue tracker](https://github.com/r-spatial/sf/issues), representing a huge amount of work to make **sf** faster, more stable and user-friendly. This example, from just one package out of dozens, shows the scale of the intellectual operation underway to make R a highly effective and continuously evolving language for geocomputation. It is instructive to watch the incessant development activity happen in public fora such as GitHub, but it is even more rewarding to become an active participant. This is one of the greatest features of the open source approach: it encourages people to get involved. This book is a result of the open source approach: it was motivated by the amazing developments in R's geographic capabilities over the last two decades, but made practically possible by dialogue and code-sharing on platforms for collaboration. We hope that in addition to disseminating useful methods for working with geographic data, this book inspires you to take a more open source approach. ================================================ FILE: CITATION.bib ================================================ @book{lovelace_geocomputation_2025, title = {Geocomputation with {{R}}}, isbn = {9781032248882}, edition = {Second}, abstract = {Book on geographic data with R.}, publisher = {{CRC Press}}, author = {Lovelace, Robin and Nowosad, Jakub and Muenchow, Jannes}, year = {2025} } ================================================ FILE: CITATION_ed1.bib ================================================ @book{lovelace_geocomputation_2019, title = {Geocomputation with {{R}}}, isbn = {1-138-30451-4}, abstract = {Book on geographic data with R.}, publisher = {{CRC Press}}, author = {Lovelace, Robin and Nowosad, Jakub and Muenchow, Jannes}, year = {2019} } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the Contributor Covenant (https://www.contributor-covenant.org), version 1.0.0, available at https://contributor-covenant.org/version/1/0/0/. ================================================ FILE: DESCRIPTION ================================================ Type: Compendium Package: Geocomputation with R Title: Geocomputation with R Version: 0.0.5 Authors@R: c(person("Robin", "Lovelace", role = c("aut")), person("Jakub", "Nowosad", email = "nowosad.jakub@gmail.com", role = c("aut", "cre")), person("Jannes", "Muenchow", role = c("aut"))) Description: Open source book on R for reproducible, geographic research License: CC BY-NC-ND 4.0 Imports: bookdown, downlit, bslib, geocompkg, spData, metathis, magick Remotes: geocompx/geocompkg, mtennekes/tmap Encoding: UTF-8 LazyData: true ================================================ FILE: LICENSE.md ================================================ ## creative commons # Attribution-NonCommercial-NoDerivatives 4.0 International Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. ### Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). ## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. ### Section 1 – Definitions. a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. ### Section 2 – Scope. a. ___License grant.___ 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and B. produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. __Term.__ The term of this Public License is specified in Section 6(a). 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 5. __Downstream recipients.__ A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. ___Other rights.___ 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. ### Section 3 – License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. ___Attribution.___ 1. If You Share the Licensed Material, You must: A. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. ### Section 4 – Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. ### Section 5 – Disclaimer of Warranties and Limitation of Liability. a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. ### Section 6 – Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. ### Section 7 – Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. ### Section 8 – Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. > > Creative Commons may be contacted at creativecommons.org ================================================ FILE: README.Rmd ================================================ --- output: github_document --- ```{r, echo = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "images/" ) is_online = curl::has_internet() ``` # Geocomputation with R [![Binder](http://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) [![RstudioCloud](images/cloud.png)](https://rstudio.cloud/project/1642300) [![Actions](https://github.com/geocompx/geocompr/workflows/Render/badge.svg)](https://github.com/geocompx/geocompr/actions) [![Docker](https://img.shields.io/docker/pulls/geocompr/geocompr?style=plastic)](https://github.com/geocompx/docker/) [![discord](https://img.shields.io/discord/878051191374876683?label=discord&logo=Discord&color=blue)](https://discord.com/invite/PMztXYgNxp) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=84222786&machine=basicLinux32gb&devcontainer_path=.devcontainer.json&location=WestEurope) ## Introduction This repository hosts the code underlying Geocomputation with R, a book by [Robin Lovelace](https://www.robinlovelace.net/), [Jakub Nowosad](https://jakubnowosad.com/), and [Jannes Muenchow](https://github.com/jannes-m). If you find the contents useful, please [cite it](https://github.com/geocompx/geocompr/raw/main/CITATION.bib) as follows: > Lovelace, Robin, Jakub Nowosad and Jannes Muenchow (2025). Geocomputation with R. The R Series. CRC Press. To learn more about the second edition of the book, see the ["Second edition of Geocomputation with R is complete" blog post](https://geocompx.org/post/2024/geocompr2-bp3/). The first version of the book has been published by [CRC Press](https://www.crcpress.com/9781138304512) in the [R Series](https://www.routledge.com/Chapman--HallCRC-The-R-Series/book-series/CRCTHERSER) and can be viewed online at [bookdown.org](https://bookdown.org/robinlovelace/geocompr/). Read the latest version at [r.geocompx.org](https://r.geocompx.org/). Contributions are very welcome. ## Contributing We encourage contributions on any part of the book, including: - improvements to the text, e.g., clarifying unclear sentences, fixing typos (see guidance from [Yihui Xie](https://yihui.org/en/2013/06/fix-typo-in-documentation/)); - changes to the code, e.g., to do things in a more efficient way; - suggestions on content (see the project's [issue tracker](https://github.com/geocompx/geocompr/issues)); - improvements to and alternative approaches in the Geocompr solutions booklet hosted at [r.geocompx.org/solutions](https://r.geocompx.org/solutions) (see a blog post on how to update solutions in files such as [_01-ex.Rmd](https://github.com/geocompx/geocompr/blob/main/_01-ex.Rmd) [here](https://geocompx.org/post/2022/geocompr-solutions/)) See [our-style.md](https://github.com/geocompx/geocompr/blob/main/misc/our-style.md) for the book's style. ```{r contributors, include=FALSE} contributors = source("code/list-contributors.R")[[1]] # save for future reference: readr::write_csv(contributors, "extdata/contributors.csv") # table view: # knitr::kable(contributors, caption = "Contributors to Geocomputation with R") # text view c_txt = contributors$name c_url = contributors$link c_rmd = paste0("[", c_txt, "](", c_url, ")") contributors_text = paste0(c_rmd, collapse = ", ") ``` Many thanks to all contributors to the book so far via GitHub (this list will update automatically): `r contributors_text`. During the project we aim to contribute 'upstream' to the packages that make geocomputation with R possible. This impact is recorded in [`our-impact.csv`](https://github.com/geocompx/geocompr/blob/main/misc/our-impact.csv). ## Downloading the source code The recommended way to get the source code underlying Geocomputation with R on your computer is by cloning the repo. You can can that on any computer with [Git](https://github.com/git-guides/install-git) installed with the following command: ```bash git clone https://github.com/geocompx/geocompr.git ``` An alternative approach, which we recommend for people who want to contribute to open source projects hosted on GitHub, is to install the [`gh` CLI tool](https://github.com/cli/cli#installation). From there cloning a fork of the source code, that you can change and share (including with Pull Requests to improve the book), can be done with the following command: ```bash gh repo fork geocompx/geocompr # (gh repo clone geocompx/geocompr # also works) ``` Both of those methods require you to have Git installed. If not, you can download the book's source code from the URL https://github.com/geocompx/geocompr/archive/refs/heads/main.zip . Download/unzip the source code from the R command line to increase reproducibility and reduce time spent clicking around: ```{r dl-unzip} #| eval=FALSE u = "https://github.com/geocompx/geocompr/archive/refs/heads/main.zip" f = basename(u) download.file(u, f) # download the file unzip(f) # unzip it file.rename(f, "geocompr") # rename the directory rstudioapi::openProject("geococompr") # or open the folder in vscode / other IDE ``` ## Reproducing the book in R/RStudio/VS Code To ease reproducibility, we created the `geocompkg` package. Install it with the following commands: ```{r readme-install-github} #| eval=FALSE install.packages("remotes") # To reproduce the first Part (chapters 1 to 8): install.packages("geocompkg", repos = c("https://geocompr.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE) ``` Installing `geocompkg` will also install core packages required for reproducing **Part I of the book** (chapters 1 to 8). Note: you may also need to install [system dependencies](https://github.com/r-spatial/sf#installing) if you're running Linux (recommended) or Mac operating systems. You also need to have the [**remotes**](https://github.com/r-lib/remotes/) package installed: To reproduce book **in its entirety**, run the following command (which installs additional 'Suggests' packages, this may take some time to run!): ```{r readme-install-github-2, message=FALSE, results='hide'} #| eval=FALSE # Install packages to fully reproduce book (may take several minutes): options(repos = c( geocompx = "https://geocompx.r-universe.dev", cran = "https://cloud.r-project.org/" )) # From geocompx.r-universe.dev (recommended): install.packages("geocompkg", dependencies = TRUE) # Alternatively from GitHub: remotes::install_github("geocompx/geocompkg", dependencies = TRUE) ``` You need a recent version of the GDAL, GEOS, PROJ and udunits libraries installed for this to work on Mac and Linux. See the **sf** package's [README](https://github.com/r-spatial/sf) for information on that. After the dependencies have been installed you should be able to build and view a local version the book with: ```{r readme-render-book} #| eval=FALSE # Change this depending on where you have the book code stored: rstudioapi::openProject("~/Downloads/geocompr") # or code /location/of/geocompr in the system terminal # or cd /location/of/geocompr then R in the system terminal, then: bookdown::render_book("index.Rmd") # to build the book browseURL("_book/index.html") # to view it # Or, to serve a live preview the book and observe impact of changes: bookdown::serve_book(".") ``` ```{r gen-code, results='hide', echo=FALSE} #| eval=FALSE geocompkg:::generate_chapter_code() ``` ## Geocompr in a devcontainer A great feature of VS Code is [devcontainers](https://code.visualstudio.com/docs/remote/containers), which allow you to develop in an isolated Docker container. If you have VS Code and the necessary dependencies installed on your computer, you can build Geocomputation with R in a devcontainer as shown below (see [#873](https://github.com/geocompx/geocompr/issues/873) for details): ![](https://user-images.githubusercontent.com/1825120/193398022-bbcfbfda-5d57-4c57-8db3-ed1fdb4a07be.png) ## Geocompr in Binder For many people the quickest way to get started with Geocomputation with R is in your web browser via Binder. To see an interactive RStudio Server instance click on the following button, which will open [mybinder.org](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) with an R installation that has all the dependencies needed to reproduce the book: [![Launch Rstudio Binder](http://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) You can also have a play with the repository in RStudio Cloud by clicking on this link (requires log-in): [![Launch Rstudio Cloud](images/cloud.png)](https://rstudio.cloud/project/1642300) ## Geocomputation with R in a Docker container See the [geocompx/docker](https://github.com/geocompx/docker) repository for details. ## Reproducing this README To reduce the book's dependencies, scripts to be run infrequently to generate input for the book are run on creation of this README. The additional packages required for this can be installed as follows: ```{r extra-pkgs, message=FALSE, eval=FALSE} source("code/extra-pkgs.R") ``` With these additional dependencies installed, you should be able to run the following scripts, which create content for the book, that we've removed from the main book build to reduce package dependencies and the book's build time: ```{r source-readme, eval=FALSE} source("code/01-cranlogs.R") source("code/sf-revdep.R") source("code/09-urban-animation.R") source("code/09-map-pkgs.R") ``` Note: the `.Rproj` file is configured to build a website not a single page. To reproduce this [README](https://github.com/geocompx/geocompr/blob/main/README.Rmd) use the following command: ```{r render-book, eval=FALSE} rmarkdown::render("README.Rmd", output_format = "github_document", output_file = "README.md") ``` ```{r scripts, eval=FALSE, echo=FALSE} # We aim to make every script in the `code` folder reproducible. # To check they can all be reproduced run the following: # Aim: test reproducibility of scripts script_names = list.files("code", full.names = T) avoid = "pkgs|anim|us|saga|sliver|tsp|parti|polycent|cv|svm|data|location|eco|rf|cran|hex" dontrun = grepl(avoid, script_names) script_names = script_names[!dontrun] counter = 0 for(i in script_names[45:length(script_names)]) { counter = counter + 1 print(paste0("Script number ", counter, ": ", i)) source(i) } ``` ```{r gen-stats, echo=FALSE, message=FALSE, warning=FALSE, eval=FALSE} # source("code/generate-chapter-code.R") book_stats = readr::read_csv("extdata/word-count-time.csv", col_types = ("iiDd")) # to prevent excessive chapter count if (Sys.Date() > max(book_stats$date) + 5) { book_stats_new = geocompkg:::generate_book_stats() book_stats = bind_rows(book_stats, book_stats_new) readr::write_csv(book_stats, "extdata/word-count-time.csv") } book_stats = dplyr::filter(book_stats, chapter <= 15) library(ggplot2) book_stats$chapter = formatC(book_stats$chapter, width = 2, format = "d", flag = "0") book_stats$chapter = fct_rev(as.factor(book_stats$chapter)) book_stats$n_pages = book_stats$n_words / 300 ``` ```{r bookstats, warning=FALSE, echo=FALSE, fig.width=8, fig.height=5, eval=FALSE} ggplot(book_stats) + geom_area(aes(date, n_pages, fill = chapter), position = "stack") + ylab("Estimated number of pages") + xlab("Date") + scale_x_date(date_breaks = "2 month", limits = c(min(book_stats$date), as.Date("2018-10-01")), date_labels = "%b %Y") + coord_cartesian(ylim = c(0, 350)) ``` ## Citations The main packages used in this book are cited from `packages.bib`. Other citations are stored online using Zotero. If you would like to add to the references, please use Zotero, join the [open group](https://www.zotero.org/groups/418217/energy-and-transport) add your citation to the open [geocompr library](https://www.zotero.org/groups/418217/energy-and-transport/items/collectionKey/9K6FRP6N). We use the following citation key format: ``` [auth:lower]_[veryshorttitle:lower]_[year] ``` This can be set from inside Zotero desktop with the Better Bibtex plugin installed (see [github.com/retorquere/zotero-better-bibtex](https://github.com/retorquere/zotero-better-bibtex)) by selecting the following menu options (with the shortcut `Alt+E` followed by `N`), and as illustrated in the figure below: ``` Edit > Preferences > Better Bibtex ``` ![](images/zotero-settings.png) Zotero settings: these are useful if you want to add references. When you export the citations as a .bib file from Zotero, use the `Better BibTex` (not `BibLaTeX`) format. We use Zotero because it is a powerful open source reference manager that integrates well with citation tools in VS Code and RStudio. ================================================ FILE: README.md ================================================ # Geocomputation with R [![Binder](http://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) [![RstudioCloud](images/cloud.png)](https://rstudio.cloud/project/1642300) [![Actions](https://github.com/geocompx/geocompr/workflows/Render/badge.svg)](https://github.com/geocompx/geocompr/actions) [![Docker](https://img.shields.io/docker/pulls/geocompr/geocompr?style=plastic)](https://github.com/geocompx/docker/) [![discord](https://img.shields.io/discord/878051191374876683?label=discord&logo=Discord&color=blue)](https://discord.com/invite/PMztXYgNxp) [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=84222786&machine=basicLinux32gb&devcontainer_path=.devcontainer.json&location=WestEurope) ## Introduction This repository hosts the code underlying Geocomputation with R, a book by [Robin Lovelace](https://www.robinlovelace.net/), [Jakub Nowosad](https://jakubnowosad.com/), and [Jannes Muenchow](https://github.com/jannes-m). If you find the contents useful, please [cite it](https://github.com/geocompx/geocompr/raw/main/CITATION.bib) as follows: > Lovelace, Robin, Jakub Nowosad and Jannes Muenchow (2025). > Geocomputation with R. The R Series. CRC Press. To learn more about the second edition of the book, see the [“Second edition of Geocomputation with R is complete” blog post](https://geocompx.org/post/2024/geocompr2-bp3/). The first version of the book has been published by [CRC Press](https://www.crcpress.com/9781138304512) in the [R Series](https://www.routledge.com/Chapman--HallCRC-The-R-Series/book-series/CRCTHERSER) and can be viewed online at [bookdown.org](https://bookdown.org/robinlovelace/geocompr/). Read the latest version at [r.geocompx.org](https://r.geocompx.org/). Contributions are very welcome. ## Contributing We encourage contributions on any part of the book, including: - improvements to the text, e.g., clarifying unclear sentences, fixing typos (see guidance from [Yihui Xie](https://yihui.org/en/2013/06/fix-typo-in-documentation/)); - changes to the code, e.g., to do things in a more efficient way; - suggestions on content (see the project’s [issue tracker](https://github.com/geocompx/geocompr/issues)); - improvements to and alternative approaches in the Geocompr solutions booklet hosted at [r.geocompx.org/solutions](https://r.geocompx.org/solutions) (see a blog post on how to update solutions in files such as [\_01-ex.Rmd](https://github.com/geocompx/geocompr/blob/main/_01-ex.Rmd) [here](https://geocompx.org/post/2022/geocompr-solutions/)) See [our-style.md](https://github.com/geocompx/geocompr/blob/main/misc/our-style.md) for the book’s style. Many thanks to all contributors to the book so far via GitHub (this list will update automatically): [prosoitos](https://github.com/prosoitos), [tibbles-and-tribbles](https://github.com/tibbles-and-tribbles), [florisvdh](https://github.com/florisvdh), [babayoshihiko](https://github.com/babayoshihiko), [katygregg](https://github.com/katygregg), [Lvulis](https://github.com/Lvulis), [rsbivand](https://github.com/rsbivand), [iod-ine](https://github.com/iod-ine), [KiranmayiV](https://github.com/KiranmayiV), [cuixueqin](https://github.com/cuixueqin), [defuneste](https://github.com/defuneste), [smkerr](https://github.com/smkerr), [zmbc](https://github.com/zmbc), [marcosci](https://github.com/marcosci), [darrellcarvalho](https://github.com/darrellcarvalho), [dcooley](https://github.com/dcooley), [FlorentBedecarratsNM](https://github.com/FlorentBedecarratsNM), [erstearns](https://github.com/erstearns), [appelmar](https://github.com/appelmar), [MikeJohnPage](https://github.com/MikeJohnPage), [eyesofbambi](https://github.com/eyesofbambi), [krystof236](https://github.com/krystof236), [nickbearman](https://github.com/nickbearman), [tylerlittlefield](https://github.com/tylerlittlefield), [sdesabbata](https://github.com/sdesabbata), [howardbaik](https://github.com/howardbaik), [edzer](https://github.com/edzer), [pat-s](https://github.com/pat-s), [giocomai](https://github.com/giocomai), [KHwong12](https://github.com/KHwong12), [LaurieLBaker](https://github.com/LaurieLBaker), [eblondel](https://github.com/eblondel), [MarHer90](https://github.com/MarHer90), [mdsumner](https://github.com/mdsumner), [ahmohil](https://github.com/ahmohil), [richfitz](https://github.com/richfitz), [VLucet](https://github.com/VLucet), [wdearden](https://github.com/wdearden), [yihui](https://github.com/yihui), [adambhouston](https://github.com/adambhouston), [chihinl](https://github.com/chihinl), [cshancock](https://github.com/cshancock), [e-clin](https://github.com/e-clin), [ec-nebi](https://github.com/ec-nebi), [gregor-d](https://github.com/gregor-d), [jasongrahn](https://github.com/jasongrahn), [p-kono](https://github.com/p-kono), [pokyah](https://github.com/pokyah), [schuetzingit](https://github.com/schuetzingit), [tim-salabim](https://github.com/tim-salabim), [tszberkowitz](https://github.com/tszberkowitz), [vlarmet](https://github.com/vlarmet), [ateucher](https://github.com/ateucher), [annakrystalli](https://github.com/annakrystalli), [andtheWings](https://github.com/andtheWings), [kant](https://github.com/kant), [gavinsimpson](https://github.com/gavinsimpson), [Himanshuteli](https://github.com/Himanshuteli), [yutannihilation](https://github.com/yutannihilation), [jimr1603](https://github.com/jimr1603), [jbixon13](https://github.com/jbixon13), [jkennedyie](https://github.com/jkennedyie), [olyerickson](https://github.com/olyerickson), [yvkschaefer](https://github.com/yvkschaefer), [katiejolly](https://github.com/katiejolly), [kwhkim](https://github.com/kwhkim), [layik](https://github.com/layik), [mpaulacaldas](https://github.com/mpaulacaldas), [mtennekes](https://github.com/mtennekes), [mvl22](https://github.com/mvl22), [ganes1410](https://github.com/ganes1410). During the project we aim to contribute ‘upstream’ to the packages that make geocomputation with R possible. This impact is recorded in [`our-impact.csv`](https://github.com/geocompx/geocompr/blob/main/misc/our-impact.csv). ## Downloading the source code The recommended way to get the source code underlying Geocomputation with R on your computer is by cloning the repo. You can can that on any computer with [Git](https://github.com/git-guides/install-git) installed with the following command: ``` bash git clone https://github.com/geocompx/geocompr.git ``` An alternative approach, which we recommend for people who want to contribute to open source projects hosted on GitHub, is to install the [`gh` CLI tool](https://github.com/cli/cli#installation). From there cloning a fork of the source code, that you can change and share (including with Pull Requests to improve the book), can be done with the following command: ``` bash gh repo fork geocompx/geocompr # (gh repo clone geocompx/geocompr # also works) ``` Both of those methods require you to have Git installed. If not, you can download the book’s source code from the URL . Download/unzip the source code from the R command line to increase reproducibility and reduce time spent clicking around: ``` r u = "https://github.com/geocompx/geocompr/archive/refs/heads/main.zip" f = basename(u) download.file(u, f) # download the file unzip(f) # unzip it file.rename(f, "geocompr") # rename the directory rstudioapi::openProject("geococompr") # or open the folder in vscode / other IDE ``` ## Reproducing the book in R/RStudio/VS Code To ease reproducibility, we created the `geocompkg` package. Install it with the following commands: ``` r install.packages("remotes") # To reproduce the first Part (chapters 1 to 8): install.packages("geocompkg", repos = c("https://geocompr.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE) ``` Installing `geocompkg` will also install core packages required for reproducing **Part I of the book** (chapters 1 to 8). Note: you may also need to install [system dependencies](https://github.com/r-spatial/sf#installing) if you’re running Linux (recommended) or Mac operating systems. You also need to have the [**remotes**](https://github.com/r-lib/remotes/) package installed: To reproduce book **in its entirety**, run the following command (which installs additional ‘Suggests’ packages, this may take some time to run!): ``` r # Install packages to fully reproduce book (may take several minutes): options(repos = c( geocompx = "https://geocompx.r-universe.dev", cran = "https://cloud.r-project.org/" )) # From geocompx.r-universe.dev (recommended): install.packages("geocompkg", dependencies = TRUE) # Alternatively from GitHub: remotes::install_github("geocompx/geocompkg", dependencies = TRUE) ``` You need a recent version of the GDAL, GEOS, PROJ and udunits libraries installed for this to work on Mac and Linux. See the **sf** package’s [README](https://github.com/r-spatial/sf) for information on that. After the dependencies have been installed you should be able to build and view a local version the book with: ``` r # Change this depending on where you have the book code stored: rstudioapi::openProject("~/Downloads/geocompr") # or code /location/of/geocompr in the system terminal # or cd /location/of/geocompr then R in the system terminal, then: bookdown::render_book("index.Rmd") # to build the book browseURL("_book/index.html") # to view it # Or, to serve a live preview the book and observe impact of changes: bookdown::serve_book(".") ``` ## Geocompr in a devcontainer A great feature of VS Code is [devcontainers](https://code.visualstudio.com/docs/remote/containers), which allow you to develop in an isolated Docker container. If you have VS Code and the necessary dependencies installed on your computer, you can build Geocomputation with R in a devcontainer as shown below (see [\#873](https://github.com/geocompx/geocompr/issues/873) for details): ![](https://user-images.githubusercontent.com/1825120/193398022-bbcfbfda-5d57-4c57-8db3-ed1fdb4a07be.png) ## Geocompr in Binder For many people the quickest way to get started with Geocomputation with R is in your web browser via Binder. To see an interactive RStudio Server instance click on the following button, which will open [mybinder.org](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) with an R installation that has all the dependencies needed to reproduce the book: [![Launch Rstudio Binder](http://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) You can also have a play with the repository in RStudio Cloud by clicking on this link (requires log-in): [![Launch Rstudio Cloud](images/cloud.png)](https://rstudio.cloud/project/1642300) ## Geocomputation with R in a Docker container See the [geocompx/docker](https://github.com/geocompx/docker) repository for details. ## Reproducing this README To reduce the book’s dependencies, scripts to be run infrequently to generate input for the book are run on creation of this README. The additional packages required for this can be installed as follows: ``` r source("code/extra-pkgs.R") ``` With these additional dependencies installed, you should be able to run the following scripts, which create content for the book, that we’ve removed from the main book build to reduce package dependencies and the book’s build time: ``` r source("code/01-cranlogs.R") source("code/sf-revdep.R") source("code/09-urban-animation.R") source("code/09-map-pkgs.R") ``` Note: the `.Rproj` file is configured to build a website not a single page. To reproduce this [README](https://github.com/geocompx/geocompr/blob/main/README.Rmd) use the following command: ``` r rmarkdown::render("README.Rmd", output_format = "github_document", output_file = "README.md") ``` ## Citations The main packages used in this book are cited from `packages.bib`. Other citations are stored online using Zotero. If you would like to add to the references, please use Zotero, join the [open group](https://www.zotero.org/groups/418217/energy-and-transport) add your citation to the open [geocompr library](https://www.zotero.org/groups/418217/energy-and-transport/items/collectionKey/9K6FRP6N). We use the following citation key format: [auth:lower]_[veryshorttitle:lower]_[year] This can be set from inside Zotero desktop with the Better Bibtex plugin installed (see [github.com/retorquere/zotero-better-bibtex](https://github.com/retorquere/zotero-better-bibtex)) by selecting the following menu options (with the shortcut `Alt+E` followed by `N`), and as illustrated in the figure below: Edit > Preferences > Better Bibtex ![](images/zotero-settings.png) Zotero settings: these are useful if you want to add references. When you export the citations as a .bib file from Zotero, use the `Better BibTex` (not `BibLaTeX`) format. We use Zotero because it is a powerful open source reference manager that integrates well with citation tools in VS Code and RStudio. ================================================ FILE: _01-ex.Rmd ================================================ E1. Think about the terms 'GIS'\index{GIS}, 'GDS' and 'geocomputation' described above. Which (if any) best describes the work you would like to do using geo* methods and software and why? E2. Provide three reasons for using a scriptable language such as R for geocomputation instead of using a graphical user interface (GUI) based GIS such as QGIS\index{QGIS}. E3. In the year 2000, Stan Openshaw wrote that geocomputation involved "practical work that is beneficial or useful" to others. Think about a practical problem and possible solutions that could be informed with new evidence derived from the analysis, visualization or modeling of geographic data. With a pen and paper (or computational equivalent) sketch inputs and possible outputs illustrating how geocomputation could help. ================================================ FILE: _02-ex.Rmd ================================================ ```{r 02-ex-e0, message=FALSE} library(sf) library(spData) library(terra) ``` E1. Use `summary()` on the geometry column of the `world` data object that is included in the **spData** package. What does the output tell us about: - Its geometry type? - The number of countries? - Its coordinate reference system (CRS)? ```{r 02-ex-e1} summary(world) # - Its geometry type? # multipolygon # - The number of countries? # 177 # - Its coordinate reference system (CRS)? # epsg:4326 ``` E2. Run the code that 'generated' the map of the world in Section 2.2.3 (Basic map-making). Find two similarities and two differences between the image on your computer and that in the book. - What does the `cex` argument do (see `?plot`)? - Why was `cex` set to the `sqrt(world$pop) / 10000`? - Bonus: experiment with different ways to visualize the global population. ```{r 02-ex-e2} plot(world["continent"], reset = FALSE) cex = sqrt(world$pop) / 10000 world_cents = st_centroid(world, of_largest = TRUE) plot(st_geometry(world_cents), add = TRUE, cex = cex) # - What does the `cex` argument do (see `?plot`)? # It specifies the size of the circles # - Why was `cex` set to the `sqrt(world$pop) / 10000`? # So the circles would be visible for small countries but not too large for large countries, also because area increases as a linear function of the square route of the diameter defined by `cex` # - Bonus: experiment with different ways to visualize the global population. plot(st_geometry(world_cents), cex = world$pop / 1e9) plot(st_geometry(world_cents), cex = world$pop / 1e8) plot(world["pop"]) plot(world["pop"], logz = TRUE) # Similarities: global extent, colorscheme, relative size of circles # # Differences: projection (Antarctica is much smaller for example), graticules, location of points in the countries. # # To understand these differences read-over, run, and experiment with different argument values in this script: https://github.com/geocompx/geocompr/raw/main/code/02-contpop.R # # `cex` refers to the diameter of symbols plotted, as explained by the help page `?graphics::points`. It is an acronym for 'Chacter symbol EXpansion'. # It was set to the square route of the population divided by 10,000 because a) otherwise the symbols would not fit on the map and b) to make circle area proportional to population. ``` E3. Use `plot()` to create maps of Nigeria in context (see Section 2.2.3). - Adjust the `lwd`, `col` and `expandBB` arguments of `plot()`. - Challenge: read the documentation of `text()` and annotate the map. ```{r 02-ex-e3} nigeria = world[world$name_long == "Nigeria", ] plot(st_geometry(nigeria), expandBB = c(0, 0.2, 0.1, 1), col = "gray", lwd = 3) plot(world[0], add = TRUE) world_coords = st_coordinates(world_cents) text(world_coords, world$iso_a2) # Alternative answer: nigeria = world[world$name_long == "Nigeria", ] africa = world[world$continent == "Africa", ] plot(st_geometry(nigeria), col = "white", lwd = 3, main = "Nigeria in context", border = "lightgray", expandBB = c(0.5, 0.2, 0.5, 0.2)) plot(st_geometry(world), lty = 3, add = TRUE, border = "gray") plot(st_geometry(nigeria), col = "yellow", add = TRUE, border = "darkgray") a = africa[grepl("Niger", africa$name_long), ] ncentre = st_centroid(a) ncentre_num = st_coordinates(ncentre) text(x = ncentre_num[, 1], y = ncentre_num[, 2], labels = a$name_long) ``` E4. Create an empty `SpatRaster` object called `my_raster` with 10 columns and 10 rows. Assign random values between 0 and 10 to the new raster and plot it. ```{r 02-ex-e4, message = FALSE} my_raster = rast(ncol = 10, nrow = 10, vals = sample(0:10, size = 10 * 10, replace = TRUE)) plot(my_raster) ``` E5. Read-in the `raster/nlcd.tif` file from the **spDataLarge** package. What kind of information can you get about the properties of this file? ```{r 02-ex-e5, message = FALSE} nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) dim(nlcd) # dimensions res(nlcd) # resolution ext(nlcd) # extent nlyr(nlcd) # number of layers cat(crs(nlcd)) # CRS ``` E6. Check the CRS of the `raster/nlcd.tif` file from the **spDataLarge** package. What kind of information you can learn from it? ```{r 02-ex-e6, message = FALSE} cat(crs(nlcd)) ``` ```{asis 02-ex-e62, message = FALSE} The WKT above describes a two-dimensional projected coordinate reference system. It is based on the GRS 1980 ellipsoid with North American Datum 1983 and the Greenwich prime meridian. It used the Transverse Mercator projection to transform from geographic to projected CRS (UTM zone 12N). Its first axis is related to eastness, while the second one is related to northness, and both axes have units in meters. The SRID of the above CRS is "EPSG:26912". ``` ================================================ FILE: _03-ex.Rmd ================================================ For these exercises we will use the `us_states` and `us_states_df` datasets from the **spData** package. You must have attached the package, and other packages used in the attribute operations chapter (**sf**, **dplyr**, **terra**) with commands such as `library(spData)` before attempting these exercises: ```{r 03-ex-e0, include=TRUE, message=FALSE} library(sf) library(dplyr) library(terra) library(spData) data(us_states) data(us_states_df) ``` `us_states` is a spatial object (of class `sf`), containing geometry and a few attributes (including name, region, area, and population) of states within the contiguous United States. `us_states_df` is a data frame (of class `data.frame`) containing the name and additional variables (including median income and poverty level, for the years 2010 and 2015) of US states, including Alaska, Hawaii and Puerto Rico. The data comes from the United States Census Bureau, and is documented in `?us_states` and `?us_states_df`. E1. Create a new object called `us_states_name` that contains only the `NAME` column from the `us_states` object using either base R (`[`) or tidyverse (`select()`) syntax. What is the class of the new object and what makes it geographic? ```{r 03-ex-e1} us_states_name = us_states["NAME"] class(us_states_name) attributes(us_states_name) attributes(us_states_name$geometry) ``` ```{asis 03-ex-e1-asis} - It is of class `sf` and `data.frame`: it has 2 classes. - It is the `sf` class that makes in geographic. - More specifically it is the attributes of the object (`sf_column`) and the geometry column (such as `bbox`, `crs`) that make it geographic. ``` E2. Select columns from the `us_states` object which contain population data. Obtain the same result using a different command (bonus: try to find three ways of obtaining the same result). Hint: try to use helper functions, such as `contains` or `matches` from **dplyr** (see `?contains`). ```{r 03-ex-e2} us_states |> select(total_pop_10, total_pop_15) # or us_states |> select(starts_with("total_pop")) # or us_states |> select(contains("total_pop")) # or us_states |> select(matches("tal_p")) ``` E3. Find all states with the following characteristics (bonus: find *and* plot them): - Belong to the Midwest region. - Belong to the West region, have an area below 250,000 km^2^ *and* in 2015 a population greater than 5,000,000 residents (hint: you may need to use the function `units::set_units()` or `as.numeric()`). - Belong to the South region, had an area larger than 150,000 km^2^ and a total population in 2015 larger than 7,000,000 residents. ```{r 03-ex-e3} us_states |> filter(REGION == "Midwest") us_states |> filter(REGION == "West", AREA < units::set_units(250000, km^2), total_pop_15 > 5000000) # or us_states |> filter(REGION == "West", as.numeric(AREA) < 250000, total_pop_15 > 5000000) us_states |> filter(REGION == "South", AREA > units::set_units(150000, km^2), total_pop_15 > 7000000) # or us_states |> filter(REGION == "South", as.numeric(AREA) > 150000, total_pop_15 > 7000000) ``` E4. What was the total population in 2015 in the `us_states` dataset? What was the minimum and maximum total population in 2015? ```{r 03-ex-e4} us_states |> summarize(total_pop = sum(total_pop_15), min_pop = min(total_pop_15), max_pop = max(total_pop_15)) ``` E5. How many states are there in each region? ```{r 03-ex-e5} us_states |> group_by(REGION) |> summarize(nr_of_states = n()) ``` E6. What was the minimum and maximum total population in 2015 in each region? What was the total population in 2015 in each region? ```{r 03-ex-e6} us_states |> group_by(REGION) |> summarize(min_pop = min(total_pop_15), max_pop = max(total_pop_15), tot_pop = sum(total_pop_15)) ``` E7. Add variables from `us_states_df` to `us_states`, and create a new object called `us_states_stats`. What function did you use and why? Which variable is the key in both datasets? What is the class of the new object? ```{r 03-ex-e7} us_states_stats = us_states |> left_join(us_states_df, by = c("NAME" = "state")) class(us_states_stats) ``` E8. `us_states_df` has two more rows than `us_states`. How can you find them? (Hint: try to use the `dplyr::anti_join()` function.) ```{r 03-ex-e8} us_states_df |> anti_join(st_drop_geometry(us_states), by = c("state" = "NAME")) ``` E9. What was the population density in 2015 in each state? What was the population density in 2010 in each state? ```{r 03-ex-e9} us_states2 = us_states |> mutate(pop_dens_15 = total_pop_15/AREA, pop_dens_10 = total_pop_10/AREA) ``` E10. How much has population density changed between 2010 and 2015 in each state? Calculate the change in percentages and map them. ```{r 03-ex-e10} us_popdens_change = us_states2 |> mutate(pop_dens_diff_10_15 = pop_dens_15 - pop_dens_10, pop_dens_diff_10_15p = (pop_dens_diff_10_15/pop_dens_10) * 100) plot(us_popdens_change["pop_dens_diff_10_15p"]) ``` E11. Change the columns' names in `us_states` to lowercase. (Hint: helper functions - `tolower()` and `colnames()` may help.) ```{r 03-ex-e11} us_states %>% setNames(tolower(colnames(.))) ``` E12. Using `us_states` and `us_states_df` create a new object called `us_states_sel`. The new object should have only two variables: `median_income_15` and `geometry`. Change the name of the `median_income_15` column to `Income`. ```{r 03-ex-e12} us_states_sel = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> select(Income = median_income_15) ``` E13. Calculate the change in the number of residents living below the poverty level between 2010 and 2015 for each state. (Hint: See ?us_states_df for documentation on the poverty level columns.) Bonus: Calculate the change in the *percentage* of residents living below the poverty level in each state. ```{r 03-ex-e13} us_pov_change = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> mutate(pov_change = poverty_level_15 - poverty_level_10) # Bonus us_pov_pct_change = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> mutate(pov_pct_10 = (poverty_level_10 / total_pop_10) * 100, pov_pct_15 = (poverty_level_15 / total_pop_15) * 100) |> mutate(pov_pct_change = pov_pct_15 - pov_pct_10) ``` E14. What was the minimum, average and maximum state's number of people living below the poverty line in 2015 for each region? Bonus: What is the region with the largest increase in people living below the poverty line? ```{r 03-ex-e14} us_pov_change_reg = us_pov_change |> group_by(REGION) |> summarize(min_state_pov_15 = min(poverty_level_15), mean_state_pov_15 = mean(poverty_level_15), max_state_pov_15 = max(poverty_level_15)) # Bonus us_pov_change |> group_by(REGION) |> summarize(region_pov_change = sum(pov_change)) |> filter(region_pov_change == max(region_pov_change)) |> pull(REGION) |> as.character() ``` E15. Create a raster from scratch, with nine rows and columns and a resolution of 0.5 decimal degrees (WGS84). Fill it with random numbers. Extract the values of the four corner cells. ```{r 03-ex-e15} r = rast(nrow = 9, ncol = 9, res = 0.5, xmin = 0, xmax = 4.5, ymin = 0, ymax = 4.5, vals = rnorm(81)) # using cell IDs r[c(1, 9, 81 - 9 + 1, 81)] r[c(1, nrow(r)), c(1, ncol(r))] ``` E16. What is the most common class of our example raster `grain`? ```{r 03-ex-e16} grain = rast(system.file("raster/grain.tif", package = "spData")) freq(grain) |> arrange(-count )# the most common classes are silt and sand (13 cells) ``` E17. Plot the histogram and the boxplot of the `dem.tif` file from the **spDataLarge** package (`system.file("raster/dem.tif", package = "spDataLarge")`). ```{r 03-ex-e17} dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) hist(dem) boxplot(dem) # we can also use ggplot2 after converting SpatRaster to a data frame library(ggplot2) ggplot(as.data.frame(dem), aes(dem)) + geom_histogram() ggplot(as.data.frame(dem), aes(dem)) + geom_boxplot() ``` ================================================ FILE: _04-ex.Rmd ================================================ ```{r 04-ex-e0, include=TRUE, message=FALSE} library(sf) library(dplyr) library(spData) ``` E1. It was established in Section \@ref(spatial-vec) that Canterbury was the region of New Zealand containing most of the 101 highest points in the country. How many of these high points does the Canterbury region contain? **Bonus:** plot the result using the `plot()` function to show all of New Zealand, `canterbury` region highlighted in yellow, high points in Canterbury represented by red crosses (hint: `pch = 7`) and high points in other parts of New Zealand represented by blue circles. See the help page `?points` for details with an illustration of different `pch` values. ```{r 04-ex-e1} canterbury = nz |> filter(Name == "Canterbury") canterbury_height = nz_height[canterbury, ] nz_not_canterbury_height = nz_height[canterbury, , op = st_disjoint] nrow(canterbury_height) # answer: 70 plot(st_geometry(nz)) plot(st_geometry(canterbury), col = "yellow", add = TRUE) plot(nz_not_canterbury_height$geometry, pch = 1, col = "blue", add = TRUE) plot(canterbury_height$geometry, pch = 4, col = "red", add = TRUE) ``` E2. Which region has the second highest number of `nz_height` points, and how many does it have? ```{r 04-ex-e2} nz_height_count = aggregate(nz_height, nz, length) nz_height_combined = cbind(nz, count = nz_height_count$elevation) nz_height_combined |> st_drop_geometry() |> select(Name, count) |> arrange(desc(count)) |> slice(2) ``` E3. Generalizing the question to all regions: how many of New Zealand's 16 regions contain points which belong to the top 101 highest points in the country? Which regions? - Bonus: create a table listing these regions in order of the number of points and their name. ```{r 04-ex-e3} # Base R way: nz_height_count = aggregate(nz_height, nz, length) nz_height_combined = cbind(nz, count = nz_height_count$elevation) plot(nz_height_combined) # Tidyverse way: nz_height_joined = st_join(nz_height, nz |> select(Name)) # Calculate n. points in each region - this contains the result nz_height_counts = nz_height_joined |> group_by(Name) |> summarise(count = n()) # Optionally join results with nz geometries: nz_height_combined = left_join(nz, nz_height_counts |> sf::st_drop_geometry()) # plot(nz_height_combined) # Check: results identical to base R result # Generate a summary table nz_height_combined |> st_drop_geometry() |> select(Name, count) |> arrange(desc(count)) |> na.omit() ``` E4. Test your knowledge of spatial predicates by finding out and plotting how US states relate to each other and other spatial objects. The starting point of this exercise is to create an object representing Colorado state in the USA. Do this with the command `colorado = us_states[us_states$NAME == "Colorado",]` (base R) or with the `filter()` function (tidyverse) and plot the resulting object in the context of US states. - Create a new object representing all the states that geographically intersect with Colorado and plot the result (hint: the most concise way to do this is with the subsetting method `[`). - Create another object representing all the objects that touch (have a shared boundary with) Colorado and plot the result (hint: remember you can use the argument `op = st_intersects` and other spatial relations during spatial subsetting operations in base R). - Bonus: create a straight line from the centroid of the District of Columbia near the East coast to the centroid of California near the West coast of the USA (hint: functions `st_centroid()`, `st_union()` and `st_cast()` described in Chapter 5 may help) and identify which states this long East-West line crosses. ```{r 04-ex-4-1} colorado = us_states[us_states$NAME == "Colorado", ] plot(us_states$geometry) plot(colorado$geometry, col = "gray", add = TRUE) ``` ```{r 04-ex-4-2} intersects_with_colorado = us_states[colorado, , op = st_intersects] plot(us_states$geometry, main = "States that intersect with Colorado") plot(intersects_with_colorado$geometry, col = "gray", add = TRUE) ``` ```{r 04-ex-4-3} # Alternative but more verbose solutions # 2: With intermediate object, one list for each state sel_intersects_colorado = st_intersects(us_states, colorado) sel_intersects_colorado_list = lengths(sel_intersects_colorado) > 0 intersects_with_colorado = us_states[sel_intersects_colorado_list, ] # 3: With intermediate object, one index for each state sel_intersects_colorado2 = st_intersects(colorado, us_states) sel_intersects_colorado2 us_states$NAME[unlist(sel_intersects_colorado2)] # 4: With tidyverse us_states |> st_filter(y = colorado, .predicate = st_intersects) ``` ```{r 04-ex-4-4} touches_colorado = us_states[colorado, , op = st_touches] plot(us_states$geometry, main = "States that touch Colorado") plot(touches_colorado$geometry, col = "gray", add = TRUE) ``` ```{r 04-ex-4-5} washington_to_cali = us_states |> filter(grepl(pattern = "Columbia|Cali", x = NAME)) |> st_centroid() |> st_union() |> st_cast("LINESTRING") states_crossed = us_states[washington_to_cali, , op = st_crosses] states_crossed$NAME plot(us_states$geometry, main = "States crossed by a straight line\n from the District of Columbia to central California") plot(states_crossed$geometry, col = "gray", add = TRUE) plot(washington_to_cali, add = TRUE) ``` E5. Use `dem = rast(system.file("raster/dem.tif", package = "spDataLarge"))`, and reclassify the elevation in three classes: low (<300), medium and high (>500). Secondly, read the NDVI raster (`ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge"))`) and compute the mean NDVI and the mean elevation for each altitudinal class. ```{r 04-ex-e5} library(terra) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) #1 dem_rcl = matrix(c(-Inf, 300, 0, 300, 500, 1, 500, Inf, 2), ncol = 3, byrow = TRUE) dem_reclass = classify(dem, dem_rcl) levels(dem_reclass) = data.frame(id = 0:2, cats = c("low", "medium", "high")) plot(dem_reclass) #2 zonal(c(dem, ndvi), dem_reclass, fun = "mean") ``` E6. Apply a line detection filter to `rast(system.file("ex/logo.tif", package = "terra"))`. Plot the result. Hint: Read `?terra::focal()`. ```{r 04-ex-e6} # from the focal help page (?terra::focal()): # Laplacian filter: filter=matrix(c(0,1,0,1,-4,1,0,1,0), nrow=3) # Sobel filters (for edge detection): # fx=matrix(c(-1,-2,-1,0,0,0,1,2,1), nrow=3) # fy=matrix(c(1,0,-1,2,0,-2,1,0,-1), nrow=3) # just retrieve the first channel of the R logo r = rast(system.file("ex/logo.tif", package = "terra")) # compute the Sobel filter filter_x = matrix(c(-1, -2, -1, 0, 0, 0, 1, 2, 1), nrow = 3) sobel_x = focal(r, w = filter_x) plot(sobel_x, col = c("white", "black")) filter_y = matrix(c(1, 0, -1, 2, 0, -2, 1, 0, -1), nrow = 3) sobel_y = focal(r, w = filter_y) plot(sobel_y, col = c("black", "white")) ``` E7. Calculate the Normalized Difference Water Index (NDWI; `(green - nir)/(green + nir)`) of a Landsat image. Use the Landsat image provided by the **spDataLarge** package (`system.file("raster/landsat.tif", package = "spDataLarge")`). Also, calculate a correlation between NDVI and NDWI for this area (hint: you can use the `layerCor()` function). ```{r 04-ex-e7} file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(file) ndvi_fun = function(nir, red){ (nir - red) / (nir + red) } ndvi_rast = lapp(multi_rast[[c(4, 3)]], fun = ndvi_fun) plot(ndvi_rast) ndwi_fun = function(green, nir){ (green - nir) / (green + nir) } ndwi_rast = lapp(multi_rast[[c(2, 4)]], fun = ndwi_fun) plot(ndwi_rast) two_rasts = c(ndvi_rast, ndwi_rast) names(two_rasts) = c("ndvi", "ndwi") # correlation -- option 1 layerCor(two_rasts, fun = "cor") # correlation -- option 2 two_rasts_df = as.data.frame(two_rasts) cor(two_rasts_df$ndvi, two_rasts_df$ndwi) ``` E8. A StackOverflow [post (stackoverflow.com/questions/35555709)](https://stackoverflow.com/questions/35555709/global-raster-of-geographic-distances) shows how to compute distances to the nearest coastline using `raster::distance()`. Try to do something similar but with `terra::distance()`: retrieve a digital elevation model of Spain, and compute a raster which represents distances to the coast across the country (hint: use `geodata::elevation_30s()`). Convert the resulting distances from meters to kilometers. Note: it may be wise to increase the cell size of the input raster to reduce compute time during this operation (`aggregate()`). ```{r 04-ex-e8} # Fetch the DEM data for Spain spain_dem = geodata::elevation_30s(country = "Spain", path = ".", mask = FALSE) # Reduce the resolution by a factor of 20 to speed up calculations spain_dem = terra::aggregate(spain_dem, fact = 20) # According to the documentation, terra::distance() will calculate distance # for all cells that are NA to the nearest cell that are not NA. To calculate # distance to the coast, we need a raster that has NA values over land and any # other value over water water_mask = is.na(spain_dem) water_mask[water_mask == 0] = NA # Use the distance() function on this mask to get distance to the coast distance_to_coast = distance(water_mask) # convert distance into km distance_to_coast_km = distance_to_coast / 1000 # Plot the result plot(distance_to_coast_km, main = "Distance to the coast (km)") ``` E9. Try to modify the approach used in the above exercise by weighting the distance raster with the elevation raster; every 100 altitudinal meters should increase the distance to the coast by 10 km. Next, compute and visualize the difference between the raster created using the Euclidean distance (E8) and the raster weighted by elevation. ```{r 04-ex-e9} # now let's weight each 100 altitudinal meters by an additional distance of 10 km distance_to_coast_km2 = distance_to_coast_km + ((spain_dem / 100) * 10) # plot the result plot(distance_to_coast_km2) # visualize the difference plot(distance_to_coast_km - distance_to_coast_km2) ``` ================================================ FILE: _05-ex.Rmd ================================================ ```{r 05-ex-e0, message=FALSE} library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ``` E1. Generate and plot simplified versions of the `nz` dataset. Experiment with different values of `keep` (ranging from 0.5 to 0.00005) for `ms_simplify()` and `dTolerance` (from 100 to 100,000) `st_simplify()`. - At what value does the form of the result start to break down for each method, making New Zealand unrecognizable? - Advanced: What is different about the geometry type of the results from `st_simplify()` compared with the geometry type of `ms_simplify()`? What problems does this create and how can this be resolved? ```{r 05-ex-e1} plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.5)) plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.05)) # Starts to breakdown here at 0.5% of the points: plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.005)) # At this point no further simplification changes the result plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.0005)) plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.00005)) plot(st_simplify(st_geometry(nz), dTolerance = 100)) plot(st_simplify(st_geometry(nz), dTolerance = 1000)) # Starts to breakdown at 10 km: plot(st_simplify(st_geometry(nz), dTolerance = 10000)) plot(st_simplify(st_geometry(nz), dTolerance = 100000)) plot(st_simplify(st_geometry(nz), dTolerance = 100000, preserveTopology = TRUE)) # Problem: st_simplify returns POLYGON and MULTIPOLYGON results, affecting plotting # Cast into a single geometry type to resolve this nz_simple_poly = st_simplify(st_geometry(nz), dTolerance = 10000) |> st_sfc() |> st_cast("POLYGON") nz_simple_multipoly = st_simplify(st_geometry(nz), dTolerance = 10000) |> st_sfc() |> st_cast("MULTIPOLYGON") plot(nz_simple_poly) length(nz_simple_poly) nrow(nz) ``` E2. In the first exercise in Chapter Spatial Data Operations it was established that Canterbury region had 70 of the 101 highest points in New Zealand. Using `st_buffer()`, how many points in `nz_height` are within 100 km of Canterbury? ```{r 05-ex-e2} canterbury = nz[nz$Name == "Canterbury", ] cant_buff = st_buffer(canterbury, 100000) nz_height_near_cant = nz_height[cant_buff, ] nrow(nz_height_near_cant) # 75 - 5 more ``` E3. Find the geographic centroid of New Zealand. How far is it from the geographic centroid of Canterbury? ```{r 05-ex-e3} cant_cent = st_centroid(canterbury) nz_centre = st_centroid(st_union(nz)) st_distance(cant_cent, nz_centre) # 234 km ``` E4. Most world maps have a north-up orientation. A world map with a south-up orientation could be created by a reflection (one of the affine transformations not mentioned in this chapter) of the `world` object's geometry. Write code to do so. Hint: you can to use the `rotation()` function from this chapter for this transformation. Bonus: create an upside-down map of your country. ```{r 05-ex-e4} rotation = function(a){ r = a * pi / 180 #degrees to radians matrix(c(cos(r), sin(r), -sin(r), cos(r)), nrow = 2, ncol = 2) } world_sfc = st_geometry(world) world_sfc_mirror = world_sfc * rotation(180) plot(world_sfc) plot(world_sfc_mirror) us_states_sfc = st_geometry(us_states) us_states_sfc_mirror = us_states_sfc * rotation(180) plot(us_states_sfc) plot(us_states_sfc_mirror) ``` E5. Run the code in Section [5.2.6](https://r.geocompx.org/geometry-operations.html#subsetting-and-clipping). With reference to the objects created in that section, subset the point in `p` that is contained within `x` *and* `y`. - Using base subsetting operators. - Using an intermediary object created with `st_intersection()`\index{vector!intersection}. ```{r 05-ex-e5a, echo=FALSE} b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles x = b[1] y = b[2] bb = st_bbox(st_union(x, y)) box = st_as_sfc(bb) set.seed(2017) p = st_sample(x = box, size = 10) ``` ```{r 05-ex-e5} p_in_y = p[y] p_in_xy = p_in_y[x] x_and_y = st_intersection(x, y) p[x_and_y] ``` E6. Calculate the length of the boundary lines of US states in meters. Which state has the longest border and which has the shortest? Hint: The `st_length` function computes the length of a `LINESTRING` or `MULTILINESTRING` geometry. ```{r 05-ex-e6} us_states9311 = st_transform(us_states, "EPSG:9311") us_states_bor = st_cast(us_states9311, "MULTILINESTRING") us_states_bor$borders = st_length(us_states_bor) arrange(us_states_bor, borders) arrange(us_states_bor, -borders) ``` E7. Read the srtm.tif file into R (`srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge"))`). This raster has a resolution of 0.00083 * 0.00083 degrees. Change its resolution to 0.01 * 0.01 degrees using all of the methods available in the **terra** package. Visualize the results. Can you notice any differences between the results of these resampling methods? ```{r 05-ex-e7} srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) rast_template = rast(ext(srtm), res = 0.01) srtm_resampl1 = resample(srtm, y = rast_template, method = "bilinear") srtm_resampl2 = resample(srtm, y = rast_template, method = "near") srtm_resampl3 = resample(srtm, y = rast_template, method = "cubic") srtm_resampl4 = resample(srtm, y = rast_template, method = "cubicspline") srtm_resampl5 = resample(srtm, y = rast_template, method = "lanczos") srtm_resampl_all = c(srtm_resampl1, srtm_resampl2, srtm_resampl3, srtm_resampl4, srtm_resampl5) plot(srtm_resampl_all) # differences plot(srtm_resampl_all - srtm_resampl1, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl2, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl3, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl4, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl5, range = c(-300, 300)) ``` ================================================ FILE: _06-ex.Rmd ================================================ Some of the following exercises use a vector (`zion_points`) and raster dataset (`srtm`) from the **spDataLarge** package. They also use a polygonal 'convex hull' derived from the vector dataset (`ch`) to represent the area of interest: ```{r 06-ex-e0, message=FALSE, include=TRUE} library(sf) library(terra) library(spData) zion_points_path = system.file("vector/zion_points.gpkg", package = "spDataLarge") zion_points = read_sf(zion_points_path) srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) ch = st_combine(zion_points) |> st_convex_hull() |> st_as_sf() ``` E1. Crop the `srtm` raster using (1) the `zion_points` dataset and (2) the `ch` dataset. Are there any differences in the output maps? Next, mask `srtm` using these two datasets. Can you see any difference now? How can you explain that? ```{r 06-ex-e1} plot(srtm) plot(st_geometry(zion_points), add = TRUE) plot(ch, add = TRUE) srtm_crop1 = crop(srtm, zion_points) srtm_crop2 = crop(srtm, ch) plot(srtm_crop1) plot(srtm_crop2) srtm_mask1 = mask(srtm, zion_points) srtm_mask2 = mask(srtm, ch) plot(srtm_mask1) plot(srtm_mask2) ``` E2. Firstly, extract values from `srtm` at the points represented in `zion_points`. Next, extract average values of `srtm` using a 90 buffer around each point from `zion_points` and compare these two sets of values. When would extracting values by buffers be more suitable than by points alone? - Bonus: Implement extraction using the **exactextractr** package and compare the results. ```{r 06-ex-e2} zion_points_buf = st_buffer(zion_points, dist = 90) plot(srtm) plot(st_geometry(zion_points_buf), add = TRUE) plot(ch, add = TRUE) zion_points_points = extract(srtm, zion_points) zion_points_buffer = extract(srtm, zion_points_buf, fun = "mean") plot(zion_points_points$srtm, zion_points_buffer$srtm) # Bonus # remotes::install_github("isciences/exactextractr") # zion_points_buf_2 = exactextractr::exact_extract(x = srtm, y = zion_points_buf, # fun = "mean") # # plot(zion_points_points$srtm, zion_points_buf_2) # plot(zion_points_buffer$srtm, zion_points_buf_2) ``` E3. Subset points higher than 3100 meters in New Zealand (the `nz_height` object) and create a template raster with a resolution of 3 km for the extent of the new point dataset. Using these two new objects: - Count numbers of the highest points in each grid cell. - Find the maximum elevation in each grid cell. ```{r 06-ex-e3} nz_height3100 = dplyr::filter(nz_height, elevation > 3100) new_graticule = st_graticule(nz_height3100, datum = "EPSG:2193") plot(st_geometry(nz_height3100), graticule = new_graticule, axes = TRUE) nz_template = rast(ext(nz_height3100), resolution = 3000, crs = crs(nz_height3100)) nz_raster = rasterize(nz_height3100, nz_template, field = "elevation", fun = "length") plot(nz_raster) plot(st_geometry(nz_height3100), add = TRUE) nz_raster2 = rasterize(nz_height3100, nz_template, field = "elevation", fun = max) plot(nz_raster2) plot(st_geometry(nz_height3100), add = TRUE) ``` E4. Aggregate the raster counting high points in New Zealand (created in the previous exercise), reduce its geographic resolution by half (so cells are 6 x 6 km) and plot the result. - Resample the lower resolution raster back to the original resolution of 3 km. How have the results changed? - Name two advantages and disadvantages of reducing raster resolution. ```{r 06-ex-e4} nz_raster_low = raster::aggregate(nz_raster, fact = 2, fun = sum, na.rm = TRUE) res(nz_raster_low) nz_resample = resample(nz_raster_low, nz_raster) plot(nz_raster_low) plot(nz_resample) # the results are spread over a greater area and there are border issues plot(nz_raster) ``` ```{asis 06-ex-e4-asis} Advantages: - lower memory use - faster processing - good for viz in some cases Disadvantages: - removes geographic detail - adds another processing step ``` E5. Polygonize the `grain` dataset and filter all squares representing clay. ```{r 06-ex-e5} grain = rast(system.file("raster/grain.tif", package = "spData")) ``` - Name two advantages and disadvantages of vector data over raster data. - When would it be useful to convert rasters to vectors in your work? ```{r 06-ex-e5-2} grain_poly = as.polygons(grain) |> st_as_sf() levels(grain) clay = dplyr::filter(grain_poly, grain == "clay") plot(clay) ``` ```{asis 06-ex-e5-2-asis} Advantages: - can be used to subset other vector objects - can do affine transformations and use sf/dplyr verbs Disadvantages: - better consistency - fast processing on some operations - functions developed for some domains ``` ================================================ FILE: _07-ex.Rmd ================================================ ```{r 07-ex-e0, message=FALSE} library(sf) library(terra) library(spData) ``` E1. Create a new object called `nz_wgs` by transforming `nz` object into the WGS84 CRS. - Create an object of class `crs` for both and use this to query their CRSs. - With reference to the bounding box of each object, what units does each CRS use? - Remove the CRS from `nz_wgs` and plot the result: what is wrong with this map of New Zealand and why? ```{r 07-ex-e1} st_crs(nz) nz_wgs = st_transform(nz, "EPSG:4326") nz_crs = st_crs(nz) nz_wgs_crs = st_crs(nz_wgs) nz_crs$epsg nz_wgs_crs$epsg st_bbox(nz) st_bbox(nz_wgs) nz_wgs_NULL_crs = st_set_crs(nz_wgs, NA) nz_27700 = st_transform(nz_wgs, "EPSG:27700") par(mfrow = c(1, 3)) plot(st_geometry(nz)) plot(st_geometry(nz_wgs)) plot(st_geometry(nz_wgs_NULL_crs)) # answer: it is fatter in the East-West direction # because New Zealand is close to the South Pole and meridians converge there plot(st_geometry(nz_27700)) par(mfrow = c(1, 1)) ``` E2. Transform the `world` dataset to the transverse Mercator projection (`"+proj=tmerc"`) and plot the result. What has changed and why? Try to transform it back into WGS 84 and plot the new object. Why does the new object differ from the original one? ```{r 07-ex-e2} # see https://github.com/r-spatial/sf/issues/509 world_tmerc = st_transform(world, "+proj=tmerc") plot(st_geometry(world_tmerc)) world_4326 = st_transform(world_tmerc, "EPSG:4326") plot(st_geometry(world_4326)) ``` E3. Transform the continuous raster (`con_raster`) into NAD83 / UTM zone 12N using the nearest neighbor interpolation method. What has changed? How does it influence the results? ```{r 07-ex-e3} con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) con_raster_utm12n = project(con_raster, "EPSG:32612", method = "near") con_raster_utm12n plot(con_raster) plot(con_raster_utm12n) ``` E4. Transform the categorical raster (`cat_raster`) into WGS 84 using the bilinear interpolation method. What has changed? How does it influence the results? ```{r 07-ex-e4} cat_raster = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) cat_raster_wgs84 = project(cat_raster, "EPSG:4326", method = "bilinear") cat_raster_wgs84 plot(cat_raster) plot(cat_raster_wgs84) ``` ================================================ FILE: _08-ex.Rmd ================================================ ```{r 08-ex-e0, message=FALSE} library(sf) library(terra) ``` E1. List and describe three types of vector, raster, and geodatabase formats. ```{asis 08-ex-e0-asis} Vector formats: Shapefile (old format supported by many programs), GeoPackage (more recent format with better support of attribute data) and GeoJSON (common format for web mapping). Raster formats: GeoTiff, Arc ASCII, ERDAS Imagine (IMG). Database formats: PostGIS, SQLite, FileGDB. ``` E2. Name at least two differences between the **sf** functions `read_sf()` and `st_read()`. ```{asis 08-ex-e2-asis} `read_sf()` is simply a 'wrapper' around `st_read()`, meaning that it calls `st_read()` behind the scenes. The differences shown in the output of the `read_sf` are `quiet = TRUE`, `stringsAsFactors = FALSE`, and `as_tibble = TRUE`: - `read_sf()` outputs are `quiet` by default, meaning less information printed to the console. - `read_sf()` outputs are tibbles by default, meaning that they are data frames with some additional features. - `read_sf()` does not convert strings to factors by default. The differences can be seen by running the following commands `nc = st_read(system.file("shape/nc.shp", package="sf"))` and `nc = read_sf(system.file("shape/nc.shp", package="sf"))` from the function's help (`?st_read`). ``` ```{r 08-ex-e2} read_sf nc = st_read(system.file("shape/nc.shp", package="sf")) nc = read_sf(system.file("shape/nc.shp", package="sf")) ``` E3. Read the `cycle_hire_xy.csv` file from the **spData** package as a spatial object (Hint: it is located in the `misc` folder). What is a geometry type of the loaded object? ```{r 08-ex-e3} c_h = read.csv(system.file("misc/cycle_hire_xy.csv", package = "spData")) |> st_as_sf(coords = c("X", "Y")) c_h ``` E4. Download the borders of Germany using **rnaturalearth**, and create a new object called `germany_borders`. Write this new object to a file of the GeoPackage format. ```{r 08-ex-e4} library(rnaturalearth) germany_borders = ne_countries(country = "Germany", returnclass = "sf") plot(germany_borders) st_write(germany_borders, "germany_borders.gpkg") ``` E5. Download the global monthly minimum temperature with a spatial resolution of 5 minutes using the **geodata** package. Extract the June values, and save them to a file named `tmin_june.tif` file (hint: use `terra::subset()`). ```{r 08-ex-e5} library(geodata) gmmt = worldclim_global(var = "tmin", res = 5, path = tempdir()) names(gmmt) plot(gmmt) gmmt_june = terra::subset(gmmt, "wc2.1_5m_tmin_06") plot(gmmt_june) writeRaster(gmmt_june, "tmin_june.tif") ``` E6. Create a static map of Germany's borders, and save it to a PNG file. ```{r 08-ex-e6} png(filename = "germany.png", width = 350, height = 500) plot(st_geometry(germany_borders), axes = TRUE, graticule = TRUE) dev.off() ``` E7. Create an interactive map using data from the `cycle_hire_xy.csv` file. Export this map to a file called `cycle_hire.html`. ```{r 08-ex-e7, eval=FALSE} library(mapview) mapview_obj = mapview(c_h, zcol = "nbikes", legend = TRUE) mapshot(mapview_obj, file = "cycle_hire.html") ``` ================================================ FILE: _09-ex.Rmd ================================================ ```{r 09-ex-e0, message=FALSE} library(sf) library(terra) library(dplyr) library(spData) ``` These exercises rely on a new object, `africa`. Create it using the `world` and `worldbank_df` datasets from the **spData** package as follows: ```{r 08-mapping-41, warning=FALSE, include=TRUE} library(spData) africa = world |> filter(continent == "Africa", !is.na(iso_a2)) |> left_join(worldbank_df, by = "iso_a2") |> select(name, subregion, gdpPercap, HDI, pop_growth) |> st_transform("ESRI:102022") |> st_make_valid() |> st_collection_extract("POLYGON") ``` We will also use `zion` and `nlcd` datasets from **spDataLarge**: ```{r 08-mapping-42, results='hide', include=TRUE} zion = read_sf((system.file("vector/zion.gpkg", package = "spDataLarge"))) nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) ``` E1. Create a map showing the geographic distribution of the Human Development Index (`HDI`) across Africa with base **graphics** (hint: use `plot()`) and **tmap** packages (hint: use `tm_shape(africa) + ...`). - Name two advantages of each based on the experience. - Name three other mapping packages and an advantage of each. - Bonus: create three more maps of Africa using these three other packages. ```{r} # graphics plot(africa["HDI"]) # # tmap # remotes::install_github("r-tmap/tmap") library(tmap) tm_shape(africa) + tm_polygons("HDI") # ggplot library(ggplot2) ggplot() + geom_sf(data = africa, aes(fill = HDI)) # ggplotly library(plotly) g = ggplot() + geom_sf(data = africa, aes(fill = HDI)) ggplotly(g) # mapsf library(mapsf) mf_map(x = africa, var = "HDI", type = "choro") ``` E2. Extend the **tmap** created for the previous exercise so the legend has three bins: "High" (`HDI` above 0.7), "Medium" (`HDI` between 0.55 and 0.7) and "Low" (`HDI` below 0.55). Bonus: improve the map aesthetics, for example by changing the legend title, class labels and color palette. ```{r} library(tmap) tm_shape(africa) + tm_polygons("HDI", fill.scale = tm_scale_intervals(breaks = c(0, 0.55, 0.7, 1), labels = c("Low", "Medium", "High"), values = "-viridis"), fill.legend = tm_legend(title = "Human Development Index")) ``` E3. Represent `africa`'s subregions on the map. Change the default color palette and legend title. Next, combine this map and the map created in the previous exercise into a single plot. ```{r} asubregions = tm_shape(africa) + tm_polygons("subregion", fill.scale = tm_scale_categorical(values = "Set3"), fill.legend = tm_legend(title = "Subregion:")) ahdi = tm_shape(africa) + tm_polygons("HDI", fill.scale = tm_scale_intervals(breaks = c(0, 0.55, 0.7, 1), labels = c("Low", "Medium", "High"), values = "-viridis"), fill.legend = tm_legend(title = "Human Development Index:")) tmap_arrange(ahdi, asubregions) ``` E4. Create a land cover map of Zion National Park. - Change the default colors to match your perception of the land cover categories - Add a scale bar and north arrow and change the position of both to improve the map's aesthetic appeal - Bonus: Add an inset map of Zion National Park's location in the context of the state of Utah. (Hint: an object representing Utah can be subset from the `us_states` dataset.) ```{r} tm_shape(nlcd) + tm_raster(col.scale = tm_scale_categorical(values = c("#495EA1", "#AF5F63", "#EDE9E4", "#487F3F", "#EECFA8", "#A4D378", "#FFDB5C", "#72D593"), levels.drop = TRUE)) + tm_scalebar(bg.color = "white", position = c("left", "bottom")) + tm_compass(bg.color = "white", position = c("right", "top")) + tm_layout(legend.position = c("left", "top"), legend.bg.color = "white") ``` ```{r} # Bonus utah = subset(us_states, NAME == "Utah") utah = st_transform(utah, st_crs(zion)) zion_region = st_bbox(zion) |> st_as_sfc() main = tm_shape(nlcd) + tm_raster(col.scale = tm_scale_categorical(values = c("#495EA1", "#AF5F63", "#EDE9E4", "#487F3F", "#EECFA8", "#A4D378", "#FFDB5C", "#72D593"), levels.drop = TRUE)) + tm_scalebar(bg.color = "white", position = c("left", "bottom")) + tm_compass(bg.color = "white", position = c("right", "top")) + tm_layout(legend.position = c("left", "top"), legend.bg.color = "white") inset = tm_shape(utah) + tm_polygons() + tm_text("UTAH", size = 3) + #tm_shape(zion) + #tm_polygons(col = "red") + tm_shape(zion_region) + tm_borders(col = "red") + tm_layout(frame = FALSE) library(grid) norm_dim = function(obj){ bbox = st_bbox(obj) width = bbox[["xmax"]] - bbox[["xmin"]] height = bbox[["ymax"]] - bbox[["ymin"]] w = width / max(width, height) h = height / max(width, height) return(unit(c(w, h), "snpc")) } main_dim = norm_dim(zion) ins_dim = norm_dim(utah) main_vp = viewport(width = main_dim[1], height = main_dim[2]) ins_vp = viewport(width = ins_dim[1] * 0.4, height = ins_dim[2] * 0.4, x = unit(1, "npc") - unit(0.5, "cm"), y = unit(0.5, "cm"), just = c("right", "bottom")) grid.newpage() print(main, vp = main_vp) pushViewport(main_vp) print(inset, vp = ins_vp) ``` E5. Create facet maps of countries in Eastern Africa: - With one facet showing HDI and the other representing population growth (hint: using variables `HDI` and `pop_growth`, respectively) - With a 'small multiple' per country ```{r} ea = subset(africa, subregion == "Eastern Africa") #1 tm_shape(ea) + tm_polygons(c("HDI", "pop_growth")) #2 tm_shape(ea) + tm_polygons() + tm_facets_wrap("name") ``` E6. Building on the previous facet map examples, create animated maps of East Africa: - Showing each country in order - Showing each country in order with a legend showing the HDI ```{r, eval=FALSE} tma1 = tm_shape(ea) + tm_polygons() + tm_facets(by = "name", nrow = 1, ncol = 1) tmap_animation(tma1, filename = "tma2.gif", width = 1000, height = 1000) browseURL("tma1.gif") tma2 = tm_shape(africa) + tm_polygons(fill = "lightgray") + tm_shape(ea) + tm_polygons(fill = "darkgray") + tm_shape(ea) + tm_polygons(fill = "HDI") + tm_facets(by = "name", nrow = 1, ncol = 1) tmap_animation(tma2, filename = "tma2.gif", width = 1000, height = 1000) browseURL("tma2.gif") ``` E7. Create an interactive map of HDI in Africa: - With **tmap** - With **mapview** - With **leaflet** - Bonus: For each approach, add a legend (if not automatically provided) and a scale bar ```{r, eval=FALSE} # tmap tmap_mode("view") tm_shape(africa) + tm_polygons("HDI") + tm_scalebar() # mapview mapview::mapview(africa["HDI"]) # leaflet africa4326 = st_transform(africa, "EPSG:4326") library(leaflet) pal = colorNumeric(palette = "YlGnBu", domain = africa4326$HDI) leaflet(africa4326) |> addTiles() |> addPolygons(stroke = FALSE, smoothFactor = 0.2, fillOpacity = 1, color = ~pal(HDI)) |> addLegend("bottomright", pal = pal, values = ~HDI, opacity = 1) |> addScaleBar() ``` E8. Sketch on paper ideas for a web mapping application that could be used to make transport or land-use policies more evidence-based: - In the city you live, for a couple of users per day - In the country you live, for dozens of users per day - Worldwide for hundreds of users per day and large data serving requirements ```{asis} Ideas could include identification of routes where many people currently drive short distances, ways to encourage access to parks, or prioritization of new developments to reduce long-distance travel. At the city level a web map would be sufficient. A the national level a mapping application, e.g., with shiny, would probably be needed. Worldwide, a database to serve the data would likely be needed. Then various front-ends could plug in to this. ``` E9. Update the code in `coffeeApp/app.R` so that instead of centering on Brazil the user can select which country to focus on: - Using `textInput()` - Using `selectInput()` ```{asis} The answer can be found in the `shinymod` branch of the geocompr repo: https://github.com/Robinlovelace/geocompr/pull/318/files You create the new widget and then use it to set the center. Note: the input data must be fed into the map earlier to prevent the polygons disappearing when you change the center this way. ``` E10. Reproduce Figure 9.1 and Figure 9.7 as closely as possible using the **ggplot2** package. ```{r} library(ggplot2) ggplot() + geom_sf(data = nz, color = NA) + coord_sf(crs = st_crs(nz), datum = NA) + theme_void() ggplot() + geom_sf(data = nz, fill = NA) + coord_sf(crs = st_crs(nz), datum = NA) + theme_void() ggplot() + geom_sf(data = nz) + coord_sf(crs = st_crs(nz), datum = NA) + theme_void() # fig 9.7 ggplot() + geom_sf(data = nz, aes(fill = Median_income)) + coord_sf(crs = st_crs(nz), datum = NA) + scale_fill_distiller(palette = "Blues", direction = 1) + theme_void() ggplot() + geom_sf(data = nz, aes(fill = Island)) + coord_sf(crs = st_crs(nz), datum = NA) + scale_fill_manual(values = c("#CC6677", "#332288")) + theme_void() ``` E11. Join `us_states` and `us_states_df` together and calculate a poverty rate for each state using the new dataset. Next, construct a continuous area cartogram based on total population. Finally, create and compare two maps of the poverty rate: (1) a standard choropleth map and (2) a map using the created cartogram boundaries. What is the information provided by the first and the second map? How do they differ from each other? ```{r} tmap_mode("plot") library(cartogram) # prepare the data us = st_transform(us_states, "EPSG:9311") us = left_join(us, us_states_df, by = c("NAME" = "state")) # calculate a poverty rate us$poverty_rate = us$poverty_level_15 / us$total_pop_15 # create a regular map ecm1 = tm_shape(us) + tm_polygons("poverty_rate", fill.legend = tm_legend(title = "Poverty rate")) # create a cartogram us_carto = cartogram_cont(us, "total_pop_15") ecm2 = tm_shape(us_carto) + tm_polygons("poverty_rate", fill.legend = tm_legend(title = "Poverty rate")) # combine two maps tmap_arrange(ecm1, ecm2) ``` E12. Visualize population growth in Africa. Next, compare it with the maps of a hexagonal and regular grid created using the **geogrid** package. ```{r} library(geogrid) hex_cells = calculate_grid(africa, grid_type = "hexagonal", seed = 25, learning_rate = 0.03) africa_hex = assign_polygons(africa, hex_cells) reg_cells = calculate_grid(africa, grid_type = "regular", seed = 25, learning_rate = 0.03) africa_reg = assign_polygons(africa, reg_cells) tgg1 = tm_shape(africa) + tm_polygons("pop_growth", fill.legend = tm_legend(title = "Population's growth (annual %)")) tgg2 = tm_shape(africa_hex) + tm_polygons("pop_growth", fill.legend = tm_legend(title = "Population's growth (annual %)")) tgg3 = tm_shape(africa_reg) + tm_polygons("pop_growth", fill.legend = tm_legend(title = "Population's growth (annual %)")) tmap_arrange(tgg1, tgg2, tgg3) ``` ================================================ FILE: _10-ex.Rmd ================================================ ```{r 10-ex-e0, message=FALSE} library(sf) library(terra) ``` E1. Compute global solar irradiation for an area of `system.file("raster/dem.tif", package = "spDataLarge")` for March 21 at 11:00 am using the `r.sun` GRASS GIS through **qgisprocess**. ```{r} library(qgisprocess) # enable grass qgis_enable_plugins("grassprovider") dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) slope = terrain(dem, "slope", unit = "degrees") aspect = terrain(dem, "aspect", unit = "degrees") qgis_algo = qgis_algorithms() grep("r.sun", qgis_algo$algorithm, value = TRUE) alg = "grass7:r.sun.incidout" qgis_show_help(alg) dem_sun = qgis_run_algorithm(alg, elevation = dem, aspect = aspect, slope = slope, day = 80, time = 11) dem_sun # output global (total) irradiance/irradiation [W.m-2] for given time gsi_dem = qgis_as_terra(dem_sun$glob_rad) plot(dem) plot(gsi_dem) ``` E2. Compute catchment area\index{catchment area} and catchment slope of `system.file("raster/dem.tif", package = "spDataLarge")` using **Rsagacmd**. ```{r} library(Rsagacmd) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") swi = saga$ta_hydrology$saga_wetness_index tidy(swi) swi_results = swi(dem, area_type = 0, slope_type = 1) swi_results_all = rast(swi_results) plot(swi_results_all[["area"]]) plot(swi_results_all[["slope"]]) ``` E3. Continue working on the `ndvi_segments` object created in the SAGA section. Extract average NDVI values from the `ndvi` raster and group them into six clusters using `kmeans()`. Visualize the results. ```{r} library(Rsagacmd) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) sg = saga$imagery_segmentation$seed_generation ndvi_seeds = sg(ndvi, band_width = 2) plot(ndvi_seeds$seed_grid) srg = saga$imagery_segmentation$seeded_region_growing ndvi_srg = srg(ndvi_seeds$seed_grid, ndvi, method = 1) plot(ndvi_srg$segments) ndvi_segments = as.polygons(ndvi_srg$segments) |> st_as_sf() # extract values ndvi_segments_vals = extract(ndvi, ndvi_segments, fun = "mean") ndvi_segments = cbind(ndvi_segments, ndvi_segments_vals) # k-means ks = kmeans(ndvi_segments[["ndvi"]], centers = 6) ndvi_segments$k = ks$cluster # merge polygons library(dplyr) ndvi_segments2 = ndvi_segments |> group_by(k) |> summarise() # visualize results library(tmap) tm1 = tm_shape(ndvi) + tm_raster(style = "cont", palette = "PRGn", title = "NDVI", n = 7) + tm_shape(ndvi_segments2) + tm_borders(col = "red") + tm_layout(legend.outside = TRUE) tm2 = tm_shape(ndvi_segments2) + tm_polygons(col = "k", style = "cat", palette = "Set1") + tm_layout(legend.outside = TRUE) tmap_arrange(tm1, tm2) ``` E4. Attach `data(random_points, package = "spDataLarge")` and read `system.file("raster/dem.tif", package = "spDataLarge")` into R. Select a point randomly from `random_points` and find all `dem` pixels that can be seen from this point (hint: viewshed\index{viewshed} can be calculated using GRASS GIS). Visualize your result. For example, plot a hillshade\index{hillshade}, the digital elevation model\index{digital elevation model}, your viewshed\index{viewshed} output, and the point. Additionally, give `mapview` a try. ```{r} library(rgrass) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) data(random_points, package = "spDataLarge") random_point = random_points[sample(1:nrow(random_points), 1), ] link2GI::linkGRASS(dem) write_RAST(dem, vname = "dem") execGRASS("r.viewshed", input = "dem", coordinates = sf::st_coordinates(random_point), output = "view", flags = "overwrite") out = read_RAST("view") # simple viz plot(out) # hillshade viz hs = shade(slope = terrain(dem, "slope", unit = "radians"), aspect = terrain(dem, "aspect", unit = "radians")) library(tmap) tm_shape(hs) + tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + tm_shape(dem) + tm_raster(alpha = 0.6, palette = hcl.colors(25, "Geyser"), legend.show = FALSE) + tm_shape(out) + tm_raster(style = "cont", legend.show = FALSE) + tm_shape(random_point) + tm_symbols(col = "black") + tm_layout(frame = FALSE) # mapview viz library(mapview) mapview(out, col = "white", map.type = "Esri.WorldImagery") + mapview(point) ``` E5. Use `gdalinfo` via a system call for a raster\index{raster} file stored on a disk of your choice. What kind of information can you find there? ```{r} link2GI::linkGDAL() our_filepath = system.file("raster/elev.tif", package = "spData") cmd = paste("gdalinfo", our_filepath) system(cmd) # Driver, file path, dimensions, CRS, resolution, bounding box, summary statistics ``` E6. Use `gdalwarp` to decrease the resolution of your raster file (for example, if the resolution is 0.5, change it into 1). Note: `-tr` and `-r` flags will be used in this exercise. ```{r} our_filepath = system.file("raster/elev.tif", package = "spData") cmd2 = paste("gdalwarp", our_filepath, "new_elev.tif", "-tr 1 1", "-r bilinear") system(cmd2) ``` E7. Query all Californian highways from the PostgreSQL/PostGIS\index{PostGIS} database living in the QGIS\index{QGIS} Cloud introduced in this chapter. ```{r} library(RPostgreSQL) conn = dbConnect(drv = PostgreSQL(), dbname = "rtafdf_zljbqm", host = "db.qgiscloud.com", port = "5432", user = "rtafdf_zljbqm", password = "d3290ead") query = paste( "SELECT *", "FROM highways", "WHERE state = 'CA';") ca_highways = read_sf(conn, query = query, geom = "wkb_geometry") plot(st_geometry(ca_highways)) ``` E8. The `ndvi.tif` raster (`system.file("raster/ndvi.tif", package = "spDataLarge")`) contains NDVI calculated for the Mongón study area based on Landsat data from September 22, 2000. Use **rstac**, **gdalcubes**, and **terra** to download Sentinel-2 images for the same area from 2020-08-01 to 2020-10-31, calculate its NDVI, and then compare it with the results from `ndvi.tif`. ```{r} library(rstac) library(gdalcubes) ?spDataLarge::ndvi.tif ndvi1 = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) bbox1 = as.numeric(st_bbox(project(ndvi1, "EPSG:4326"))) # get data s = stac("https://earth-search.aws.element84.com/v0") items = s |> stac_search(collections = "sentinel-s2-l2a-cogs", bbox = bbox1, datetime = "2020-08-01/2020-10-31") |> post_request() |> items_fetch() collection = stac_image_collection(items$features, property_filter = function(x) {x[["eo:cloud_cover"]] < 10}) v = cube_view(srs = "EPSG:32717", extent = collection, dx = xres(ndvi1), dy = yres(ndvi1), dt = "P1D") # calculate ndvi ndvi2 = raster_cube(collection, v) |> select_bands(c("B04", "B08")) |> apply_pixel("(B08-B04)/(B08+B04)", "NDVI") # write results to file gdalcubes_options(parallel = 2) gdalcubes::write_tif(ndvi2, dir = ".", prefix = "ndvi2") # unify two datasets ndvi2 = rast("ndvi22020-10-10.tif") plot(ndvi2) ndvi2 = resample(ndvi2, ndvi1, method = "bilinear") plot(ndvi2) # vizualize the final results ndvi_all = c(ndvi1, ndvi2) names(ndvi_all) = c("y2000", "y2020") library(tmap) tm_shape(ndvi_all) + tm_raster(style = "cont") ``` ================================================ FILE: _11-ex.Rmd ================================================ ```{asis 11-ex-asis1, message=FALSE} The solutions assume the following packages are attached (other packages will be attached when needed): ``` ```{r setup11, include=FALSE} poly_centroid = function(poly_mat) { Origin = poly_mat[1, ] # create a point representing the origin i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) {rbind(Origin, poly_mat[x:(x + 1), ], Origin)}) C_list = lapply(T_all, t_centroid) C = do.call(rbind, C_list) A = vapply(T_all, t_area, FUN.VALUE = double(1)) c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) } ``` ```{r 11-ex-e0} library(sf) ``` E1. Read the script [`11-centroid-alg.R`](https://github.com/geocompx/geocompr/blob/main/code/11-centroid-alg.R) in the `code` folder of the book's GitHub repository. - Which of the best practices covered in Section \@ref(scripts) does it follow? - Create a version of the script on your computer in an IDE\index{IDE} such as RStudio\index{RStudio} (preferably by typing the script line-by-line, in your own coding style and with your own comments, rather than copy-pasting --- this will help you learn how to type scripts). Using the example of a square polygon (e.g., created with `poly_mat = cbind(x = c(0, 9, 9, 0, 0), y = c(0, 0, 9, 9, 0))`) execute the script line-by-line. - What changes could be made to the script to make it more reproducible? - How could the documentation be improved? ```{asis 11-ex-e1, message=FALSE} The script is stored in a logical location with a sensible file name. The script is well documented with comments and the code is well formatted. The script is reproducible. Open a file and create a new script in RStudio, e.g., with the keyboard shortcut `Ctrl + Shift + N` (Windows) or `Cmd + Shift + N` (Mac), by clicking `File > New File > R Script` or by clicking the `+` icon in the top left of the `Source` pane. You can also create a new R script from the R console with the command `file.create("11-centroid-alg.R")`. The script is already reproducible, with a message stating that it needs an object called `poly_mat` to be present and, if none is present, it creates an example dataset at the outset for testing. For people new to R it could also contain a comment stating that R must be installed before running the script. Documentation could be improved with a more detailed description of the algorithm, including a link to the relevant section of the book. Furthermore, the anonymous functions could be replaced with named functions and documented with Roxygen2 comments. ``` E2. In the geometric algorithms section, we calculated that the area of the polygon `poly_mat` was 245 units squared and that its centroid as at the coordinates (8.8, 9.2). - Reproduce the results on your own computer with reference to the script [`11-centroid-alg.R`](https://github.com/geocompx/geocompr/blob/main/code/11-centroid-alg.R), an implementation of this algorithm (bonus: type the commands - try to avoid copy-pasting). - Are the results correct? Verify them by converting `poly_mat` into an `sfc` object (named `poly_sfc`) with `st_polygon()` (hint: this function takes objects of class `list()`) and then using `st_area()` and `st_centroid()`. ```{r 11-ex-e} # We can verify the answer by converting `poly_mat` into a simple feature collection # as follows, which shows the calculations match: x_coords = c(10, 20, 12, 0, 0, 10) y_coords = c(0, 15, 20, 10, 0, 0) poly_mat = cbind(x_coords, y_coords) poly_sfc = sf::st_polygon(list(poly_mat)) sf::st_area(poly_sfc) sf::st_centroid(poly_sfc) # By calling the script: # source("https://github.com/geocompx/geocompr/raw/main/code/11-centroid-alg.R") ``` E3. It was stated that the algorithm\index{algorithm} we created only works for *convex hulls*. Define convex hulls\index{convex hull} (see the geometry operations chapter) and test the algorithm on a polygon that is *not* a convex hull. ```{r 11-ex-e3} x_coords = c(10, 20, 12, 0, 0, 5, 10) y_coords = c(0, 15, 20, 10, 0, 5, 0) plot(x_coords, y_coords, type = "l") poly_mat = cbind(x_coords, y_coords) # source("https://github.com/geocompx/geocompr/raw/main/code/11-centroid-alg.R") # Area from our script: 270 poly_sfc = sf::st_polygon(list(poly_mat)) sf::st_area(poly_sfc) # Actual area: 220 ``` - Bonus 1: Think about why the method only works for convex hulls and note changes that would need to be made to the algorithm to make it work for other types of polygon. - Bonus 2: Building on the contents of `11-centroid-alg.R`, write an algorithm only using base R functions that can find the total length of linestrings represented in matrix form. ```{asis 11-ex-e3-bonus1} The algorithm would need to be able to have negative as well as positive area values. We leave Bonus 2 as an exercise for the reader. ``` E4. In the functions section, we created different versions of the `poly_centroid()` function that generated outputs of class `sfg` (`poly_centroid_sfg()`) and type-stable `matrix` outputs (`poly_centroid_type_stable()`). Further extend the function by creating a version (e.g., called `poly_centroid_sf()`) that is type stable (only accepts inputs of class `sf`) *and* returns `sf` objects (hint: you may need to convert the object `x` into a matrix with the command `sf::st_coordinates(x)`). - Verify if it works by running `poly_centroid_sf(sf::st_sf(sf::st_sfc(poly_sfc)))` - What error message do you get when you try to run `poly_centroid_sf(poly_mat)`? ```{r 11-ex-e4} poly_centroid_sf = function(x) { stopifnot(is(x, "sf")) xcoords = sf::st_coordinates(x) centroid_coords = poly_centroid(xcoords) centroid_sf = sf::st_sf(geometry = sf::st_sfc(sf::st_point(centroid_coords))) centroid_sf } poly_centroid_sf(sf::st_sf(sf::st_sfc(poly_sfc))) poly_centroid_sf(poly_sfc) poly_centroid_sf(poly_mat) ``` ================================================ FILE: _12-ex.Rmd ================================================ ```{asis 12-ex-asis1, message=FALSE} The solutions assume the following packages are attached (other packages will be attached when needed): ``` ```{r 12-ex-e0, message=FALSE, warning=FALSE, eval=FALSE} library(dplyr) # library(kernlab) library(mlr3) library(mlr3learners) library(mlr3extralearners) library(mlr3spatiotempcv) library(mlr3tuning) library(qgisprocess) library(terra) library(sf) library(tmap) ``` E1. Compute the following terrain attributes from the `elev` dataset loaded with `terra::rast(system.file("raster/ta.tif", package = "spDataLarge"))$elev` with the help of R-GIS bridges (see the bridges to GIS software chapter): - Slope - Plan curvature - Profile curvature - Catchment area ```{r 12-ex-e1-1, eval=FALSE} # attach data dem = terra::rast(system.file("raster/ta.tif", package = "spDataLarge"))$elev algs = qgisprocess::qgis_algorithms() qgis_search_algorithms("curvature") alg = "sagang:slopeaspectcurvature" qgisprocess::qgis_show_help(alg) qgisprocess::qgis_get_argument_specs(alg) # terrain attributes (ta) out_nms = paste0(tempdir(), "/", c("slope", "cplan", "cprof"), ".sdat") args = rlang::set_names(out_nms, c("SLOPE", "C_PLAN", "C_PROF")) out = qgis_run_algorithm(alg, ELEVATION = dem, METHOD = 6, UNIT_SLOPE = "[1] degree", !!!args, .quiet = TRUE ) ta = out[names(args)] |> unlist() |> terra::rast() names(ta) = c("slope", "cplan", "cprof") # catchment area qgis_search_algorithms("[Cc]atchment") alg = "sagang:catchmentarea" qgis_show_help(alg) qgis_get_argument_specs(alg) carea = qgis_run_algorithm(alg, ELEVATION = dem, METHOD = 4, FLOW = file.path(tempdir(), "carea.sdat")) # transform carea carea = terra::rast(carea$FLOW[1]) log10_carea = log10(carea) names(log10_carea) = "log10_carea" # add log_carea and dem to the terrain attributes ta = c(ta, dem, log10_carea) ``` E2. Extract the values from the corresponding output rasters to the `lsl` data frame (`data("lsl", package = "spDataLarge"`) by adding new variables called `slope`, `cplan`, `cprof`, `elev` and `log_carea`. ```{r 12-ex-e2, eval=FALSE} # attach terrain attribute raster stack (in case you have skipped the previous # exercise) data("lsl", package = "spDataLarge") ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) lsl = select(lsl, x, y, lslpts) # extract values to points, i.e., create predictors lsl[, names(ta)] = terra::extract(ta, lsl[, c("x", "y")]) |> select(-ID) ``` E3. Use the derived terrain attribute rasters in combination with a GLM to make a spatial prediction map similar to that shown in Figure 12.2. Running `data("study_mask", package = "spDataLarge")` attaches a mask of the study area. ```{r 12-ex-e3, eval=FALSE} # attach data (in case you have skipped exercises 1) and 2) # landslide points with terrain attributes and terrain attribute raster stack data("lsl", "study_mask", package = "spDataLarge") ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # fit the model fit = glm(lslpts ~ slope + cplan + cprof + elev + log10_carea, data = lsl, family = binomial()) # make the prediction pred = terra::predict(object = ta, model = fit, type = "response") # make the map lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) hs = terra::shade(ta$slope * pi / 180, terra::terrain(ta$elev, v = "aspect", unit = "radians")) rect = tmaptools::bb_poly(raster::raster(hs)) bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.00001, 1), ylim = c(-0.00001, 1), relative = TRUE) tm_shape(terra::mask(hs, study_mask), bbox = bbx) + tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, labels.rot = c(0, 90), lines = FALSE) + tm_raster(col.scale = tm_scale(values = gray(0:100 / 100), n = 100), col.legend = tm_legend_hide()) + # prediction raster tm_shape(terra::mask(pred, study_mask)) + tm_raster(col_alpha = 0.5, col.scale = tm_scale(values = "Reds", n = 6), col.legend = tm_legend(title = "Susceptibility")) + # rectangle and outer margins tm_shape(rect) + tm_borders() + tm_layout(legend.position = c("left", "bottom"), legend.title.size = 0.9) ``` E4. Compute a 100-repeated 5-fold non-spatial cross-validation and spatial CV based on the GLM learner and compare the AUROC values from both resampling strategies with the help of boxplots. Hint: You need to specify a non-spatial resampling strategy. Another hint: You might want to solve Excercises 4 to 6 in one go with the help of `mlr3::benchmark()` and `mlr3::benchmark_grid()` (for more information, please refer to https://mlr3book.mlr-org.com/chapters/chapter10/advanced_technical_aspects_of_mlr3.html#sec-fallback). When doing so, keep in mind that the computation can take very long, probably several days. This, of course, depends on your system. Computation time will be shorter the more RAM and cores you have at your disposal. ```{r 12-ex-e4, eval=FALSE} # attach data (in case you have skipped exercises 1) and 2) data("lsl", package = "spDataLarge") # landslide points with terrain attributes # create task task = TaskClassifST$new( id = "lsl_ecuador", backend = mlr3::as_data_backend(lsl), target = "lslpts", positive = "TRUE", coordinate_names = c("x", "y"), coords_as_features = FALSE, crs = 32717 ) # construct learners (for all subsequent exercises) # GLM lrn_glm = lrn("classif.log_reg", predict_type = "prob") lrn_glm$fallback = lrn("classif.featureless", predict_type = "prob") # SVM # construct SVM learner (using ksvm function from the kernlab package) lrn_ksvm = lrn("classif.ksvm", predict_type = "prob", kernel = "rbfdot", type = "C-svc") lrn_ksvm$fallback = lrn("classif.featureless", predict_type = "prob") # specify nested resampling and adjust learner accordingly # five spatially disjoint partitions tune_level = rsmp("spcv_coords", folds = 5) # use 50 randomly selected hyperparameters terminator = trm("evals", n_evals = 50) tuner = tnr("random_search") # define the outer limits of the randomly selected hyperparameters ps = ps( C = p_dbl(lower = -12, upper = 15, trafo = function(x) 2^x), sigma = p_dbl(lower = -15, upper = 6, trafo = function(x) 2^x) ) at_ksvm = AutoTuner$new( learner = lrn_ksvm, resampling = tune_level, measure = msr("classif.auc"), search_space = ps, terminator = terminator, tuner = tuner ) # QDA lrn_qda = lrn("classif.qda", predict_type = "prob") lrn_qda$fallback = lrn("classif.featureless", predict_type = "prob") # SVM without tuning hyperparameters vals = lrn_ksvm$param_set$values lrn_ksvm_notune = lrn_ksvm$clone() lrn_ksvm_notune$param_set$values = c(vals, C = 1, sigma = 1) # define resampling strategies # specify the reampling method, i.e. spatial CV with 100 repetitions and 5 folds # -> in each repetition dataset will be splitted into five folds # method: repeated_spcv_coords -> spatial partioning rsmp_sp = rsmp("repeated_spcv_coords", folds = 5, repeats = 100) # method: repeated_cv -> non-spatial partitioning rsmp_nsp = rsmp("repeated_cv", folds = 5, repeats = 100) # (spatial) cross-validataion #**************************** # create your design grid = benchmark_grid(tasks = task, learners = list(lrn_glm, at_ksvm, lrn_qda, lrn_ksvm_notune), resamplings = list(rsmp_sp, rsmp_nsp)) # execute the cross-validation library(future) # execute the outer loop sequentially and parallelize the inner loop future::plan(list("sequential", "multisession"), workers = floor(availableCores() / 2)) set.seed(021522) bmr = benchmark(grid, store_backends = FALSE, store_models = FALSE, encapsulate = "evaluate") # stop parallelization future:::ClusterRegistry("stop") # save your result, e.g., to # saveRDS(bmr, file = "extdata/12-bmr.rds") # plot your result autoplot(bmr, measure = msr("classif.auc")) ``` E5. Model landslide susceptibility using a quadratic discriminant analysis (QDA). Assess the predictive performance of the QDA. What is the a difference between the spatially cross-validated mean AUROC value of the QDA and the GLM? ```{r 12-ex-e5, eval=FALSE} # attach data (in case you have skipped exercise 4) bmr = readRDS("extdata/12-bmr.rds") # plot your result autoplot(bmr, measure = msr("classif.auc")) # QDA has higher AUROC values on average than GLM which indicates moderately # non-linear boundaries ``` E6. Run the SVM without tuning the hyperparameters. Use the `rbfdot` kernel with $\sigma$ = 1 and *C* = 1. Leaving the hyperparameters unspecified in **kernlab**'s `ksvm()` would otherwise initialize an automatic non-spatial hyperparameter tuning. ```{r 12-ex-e6, eval=FALSE} # attach data (in case you have skipped exercise 4) bmr = readRDS("extdata/12-bmr.rds") # plot your result autoplot(bmr, measure = msr("classif.auc")) ``` ================================================ FILE: _13-ex.Rmd ================================================ ```{r 13-ex-e0, message=FALSE} library(sf) library(spDataLarge) ``` E1. In much of the analysis presented in the chapter, we focused on active modes, but what about driving trips? - What proportion of trips in the `desire_lines` object are made by driving? - What proportion of `desire_lines` have a straight line length of 5 km or more in distance? - What proportion of trips in desire lines that are longer than 5 km in length are made by driving? - Plot the desire lines that are both less than 5 km in length and along which more than 50% of trips are made by car. - What do you notice about the location of these car-dependent yet short desire lines? ```{r 13-e1, eval=FALSE, echo=FALSE} sum(desire_lines$car_driver) / sum(desire_lines$all) # 57% desire_lines_5km_plus = desire_lines |> filter(distance_km > 5) # Just over are half ar 5km+, 54%: nrow(desire_lines_5km_plus) / nrow(desire_lines) # 71 of 5km+ trips are made by car sum(desire_lines_5km_plus$car_driver) / sum(desire_lines_5km_plus$all) desire_lines_driving = desire_lines |> mutate(`Proportion driving` = car_driver / all) |> filter(`Proportion driving` > 0.5) nrow(desire_lines_5km_plus_driving) / nrow(desire_lines) desire_lines_5km_less_50_pct_driving = desire_lines |> filter(distance_km <= 5) |> mutate(`Proportion driving` = car_driver / all) |> filter(`Proportion driving` > 0.5) desire_lines_5km_less_50_pct_driving |> tm_shape() + tm_lines("Proportion driving") ``` E2. What additional length of cycleways would be built if all the sections beyond 100 m from existing cycleways in Figure 13.8, were constructed? ```{r 13-transport-29, eval=FALSE, echo=FALSE} sum(st_length(route_network_no_infra)) # 104193.6 [m] # Just over 100 km ``` E3. What proportion of trips represented in the `desire_lines` are accounted for in the `routes_short_scenario` object? - Bonus: what proportion of all trips happen on desire lines that cross `routes_short_scenario`? ```{r 13-transport-30, echo=FALSE, eval=FALSE} sum(routes_short_scenario$all) / sum(desire_lines$all) # 13% d_intersect = desire_lines[routes_short_scenario, , op = st_crosses] sum(d_intersect$all) / sum(desire_lines$all) # 88% ``` E4. The analysis presented in this chapter is designed for teaching how geocomputation methods can be applied to transport research. If you were doing this for real, in government or for a transport consultancy, what top 3 things would you do differently? ```{r} # Higher level of geographic resolution. # Use cycle-specific routing services. # Identify key walking routes. # Include a higher proportion of trips in the analysis ``` E5. Clearly, the routes identified in Figure 13.8 only provide part of the picture. How would you extend the analysis? E6. Imagine that you want to extend the scenario by creating key *areas* (not routes) for investment in place-based cycling policies such as car-free zones, cycle parking points and reduced car parking strategy. How could raster\index{raster} datasets assist with this work? - Bonus: develop a raster layer that divides the Bristol region into 100 cells (10 x 10) and estimate the average speed limit of roads in each, from the `bristol_ways` dataset (see Chapter \@ref(location)). ================================================ FILE: _14-ex.Rmd ================================================ ```{asis 14-ex-asis1, message=FALSE} The solutions assume the following packages are attached (other packages will be attached when needed): ``` ```{r 14-ex-e0, message=FALSE, warning=FALSE} library(sf) library(dplyr) library(purrr) library(terra) library(osmdata) library(spDataLarge) ``` E1. This exercise requires the **z22** package for accessing 100 m resolution data. Install it with `remotes::install_github("JsLth/z22")`. Load the population data at 100 m cell resolution using `z22::z22_data("population", res = "100m", year = 2022)`. Aggregate it to a cell resolution of 1 km using `terra::aggregate()` with `fun = sum`, and compare the result with the 1 km resolution data from `census_de`. Note that the 100 m data is much larger and may take some time to download. ```{r, 14-ex-e1, eval=FALSE} # Coarse inhabitant raster (1 km resolution) #******************************************* # Load 1 km population data from spDataLarge data("census_de", package = "spDataLarge") pop_1km = census_de |> select(x, y, pop) |> mutate(pop = ifelse(pop < 0, NA, pop)) inh_coarse = terra::rast(pop_1km, type = "xyz", crs = "EPSG:3035") # Fine inhabitant raster (100 m resolution) #****************************************** # Load 100 m population data using z22 (this may take some time) pop_100m = z22::z22_data("population", res = "100m", year = 2022, as = "df") pop_100m = pop_100m |> rename(pop = cat_0) |> mutate(pop = ifelse(pop < 0, NA, pop)) inh_fine = terra::rast(pop_100m, type = "xyz", crs = "EPSG:3035") # Comparing the coarse with the fine raster #****************************************** # aggregate to the resolution of the coarse raster inh_fine_agg = terra::aggregate( inh_fine, fact = terra::res(inh_coarse)[1] / terra::res(inh_fine)[1], fun = sum, na.rm = TRUE) # origin has to be the same terra::origin(inh_fine_agg) = terra::origin(inh_coarse) # make the comparison summary(inh_fine_agg - inh_coarse) plot(inh_fine_agg - inh_coarse) # Note: Since Census 2022 provides actual counts at both resolutions, # differences should be minimal (mainly due to rounding or edge effects) terra::global((abs(inh_fine_agg - inh_coarse) > 100), fun = "sum", na.rm = TRUE) ``` E2. Suppose our bike shop predominantly sold electric bikes to older people. Change the age raster accordingly, repeat the remaining analyses and compare the changes with our original result. ```{r, 14-ex-e2, eval=FALSE} # Load data from spDataLarge data("census_de", package = "spDataLarge") input_tidy = census_de |> mutate(across(c(pop, women, mean_age, hh_size), ~ifelse(.x < 0, NA, .x))) input_ras = terra::rast(input_tidy, type = "xyz", crs = "EPSG:3035") # attach further necessary data data("metro_names", "shops", package = "spDataLarge") # Basically, we are assuming that especially older people will use an electric # bike, therefore, we increase the weights for raster cells where predominantly # older people are living. # Reclassification matrices for continuous values rcl_women = matrix(c( 0, 40, 3, 40, 47, 2, 47, 53, 1, 53, 60, 0, 60, 100, 0 ), ncol = 3, byrow = TRUE) # For elderly electric bikes: give highest weights to older age groups rcl_age = matrix(c( 0, 40, 0, # Young -> low weight 40, 42, 0, # Young-ish -> low weight 42, 44, 1, # Middle-aged -> some weight 44, 47, 2, # Older -> higher weight 47, 120, 3 # Elderly -> highest weight ), ncol = 3, byrow = TRUE) rcl_hh = matrix(c( 0, 1.5, 3, 1.5, 2.0, 2, 2.0, 2.5, 1, 2.5, 3.0, 0, 3.0, 100, 0 ), ncol = 3, byrow = TRUE) rcl = list(rcl_women, rcl_age, rcl_hh) # Separate population (used as counts for metro detection) from variables to reclassify pop_ras = input_ras$pop demo_vars = c("women", "mean_age", "hh_size") reclass = input_ras[[demo_vars]] for (i in seq_len(terra::nlyr(reclass))) { reclass[[i]] = terra::classify(x = reclass[[i]], rcl = rcl[[i]], right = NA) } names(reclass) = demo_vars # The rest of the analysis follows exactly the code presented in the book. # Add metro names to metros sf object #************************************ metro_names_vec = dplyr::pull(metro_names, city) |> as.character() |> {\(x) ifelse(x == "Velbert", "Düsseldorf", x)}() |> {\(x) gsub("ü", "ue", x)}() pop_agg = terra::aggregate(pop_ras, fact = 20, fun = sum, na.rm = TRUE) pop_agg = pop_agg[pop_agg > 500000, drop = FALSE] polys = pop_agg |> terra::patches(directions = 8) |> terra::as.polygons() |> sf::st_as_sf() metros = polys |> dplyr::group_by(patches) |> dplyr::summarize() metros$metro_names = metro_names_vec # Create shop/poi density raster #******************************* shops = sf::st_transform(shops, sf::st_crs(reclass)) # create poi raster poi = terra::rasterize(x = shops, y = reclass, field = "osm_id", fun = "length") # construct reclassification matrix int = classInt::classIntervals(values(poi), n = 4, style = "fisher") int = round(int$brks) rcl_poi = matrix(c(int[1], rep(int[-c(1, length(int))], each = 2), int[length(int)] + 1), ncol = 2, byrow = TRUE) rcl_poi = cbind(rcl_poi, 0:3) # reclassify poi = terra::classify(poi, rcl = rcl_poi, right = NA) names(poi) = "poi" # add poi raster to demographic weights reclass = c(reclass, poi) # Identify suitable locations #**************************** # calculate the total score result = sum(reclass) # have a look at suitable bike shop locations in Berlin berlin = metros[metros$metro_names == "Berlin", ] berlin_raster = terra::crop(result, berlin) # summary(berlin_raster) # berlin_raster berlin_raster = berlin_raster > 9 berlin_raster[berlin_raster == 0] = NA # make the plot leaflet::leaflet() |> leaflet::addTiles() |> leaflet::addRasterImage(raster::raster(berlin_raster), colors = "darkgreen", opacity = 0.8) |> leaflet::addLegend("bottomright", colors = c("darkgreen"), labels = c("potential locations"), title = "Legend") ``` ================================================ FILE: _15-ex.Rmd ================================================ The solutions assume the following packages are attached (other packages will be attached when needed): ```{r 15-ex-e0, message=FALSE, warning=FALSE, eval=FALSE} library(sf) library(terra) library(data.table) library(dplyr) library(future) library(ggplot2) library(lgr) library(mlr3) library(mlr3learners) library(mlr3spatiotempcv) library(mlr3tuning) library(mlr3viz) library(progressr) library(qgisprocess) library(tictoc) library(vegan) ``` E1. Run a NMDS\index{NMDS} using the percentage data of the community matrix. Report the stress value and compare it to the stress value as retrieved from the NMDS using presence-absence data. What might explain the observed difference? ```{r 15-ex-e1, message=FALSE, eval=FALSE} data("comm", package = "spDataLarge") pa = vegan::decostand(comm, "pa") pa = pa[rowSums(pa) != 0, ] comm = comm[rowSums(comm) != 0, ] set.seed(25072018) nmds_pa = vegan::metaMDS(comm = pa, k = 4, try = 500) nmds_per = vegan::metaMDS(comm = comm, k = 4, try = 500) nmds_pa$stress nmds_per$stress ``` ```{asis 15-ex-e1-asis, message=FALSE} The NMDS using the presence-absence values yields a better result (`nmds_pa$stress`) than the one using percentage data (`nmds_per$stress`). This might seem surprising at first sight. On the other hand, the percentage matrix contains both more information and more noise. Another aspect is how the data was collected. Imagine a botanist in the field. It might seem feasible to differentiate between a plant which has a cover of 5% and another species that covers 10%. However, what about a herbal species that was only detected three times and consequently has a very tiny cover, e.g., 0.0001%. Maybe another herbal species was detected 6 times, is its cover then 0.0002%? The point here is that percentage data as specified during a field campaign might reflect a precision that the data does not have. This again introduces noise which in turn will worsen the ordination result. Still, it is a valuable information if one species had a higher frequency or coverage in one plot than another compared to just presence-absence data. One compromise would be to use a categorical scale such as the Londo scale. ``` E2. Compute all the predictor rasters\index{raster} we have used in the chapter (catchment slope, catchment area), and put them into a `SpatRaster`-object. Add `dem` and `ndvi` to it. Next, compute profile and tangential curvature and add them as additional predictor rasters (hint: `grass7:r.slope.aspect`). Finally, construct a response-predictor matrix. The scores of the first NMDS\index{NMDS} axis (which were the result when using the presence-absence community matrix) rotated in accordance with elevation represent the response variable, and should be joined to `random_points` (use an inner join). To complete the response-predictor matrix, extract the values of the environmental predictor raster object to `random_points`. ```{r 15-ex-e2, eval = FALSE} # first compute the terrain attributes we have also used in the chapter library(dplyr) library(terra) library(qgisprocess) library(vegan) data("comm", "random_points", package = "spDataLarge") dem = terra::rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = terra::rast(system.file("raster/ndvi.tif", package = "spDataLarge")) # use presence-absence matrix and get rid of empty pa = vegan::decostand(comm, "pa") pa = pa[rowSums(pa) != 0, ] # enable plugins if not already done so qgisprocess::qgis_enable_plugins(c("grassprovider", "processing_saga_nextgen")) # compute environmental predictors (ep) catchment slope and catchment area ep = qgisprocess::qgis_run_algorithm( alg = "sagang:sagawetnessindex", DEM = dem, SLOPE_TYPE = 1, SLOPE = tempfile(fileext = ".sdat"), AREA = tempfile(fileext = ".sdat"), .quiet = TRUE) # read in catchment area and catchment slope ep = ep[c("AREA", "SLOPE")] |> unlist() |> terra::rast() # assign proper names names(ep) = c("carea", "cslope") # make sure all rasters share the same origin origin(ep) = origin(dem) # add dem and ndvi to the multi-layer SpatRaster object ep = c(dem, ndvi, ep) ep$carea = log10(ep$carea) # compute the curvatures qgis_show_help("grass7:r.slope.aspect") curvs = qgis_run_algorithm( "grass7:r.slope.aspect", elevation = dem, .quiet = TRUE) # adding curvatures to ep curv_nms = c("pcurvature", "tcurvature") curvs = curvs[curv_nms] |> unlist() |> terra::rast() curvs = terra::app(curvs, as.numeric) names(curvs) = curv_nms ep = c(ep, curvs) random_points[, names(ep)] = # terra::extract adds an ID column, we don't need terra::extract(ep, random_points) |> select(-ID) elev = dplyr::filter(random_points, id %in% rownames(pa)) %>% dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = MDSrotate(nmds_pa, elev) # extracting the first two axes sc = vegan::scores(rotnmds, choices = 1:2, display = "sites") rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) # join the predictors (dem, ndvi and terrain attributes) rp = dplyr::inner_join(random_points, rp, by = "id") # saveRDS(rp, "extdata/15-rp_exercises.rds") ``` E3. Retrieve the bias-reduced RMSE of a random forest\index{random forest} and a linear model using spatial cross-validation\index{cross-validation!spatial CV}. The random forest modeling should include the estimation of optimal hyperparameter\index{hyperparameter} combinations (random search with 50 iterations) in an inner tuning loop. Parallelize\index{parallelization} the tuning level. Report the mean RMSE\index{RMSE} and use a boxplot to visualize all retrieved RMSEs. Please note that this exercise is best solved using the mlr3 functions `benchmark_grid()` and `benchmark()` (see https://mlr3book.mlr-org.com/perf-eval-cmp.html#benchmarking for more information). ```{r 15-ex-e3, message=FALSE, eval=FALSE} library(dplyr) library(future) library(mlr3) library(mlr3spatiotempcv) library(mlr3learners) library(mlr3viz) library(paradox) library(ranger) # in case you have not run the previous exercises, run: # rp = readRDS("extdata/15-rp_exercises.rds") # define the task task = mlr3spatiotempcv::as_task_regr_st( select(rp, -id, -spri), target = "sc", id = "mongon") # define the learners mlr3::mlr_learners # linear model lrn_lm = mlr3::lrn("regr.lm", predict_type = "response") # random forest lrn_rf = mlr3::lrn("regr.ranger", predict_type = "response") # now define the AutoTuner of the random forest search_space = paradox::ps( mtry = paradox::p_int(lower = 1, upper = ncol(task$data()) - 1), sample.fraction = paradox::p_dbl(lower = 0.2, upper = 0.9), min.node.size = paradox::p_int(lower = 1, upper = 10) ) at_rf = mlr3tuning::AutoTuner$new( learner = lrn_rf, # spatial partitioning resampling = mlr3::rsmp("spcv_coords", folds = 5), # performance measure measure = mlr3::msr("regr.rmse"), search_space = search_space, # random search with 50 iterations terminator = mlr3tuning::trm("evals", n_evals = 50), tuner = mlr3tuning::tnr("random_search") ) # define the resampling strategy rsmp_sp = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) # create the benchmark design design_grid = mlr3::benchmark_grid( tasks = task, learners = list(lrn_lm, at_rf), resamplings = rsmp_sp) print(design_grid) # execute the outer loop sequentially and parallelize the inner loop future::plan(list("sequential", "multisession"), workers = floor(future::availableCores() / 2)) set.seed(10112022) # reduce verbosity lgr::get_logger("mlr3")$set_threshold("warn") lgr::get_logger("bbotk")$set_threshold("info") # BE CAREFUL: Running the benchmark might take quite some time # therefore, we have stored the result of the benchmarking in # extdata/15-bmr-exercises.rds (see below) tictoc::tic() progressr::with_progress(expr = { bmr = mlr3::benchmark( design = design_grid, # New argument `encapsulate` for `resample()` and `benchmark()` to # conveniently enable encapsulation and also set the fallback learner to the # respective featureless learner. This is simply for convenience, configuring # each learner individually is still possible and allows a more fine-grained # control encapsulate = "evaluate", store_backends = FALSE, store_models = FALSE) }) tictoc::toc() # stop parallelization future:::ClusterRegistry("stop") # we have saved the result as follows # saveRDS(bmr, file = "extdata/15-bmr_exercises.rds") # READ IT IN in in case you don't want to run the spatial CV yourself as it is computationally # demanding # bmr = readRDS("extdata/15-bmr_exercises.rds") # mean RMSE bmr$aggregate(measures = msr("regr.rmse")) # or computed manually agg = bmr$aggregate(measures = msr("regr.rmse")) # lm performs slightly better when considering the mean rmse purrr::map(agg$resample_result, ~ mean(.$score(msr("regr.rmse"))$regr.rmse)) # however, looking at the median, the random forest performs slightly better purrr::map(agg$resample_result, ~ median(.$score(msr("regr.rmse"))$regr.rmse)) # make a boxplot (when using autoplot, mlr3viz needs to be attached!) library(mlr3viz) autoplot(bmr, measure = msr("regr.rmse")) # or doing it "manually" # extract the AUROC values and put them into one data.table d = purrr::map_dfr(agg$resample_result, ~ .$score(msr("regr.rmse"))) # create the boxplots library(ggplot2) ggplot(data = d, mapping = aes(x = learner_id, y = regr.rmse)) + geom_boxplot(fill = c("lightblue2", "mistyrose2")) + theme_bw() + labs(y = "RMSE", x = "model") ``` ```{asis 15-ex-e3-asis, message=FALSE} In fact, `lm` performs at least as good the random forest model, and thus should be preferred since it is much easier to understand and computationally much less demanding (no need for fitting hyperparameters). But keep in mind that the used dataset is small in terms of observations and predictors and that the response-predictor relationships are also relatively linear. ``` ================================================ FILE: _404.Rmd ================================================ ```{r c404, echo=FALSE, message=FALSE, fig.asp=1} library(tmap) library(sf) null_island = st_point(c(0, 0)) null_island = st_sfc(null_island, crs = "EPSG:4326") null_island = st_sf(name = "Null Island", geom = null_island) tm_shape(null_island) + tm_graticules(labels.col = "gray40") + tm_text("name", size = 5, fontface = "italic") + tm_layout(bg.color = "lightblue") + tm_title("You are here:", color = "gray40") ``` ================================================ FILE: _bookdown.yml ================================================ book_filename: geocompr2 rmd_files: - "index.Rmd" - "01-introduction.Rmd" - "02-spatial-data.Rmd" - "03-attribute-operations.Rmd" - "04-spatial-operations.Rmd" - "05-geometry-operations.Rmd" - "06-raster-vector.Rmd" - "07-reproj.Rmd" - "08-read-write-plot.Rmd" - "09-mapping.Rmd" - "10-gis.Rmd" - "11-algorithms.Rmd" - "12-spatial-cv.Rmd" - "13-transport.Rmd" - "14-location.Rmd" - "15-eco.Rmd" - "16-synthesis.Rmd" - "references.Rmd" new_session: true delete_merged_file: true language: label: fig: "FIGURE " tab: "TABLE " ui: edit: "Edit" chapter_name: "Chapter " ================================================ FILE: _output.yml ================================================ bookdown::bs4_book: theme: primary: "#3860b6" #links base_font: google: family: Lato heading_font: google: family: Montserrat wght: 600 code_font: google: family: Roboto Mono bg: "#fefefe" # backgrounds fg: "#000000" # fonts repo: base: https://github.com/geocompx/geocompr branch: main includes: in_header: style/ga.html template: style/bs4_book.html css: style/style.css # bookdown::pdf_book: # includes: # in_header: style/preamble.tex # before_body: style/before_body.tex # after_body: style/after_body.tex # keep_tex: true # dev: "cairo_pdf" # latex_engine: xelatex # citation_package: natbib # template: null # urlcolor: "black" # pandoc_args: ["--top-level-division=chapter"] # toc_depth: 3 # toc_unnumbered: false # toc_appendix: true # quote_footer: ["\\VA{", "}{}"] # highlight_bw: true ================================================ FILE: _redirects ================================================ # http and https need separate rules if you don’t force_ssl! http://geocompr.robinlovelace.net/* http://r.geocompx.org/:splat 301! https://geocompr.robinlovelace.net/* https://r.geocompx.org/:splat 301! /solutions/* https://geocompx.github.io/solutions/:splat 200 /es/* https://geocompx.github.io/geocompr-es/:splat 200 /fr/* https://geocompx.github.io/geocompr-fr/:splat 200 /jp/* https://geocompx.github.io/geocompr-jp/:splat 200 ================================================ FILE: apps/CycleHireApp/app.R ================================================ # Shiny app for cycle hire from https://github.com/geocompx/geocompr/issues/584 # Author - Kiranmayi Vadlamudi # 2020-12-25 # Last updated: 2023-04-19 by Jakub Nowosad library(shiny) library(sf) library(spData) library(spDataLarge) library(leaflet) library(units) library(dplyr) library(stringr) # Based on input coordinates finding the nearest bicycle points ui = fluidPage( # Application title titlePanel("CycleHireApp"), # Numeric Input from User bootstrapPage( div(style = "display:inline-block", numericInput("x", ("Enter x-coordinate of your location"), value = 51.5, step = 0.001)), div(style = "display:inline-block", numericInput("y", ("Enter y-coordinate of your location"), value = -0.1, step = 0.001)), div(style = "display:inline-block", numericInput("num", "How many cycles are you looking for?", value = 1, step = 1)) ), # Where leaflet map will be rendered fluidRow( leafletOutput("map", height = 300) ) ) server = function(input, output) { #Centering the leaflet map onto London - use if needed map_centre = matrix(c(-0.2574846, 51.4948089), nrow = 1, ncol = 2, dimnames = list(c("r1"), c("X", "Y"))) #Based on input coords calculating top 5 closest stations to be displayed #Making reactive object of input location coordinates input_pt = reactive({ matrix(c(input$y, input$x), nrow = 1, ncol = 2, dimnames = list(c("r1"), c("X", "Y"))) }) #Rendering the output map showing the input coordinates output$map = renderLeaflet({ leaflet() |> addTiles() |> setView(lng = input_pt()[, "X"], input_pt()[, "Y"], zoom = 15) }) #Finding the top distance between input coordinates and all other cycle stations, then sorting them. data = reactive({ cycle_hire$dist = st_point(input_pt()) |> st_sfc() |> st_set_crs("EPSG:4326") |> st_distance(cycle_hire$geometry) |> t() |> set_units("km") cycle_hire[order(cycle_hire$dist), ] }) #Filtering the distance data from above to show top 5 closest stations meeting requirement of # of bikes needed filteredData = reactive({ data() |> filter(nbikes >= input$num) |> head(5) |> mutate(popup = str_c(str_c("Station:", name, sep = " "), str_c("Available bikes:", nbikes, sep = " "), sep = "
")) }) #Making changes to the output leaflet map reflecting the cycle stations found above icons = awesomeIcons(icon = "bicycle", library = "fa", squareMarker = TRUE, markerColor = "blue") observe({ proxy = leafletProxy("map", data = filteredData()) |> clearMarkers() proxy |> clearMarkers() |> addAwesomeMarkers(icon = icons, popup = ~popup) |> addMarkers(lng = input_pt()[, "X"], input_pt()[, "Y"], label = "Your Location") }) } # Run the application shinyApp(ui = ui, server = server) ================================================ FILE: apps/CycleHireApp/manifest.json ================================================ { "version": 1, "locale": "en_AU", "platform": "3.6.3", "metadata": { "appmode": "shiny", "primary_rmd": null, "primary_html": null, "content_category": null, "has_parameters": false }, "packages": { "BH": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "BH", "Type": "Package", "Title": "Boost C++ Header Files", "Version": "1.72.0-3", "Date": "2019-12-28", "Author": "Dirk Eddelbuettel, John W. Emerson and Michael J. Kane", "Maintainer": "Dirk Eddelbuettel ", "Description": "Boost provides free peer-reviewed portable C++ source \n libraries. A large part of Boost is provided as C++ template code\n which is resolved entirely at compile-time without linking. This \n package aims to provide the most useful subset of Boost libraries \n for template use among CRAN packages. By placing these libraries in \n this package, we offer a more efficient distribution system for CRAN \n as replication of this code in the sources of other packages is \n avoided. As of release 1.72.0-3, the following Boost libraries are\n included: 'accumulators' 'algorithm' 'align' 'any' 'atomic' 'bimap'\n 'bind' 'circular_buffer' 'compute' 'concept' 'config' 'container'\n 'date_time' 'detail' 'dynamic_bitset' 'exception' 'flyweight'\n 'foreach' 'functional' 'fusion' 'geometry' 'graph' 'heap' 'icl'\n 'integer' 'interprocess' 'intrusive' 'io' 'iostreams' 'iterator'\n 'math' 'move' 'mp11' 'mpl' 'multiprcecision' 'numeric' 'pending'\n 'phoenix' 'polygon' 'preprocessor' 'propery_tree' 'random' 'range'\n 'scope_exit' 'smart_ptr' 'sort' 'spirit' 'tuple' 'type_traits'\n 'typeof' 'unordered' 'utility' 'uuid'.", "License": "BSL-1.0", "URL": "https://github.com/eddelbuettel/bh", "BugReports": "https://github.com/eddelbuettel/bh/issues", "NeedsCompilation": "no", "Packaged": "2019-12-28 14:48:54.73981 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-01-08 23:20:08 UTC", "Built": "R 3.6.0; ; 2020-01-09 18:04:01 UTC; unix" } }, "R6": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "R6", "Title": "Encapsulated Classes with Reference Semantics", "Version": "2.4.1", "Authors@R": "person(\"Winston\", \"Chang\", role = c(\"aut\", \"cre\"), email = \"winston@stdout.org\")", "Description": "Creates classes with reference semantics, similar to R's built-in\n reference classes. Compared to reference classes, R6 classes are simpler\n and lighter-weight, and they are not built on S4 classes so they do not\n require the methods package. These classes allow public and private\n members, and they support inheritance, even when the classes are defined in\n different packages.", "Depends": "R (>= 3.0)", "Suggests": "knitr, microbenchmark, pryr, testthat, ggplot2, scales", "License": "MIT + file LICENSE", "URL": "https://r6.r-lib.org, https://github.com/r-lib/R6/", "LazyData": "true", "BugReports": "https://github.com/r-lib/R6/issues", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-11-12 20:00:15 UTC; winston", "Author": "Winston Chang [aut, cre]", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2019-11-12 22:50:03 UTC", "Built": "R 3.6.0; ; 2019-11-13 16:10:18 UTC; unix" } }, "Rcpp": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "Rcpp", "Title": "Seamless R and C++ Integration", "Version": "1.0.5", "Date": "2020-07-01", "Author": "Dirk Eddelbuettel, Romain Francois, JJ Allaire, Kevin Ushey, Qiang Kou,\n Nathan Russell, Douglas Bates and John Chambers", "Maintainer": "Dirk Eddelbuettel ", "Description": "The 'Rcpp' package provides R functions as well as C++ classes which\n offer a seamless integration of R and C++. Many R data types and objects can be\n mapped back and forth to C++ equivalents which facilitates both writing of new\n code as well as easier integration of third-party libraries. Documentation\n about 'Rcpp' is provided by several vignettes included in this package, via the\n 'Rcpp Gallery' site at , the paper by Eddelbuettel and\n Francois (2011, ), the book by Eddelbuettel (2013,\n ) and the paper by Eddelbuettel and Balamuta (2018,\n ); see 'citation(\"Rcpp\")' for details.", "Imports": "methods, utils", "Suggests": "tinytest, inline, rbenchmark, pkgKitten (>= 0.1.2)", "URL": "http://www.rcpp.org, https://dirk.eddelbuettel.com/code/rcpp.html,\nhttps://github.com/RcppCore/Rcpp", "License": "GPL (>= 2)", "BugReports": "https://github.com/RcppCore/Rcpp/issues", "MailingList": "rcpp-devel@lists.r-forge.r-project.org", "RoxygenNote": "6.1.1", "NeedsCompilation": "yes", "Packaged": "2020-07-01 16:50:09.72105 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-07-06 13:40:08 UTC", "Built": "R 3.6.2; x86_64-apple-darwin15.6.0; 2020-07-07 04:26:06 UTC; unix" } }, "base64enc": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "base64enc", "Version": "0.1-3", "Title": "Tools for base64 encoding", "Author": "Simon Urbanek ", "Maintainer": "Simon Urbanek ", "Depends": "R (>= 2.9.0)", "Enhances": "png", "Description": "This package provides tools for handling base64 encoding. It is more flexible than the orphaned base64 package.", "License": "GPL-2 | GPL-3", "URL": "http://www.rforge.net/base64enc", "NeedsCompilation": "yes", "Packaged": "2015-02-04 20:31:00 UTC; svnuser", "Repository": "CRAN", "Date/Publication": "2015-07-28 08:03:37", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-04-26 19:50:57 UTC; unix" } }, "commonmark": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "commonmark", "Type": "Package", "Title": "High Performance CommonMark and Github Markdown Rendering in R", "Version": "1.7", "Authors@R": "c(\n person(\"Jeroen\", \"Ooms\", ,\"jeroen@berkeley.edu\", role = c(\"aut\", \"cre\")),\n person(\"John MacFarlane\", role = \"cph\", comment = \"Author of cmark\"))", "URL": "http://github.com/jeroen/commonmark (devel)\nhttps://github.github.com/gfm/ (spec)", "BugReports": "http://github.com/jeroen/commonmark/issues", "Description": "The CommonMark specification defines a rationalized version of markdown\n syntax. This package uses the 'cmark' reference implementation for converting\n markdown text into various formats including html, latex and groff man. In\n addition it exposes the markdown parse tree in xml format. Also includes opt-in\n support for GFM extensions including tables, autolinks, and strikethrough text.", "License": "BSD_2_clause + file LICENSE", "Suggests": "curl, testthat, xml2", "RoxygenNote": "6.0.1", "NeedsCompilation": "yes", "Packaged": "2018-12-01 11:57:14 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre],\n John MacFarlane [cph] (Author of cmark)", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2018-12-01 12:30:03 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-04-26 20:04:11 UTC; unix" } }, "crayon": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "crayon", "Title": "Colored Terminal Output", "Version": "1.3.4", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\",\n role = c(\"aut\", \"cre\")),\n person(\n \"Brodie\", \"Gaslam\", email=\"brodie.gaslam@yahoo.com\",\n role=c(\"ctb\"))\n )", "Description": "Colored terminal output on terminals that support 'ANSI'\n color and highlight codes. It also works in 'Emacs' 'ESS'. 'ANSI'\n color support is automatically detected. Colors and highlighting can\n be combined and nested. New styles can also be created easily.\n This package was inspired by the 'chalk' 'JavaScript' project.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/crayon#readme", "BugReports": "https://github.com/r-lib/crayon/issues", "Collate": "'ansi-256.r' 'combine.r' 'string.r' 'utils.r'\n'crayon-package.r' 'disposable.r' 'has_ansi.r' 'has_color.r'\n'styles.r' 'machinery.r' 'parts.r' 'print.r' 'style-var.r'\n'show.r' 'string_operations.r'", "Imports": "grDevices, methods, utils", "Suggests": "mockery, rstudioapi, testthat, withr", "RoxygenNote": "6.0.1.9000", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2017-09-15 18:14:04 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre],\n Brodie Gaslam [ctb]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2017-09-16 19:49:46 UTC", "Built": "R 3.6.0; ; 2019-04-26 19:49:57 UTC; unix" } }, "digest": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "digest", "Author": "Dirk Eddelbuettel with contributions \n by Antoine Lucas, Jarek Tuszynski, Henrik Bengtsson, Simon Urbanek,\n Mario Frasca, Bryan Lewis, Murray Stokely, Hannes Muehleisen,\n Duncan Murdoch, Jim Hester, Wush Wu, Qiang Kou, Thierry Onkelinx, \n Michel Lang, Viliam Simko, Kurt Hornik, Radford Neal, Kendon Bell,\n Matthew de Queljoe, Ion Suruceanu, and Bill Denney.", "Version": "0.6.25", "Date": "2020-02-22", "Maintainer": "Dirk Eddelbuettel ", "Title": "Create Compact Hash Digests of R Objects", "Description": "Implementation of a function 'digest()' for the creation \n of hash digests of arbitrary R objects (using the 'md5', 'sha-1', 'sha-256', \n 'crc32', 'xxhash', 'murmurhash' and 'spookyhash' algorithms) permitting easy\n comparison of R language objects, as well as functions such as'hmac()' to\n create hash-based message authentication code. Please note that this package\n is not meant to be deployed for cryptographic purposes for which more\n comprehensive (and widely tested) libraries such as 'OpenSSL' should be\n used.", "URL": "http://dirk.eddelbuettel.com/code/digest.html", "BugReports": "https://github.com/eddelbuettel/digest/issues", "Depends": "R (>= 3.1.0)", "Imports": "utils", "License": "GPL (>= 2)", "Suggests": "tinytest, knitr, rmarkdown", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-02-22 14:55:28.282493 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-02-23 00:10:02 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2020-02-23 13:17:48 UTC; unix" } }, "fastmap": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "fastmap", "Title": "Fast Implementation of a Key-Value Store", "Version": "1.0.1", "Authors@R": "c(\n person(\"Winston\", \"Chang\", email = \"winston@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(given = \"RStudio\", role = c(\"cph\", \"fnd\")),\n person(given = \"Tessil\", role = \"cph\", comment = \"hopscotch_map library\")\n )", "Description": "Fast implementation of a key-value store. Environments are commonly\n used as key-value stores, but every time a new key is used, it is added to\n R's global symbol table, causing a small amount of memory leakage. This can\n be problematic in cases where many different keys are used. Fastmap avoids\n this memory leak issue by implementing the map using data structures in C++.", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "6.1.1", "Suggests": "testthat (>= 2.1.1)", "URL": "https://r-lib.github.io/fastmap/, https://github.com/r-lib/fastmap", "BugReports": "https://github.com/r-lib/fastmap/issues", "NeedsCompilation": "yes", "Packaged": "2019-10-07 23:14:48 UTC; winston", "Author": "Winston Chang [aut, cre],\n RStudio [cph, fnd],\n Tessil [cph] (hopscotch_map library)", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2019-10-08 05:20:02 UTC", "Built": "R 3.6.3; x86_64-apple-darwin15.6.0; 2020-04-04 09:14:11 UTC; unix" } }, "glue": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "glue", "Title": "Interpreted String Literals", "Version": "1.4.1", "Authors@R": "person(\"Jim\", \"Hester\", email = \"james.f.hester@gmail.com\", role = c(\"aut\", \"cre\"))", "Description": "An implementation of interpreted string literals, inspired by\n Python's Literal String Interpolation and Docstrings\n and Julia's Triple-Quoted String Literals\n .", "Depends": "R (>= 3.1)", "Imports": "methods", "Suggests": "testthat, covr, magrittr, crayon, knitr, rmarkdown, DBI,\nRSQLite, R.utils, forcats, microbenchmark, rprintf, stringr,\nggplot2, dplyr, withr, vctrs (>= 0.3.0)", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "URL": "https://github.com/tidyverse/glue, https://glue.tidyverse.org/", "BugReports": "https://github.com/tidyverse/glue/issues", "VignetteBuilder": "knitr", "ByteCompile": "true", "NeedsCompilation": "yes", "Packaged": "2020-05-13 14:58:42 UTC; jhester", "Author": "Jim Hester [aut, cre]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-05-13 16:10:07 UTC", "Built": "R 3.6.2; x86_64-apple-darwin15.6.0; 2020-05-14 04:29:29 UTC; unix" } }, "htmltools": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "htmltools", "Type": "Package", "Title": "Tools for HTML", "Version": "0.5.0", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = \"aut\", email = \"joe@rstudio.com\"),\n person(\"Carson\", \"Sievert\", role = c(\"aut\", \"cre\"), email = \"carson@rstudio.com\", comment = c(ORCID = \"0000-0002-4958-2844\")),\n person(\"Winston\", \"Chang\", role = \"aut\", email = \"winston@rstudio.com\"),\n person(\"Yihui\", \"Xie\", role = \"aut\", email = \"yihui@rstudio.com\"),\n person(\"Jeff\", \"Allen\", role = \"aut\", email = \"jeff@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\")\n )", "Description": "Tools for HTML generation and output.", "Depends": "R (>= 2.14.1)", "Imports": "utils, digest, grDevices, base64enc, rlang", "Suggests": "markdown, testthat, withr, Cairo, ragg", "Enhances": "knitr", "License": "GPL (>= 2)", "URL": "https://github.com/rstudio/htmltools", "BugReports": "https://github.com/rstudio/htmltools/issues", "RoxygenNote": "7.1.0.9000", "Encoding": "UTF-8", "Collate": "'colors.R' 'html_dependency.R' 'html_escape.R' 'html_print.R'\n'images.R' 'known_tags.R' 'shim.R' 'utils.R' 'tags.R'\n'template.R'", "NeedsCompilation": "yes", "Packaged": "2020-06-15 14:46:19 UTC; cpsievert", "Author": "Joe Cheng [aut],\n Carson Sievert [aut, cre] (),\n Winston Chang [aut],\n Yihui Xie [aut],\n Jeff Allen [aut],\n RStudio [cph]", "Maintainer": "Carson Sievert ", "Repository": "CRAN", "Date/Publication": "2020-06-16 14:10:10 UTC", "Built": "R 3.6.2; x86_64-apple-darwin15.6.0; 2020-06-17 04:30:46 UTC; unix" } }, "httpuv": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "httpuv", "Type": "Package", "Encoding": "UTF-8", "Title": "HTTP and WebSocket Server Library", "Version": "1.5.2", "Author": "Joe Cheng, Hector Corrada Bravo [ctb], Jeroen Ooms [ctb],\n Winston Chang [ctb]", "Copyright": "RStudio, Inc.; Joyent, Inc.; Nginx Inc.; Igor Sysoev; Niels\nProvos; Internet Systems Consortium, Inc.; Alexander Chemeris;\nBerkeley Software Design; Google Inc.; Sony Mobile\nCommunications AB; Alexander Peslyak; Free Software Foundation,\nInc.; X Consortium; Ben Noordhuis; StrongLoop, Inc.; Saúl\nIbarra Corretgé; Bert Belder; Fedor Indutny; libuv project;\nRefael Ackermann; Kenneth MacKay; Emergya; Diego Pettenò; xine\nproject, The Regents of the University of California, Dariusz\nDwornikowski", "Maintainer": "Joe Cheng ", "Description": "Provides low-level socket and protocol support for handling\n HTTP and WebSocket requests directly from within R. It is primarily\n intended as a building block for other packages, rather than making it\n particularly easy to create complete web applications using httpuv alone.\n httpuv is built on top of the libuv and http-parser C libraries, both of\n which were developed by Joyent, Inc. (See LICENSE file for libuv and\n http-parser license information.)", "License": "GPL (>= 2) | file LICENSE", "Depends": "R (>= 2.15.1)", "Imports": "Rcpp (>= 0.11.0), utils, R6, promises, later (>= 0.8.0)", "LinkingTo": "Rcpp, BH, later", "URL": "https://github.com/rstudio/httpuv", "SystemRequirements": "GNU make", "RoxygenNote": "6.1.1", "Suggests": "testthat, callr, curl, websocket", "Collate": "'RcppExports.R' 'httpuv.R' 'random_port.R' 'server.R'\n'static_paths.R' 'utils.R'", "NeedsCompilation": "yes", "Packaged": "2019-09-11 04:12:10 UTC; jcheng", "Repository": "CRAN", "Date/Publication": "2019-09-11 05:40:02 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-09-12 12:27:08 UTC; unix" } }, "jsonlite": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "jsonlite", "Version": "1.7.0", "Title": "A Robust, High Performance JSON Parser and Generator for R", "License": "MIT + file LICENSE", "NeedsCompilation": "yes", "Depends": "methods", "Authors@R": "c(\n person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), email = \"jeroen@berkeley.edu\",\n comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(\"Duncan\", \"Temple Lang\", role = \"ctb\"),\n person(\"Lloyd\", \"Hilaiel\", role = \"cph\", comment=\"author of bundled libyajl\"))", "URL": "https://arxiv.org/abs/1403.2805 (paper)\nhttps://jeroen.cran.dev/jsonlite (docs)", "BugReports": "http://github.com/jeroen/jsonlite/issues", "Maintainer": "Jeroen Ooms ", "VignetteBuilder": "knitr, R.rsp", "Description": "A fast JSON parser and generator optimized for statistical data\n and the web. Started out as a fork of 'RJSONIO', but has been completely\n rewritten in recent versions. The package offers flexible, robust, high\n performance tools for working with JSON in R and is particularly powerful\n for building pipelines and interacting with a web API. The implementation is\n based on the mapping described in the vignette (Ooms, 2014). In addition to\n converting JSON data from/to R objects, 'jsonlite' contains functions to\n stream, validate, and prettify JSON data. The unit tests included with the\n package verify that all edge cases are encoded and decoded consistently for\n use with dynamic data in systems and applications.", "Suggests": "httr, curl, plyr, testthat, knitr, rmarkdown, R.rsp, sf, sp", "RoxygenNote": "7.1.0", "Encoding": "UTF-8", "Packaged": "2020-06-24 22:21:10 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] (),\n Duncan Temple Lang [ctb],\n Lloyd Hilaiel [cph] (author of bundled libyajl)", "Repository": "CRAN", "Date/Publication": "2020-06-25 11:50:11 UTC", "Built": "R 3.6.2; x86_64-apple-darwin15.6.0; 2020-06-26 06:13:20 UTC; unix" } }, "later": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "later", "Type": "Package", "Title": "Utilities for Scheduling Functions to Execute Later with Event\nLoops", "Version": "1.0.0", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = c(\"aut\", \"cre\"), email = \"joe@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\"),\n person(\"Winston\", \"Chang\", role = c(\"aut\"), email = \"winston@rstudio.com\"),\n person(\"Marcus\", \"Geelnard\", role = c(\"ctb\", \"cph\"), comment = \"TinyCThread library, https://tinycthread.github.io/\"),\n person(\"Evan\", \"Nemerson\", role = c(\"ctb\", \"cph\"), comment = \"TinyCThread library, https://tinycthread.github.io/\")\n )", "Description": "Executes arbitrary R or C functions some time after the current\n time, after the R execution stack has emptied. The functions are scheduled\n in an event loop.", "URL": "https://github.com/r-lib/later", "BugReports": "https://github.com/r-lib/later/issues", "License": "GPL (>= 2)", "Imports": "Rcpp (>= 0.12.9), rlang", "LinkingTo": "Rcpp, BH", "RoxygenNote": "6.1.1", "Suggests": "knitr, rmarkdown, testthat", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2019-10-03 23:31:59 UTC; jcheng", "Author": "Joe Cheng [aut, cre],\n RStudio [cph],\n Winston Chang [aut],\n Marcus Geelnard [ctb, cph] (TinyCThread library,\n https://tinycthread.github.io/),\n Evan Nemerson [ctb, cph] (TinyCThread library,\n https://tinycthread.github.io/)", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2019-10-04 05:00:02 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-10-05 12:17:52 UTC; unix" } }, "magrittr": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "magrittr", "Type": "Package", "Title": "A Forward-Pipe Operator for R", "Version": "1.5", "Author": "Stefan Milton Bache and\n Hadley Wickham ", "Maintainer": "Stefan Milton Bache ", "Description": "Provides a mechanism for chaining commands with a\n new forward-pipe operator, %>%. This operator will forward a\n value, or the result of an expression, into the next function\n call/expression. There is flexible support for the type\n of right-hand side expressions. For more information, see\n package vignette.\n To quote Rene Magritte, \"Ceci n'est pas un pipe.\"", "Suggests": "testthat, knitr", "VignetteBuilder": "knitr", "License": "MIT + file LICENSE", "ByteCompile": "Yes", "Packaged": "2014-11-22 08:50:53 UTC; shb", "NeedsCompilation": "no", "Repository": "CRAN", "Date/Publication": "2014-11-22 19:15:57", "Built": "R 3.6.0; ; 2019-04-26 19:50:09 UTC; unix" } }, "mime": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "mime", "Type": "Package", "Title": "Map Filenames to MIME Types", "Version": "0.9", "Authors@R": "c(\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Jeffrey\", \"Horner\", role = \"ctb\"),\n person(\"Beilei\", \"Bian\", role = \"ctb\")\n )", "Description": "Guesses the MIME type from a filename extension using the data\n derived from /etc/mime.types in UNIX-type systems.", "Imports": "tools", "License": "GPL", "URL": "https://github.com/yihui/mime", "BugReports": "https://github.com/yihui/mime/issues", "LazyData": "TRUE", "RoxygenNote": "7.0.2", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-02-04 17:11:45 UTC; yihui", "Author": "Yihui Xie [aut, cre] (),\n Jeffrey Horner [ctb],\n Beilei Bian [ctb]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2020-02-04 18:20:06 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2020-02-05 20:05:16 UTC; unix" } }, "pacman": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "pacman", "Type": "Package", "Title": "Package Management Tool", "Version": "0.5.1", "Authors@R": "c(person(\"Tyler\", \"Rinker\", role = c(\"aut\", \"cre\", \"ctb\"),\n email = \"tyler.rinker@gmail.com\"), person(\"Dason\",\n \"Kurkiewicz\", role = c(\"aut\", \"ctb\"), email =\n \"dasonk@gmail.com\"), person(\"Keith\", \"Hughitt\", role =\n c(\"ctb\")), person(\"Albert\", \"Wang\", role = c(\"ctb\")),\n person(\"Garrick\", \"Aden-Buie\", role = c(\"ctb\")),\n person(\"Albert\", \"Wang\", role = c(\"ctb\")),\n person(\"Lukas\", \"Burk\", role = c(\"ctb\")))", "Depends": "R (>= 3.5.0)", "Imports": "remotes, methods, stats, utils", "Suggests": "BiocManager, knitr, lattice, testthat (>= 0.9.0), XML", "BugReports": "https://github.com/trinker/pacman/issues?state=open", "Description": "Tools to more conveniently perform tasks associated with\n add-on packages. pacman conveniently wraps library and package\n related functions and names them in an intuitive and consistent\n fashion. It seeks to combine functionality from lower level\n functions which can speed up workflow.", "License": "GPL-2", "URL": "https://github.com/trinker/pacman", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-03-11 01:37:03 UTC; trinker", "Author": "Tyler Rinker [aut, cre, ctb],\n Dason Kurkiewicz [aut, ctb],\n Keith Hughitt [ctb],\n Albert Wang [ctb],\n Garrick Aden-Buie [ctb],\n Albert Wang [ctb],\n Lukas Burk [ctb]", "Maintainer": "Tyler Rinker ", "Repository": "CRAN", "Date/Publication": "2019-03-11 11:50:07 UTC", "Built": "R 3.6.0; ; 2019-04-26 20:48:03 UTC; unix" } }, "promises": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "promises", "Type": "Package", "Title": "Abstractions for Promise-Based Asynchronous Programming", "Version": "1.1.0", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", email = \"joe@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Description": "Provides fundamental abstractions for doing asynchronous programming\n in R using promises. Asynchronous programming is useful for allowing a single\n R process to orchestrate multiple tasks in the background while also attending\n to something else. Semantics are similar to 'JavaScript' promises, but with a\n syntax that is idiomatic R.", "License": "MIT + file LICENSE", "Imports": "R6, Rcpp, later, rlang, stats, magrittr", "Suggests": "testthat, future, knitr, rmarkdown", "LinkingTo": "later, Rcpp", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "LazyData": "true", "VignetteBuilder": "knitr", "URL": "https://rstudio.github.io/promises,\nhttps://github.com/rstudio/promises", "BugReports": "https://github.com/rstudio/promises/issues", "NeedsCompilation": "yes", "Packaged": "2019-10-04 16:59:31 UTC; jcheng", "Author": "Joe Cheng [aut, cre],\n RStudio [cph, fnd]", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2019-10-04 23:00:05 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-10-05 14:55:24 UTC; unix" } }, "remotes": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "remotes", "Title": "R Package Installation from Remote Repositories, Including\n'GitHub'", "Version": "2.2.0", "Authors@R": "c(\n person(\"Jim\", \"Hester\", , \"jim.hester@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", role = c(\"aut\")),\n person(\"Hadley\", \"Wickham\", role = c(\"aut\")),\n person(\"Winston\", \"Chang\", role = \"aut\"),\n person(\"RStudio\", role = \"cph\"),\n person(\"Martin\", \"Morgan\", role = \"aut\"),\n person(\"Dan\", \"Tenenbaum\", role = \"aut\"),\n person(\"Mango Solutions\", role = \"cph\")\n )", "Description": "Download and install R packages stored in 'GitHub', 'GitLab', \n 'Bitbucket', 'Bioconductor', or plain 'subversion' or 'git' repositories. \n This package provides the 'install_*' functions in 'devtools'. Indeed most \n of the code was copied over from 'devtools'.", "License": "GPL (>= 2)", "URL": "https://remotes.r-lib.org, https://github.com/r-lib/remotes#readme", "BugReports": "https://github.com/r-lib/remotes/issues", "Imports": "methods, stats, tools, utils", "Suggests": "brew, callr, codetools, curl, covr, git2r (>= 0.23.0), knitr,\nmockery, pkgbuild (>= 1.0.1), pingr, rmarkdown, rprojroot,\ntestthat, withr", "Depends": "R (>= 3.0.0)", "VignetteBuilder": "knitr", "RoxygenNote": "7.1.1", "SystemRequirements": "Subversion for install_svn, git for install_git", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-07-20 18:04:55 UTC; jhester", "Author": "Jim Hester [aut, cre],\n Gábor Csárdi [aut],\n Hadley Wickham [aut],\n Winston Chang [aut],\n RStudio [cph],\n Martin Morgan [aut],\n Dan Tenenbaum [aut],\n Mango Solutions [cph]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-07-21 08:40:07 UTC", "Built": "R 3.6.2; ; 2020-07-22 05:43:54 UTC; unix" } }, "rlang": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "rlang", "Version": "0.4.7", "Title": "Functions for Base Types and Core R and 'Tidyverse' Features", "Description": "A toolbox for working with base types, core R features\n like the condition system, and core 'Tidyverse' features like tidy\n evaluation.", "Authors@R": "c(\n person(\"Lionel\", \"Henry\", ,\"lionel@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", ,\"hadley@rstudio.com\", \"aut\"),\n person(\"RStudio\", role = \"cph\")\n )", "License": "GPL-3", "LazyData": "true", "ByteCompile": "true", "Biarch": "true", "Depends": "R (>= 3.2.0)", "Suggests": "cli, covr, crayon, glue, magrittr, methods, pillar,\nrmarkdown, testthat (>= 2.3.0), vctrs (>= 0.2.3), withr", "Encoding": "UTF-8", "RoxygenNote": "7.1.1", "URL": "http://rlang.r-lib.org, https://github.com/r-lib/rlang", "BugReports": "https://github.com/r-lib/rlang/issues", "NeedsCompilation": "yes", "Packaged": "2020-07-09 12:06:27 UTC; lionel", "Author": "Lionel Henry [aut, cre],\n Hadley Wickham [aut],\n RStudio [cph]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-07-09 23:00:18 UTC", "Built": "R 3.6.2; x86_64-apple-darwin15.6.0; 2020-07-10 05:06:02 UTC; unix" } }, "shiny": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "shiny", "Type": "Package", "Title": "Web Application Framework for R", "Version": "1.5.0", "Authors@R": "c(\n person(\"Winston\", \"Chang\", role = c(\"aut\", \"cre\"), email = \"winston@rstudio.com\"),\n person(\"Joe\", \"Cheng\", role = \"aut\", email = \"joe@rstudio.com\"),\n person(\"JJ\", \"Allaire\", role = \"aut\", email = \"jj@rstudio.com\"),\n person(\"Yihui\", \"Xie\", role = \"aut\", email = \"yihui@rstudio.com\"),\n person(\"Jonathan\", \"McPherson\", role = \"aut\", email = \"jonathan@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\"),\n person(family = \"jQuery Foundation\", role = \"cph\",\n comment = \"jQuery library and jQuery UI library\"),\n person(family = \"jQuery contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt\"),\n person(family = \"jQuery UI contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt\"),\n person(\"Mark\", \"Otto\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(\"Jacob\", \"Thornton\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Bootstrap contributors\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Twitter, Inc\", role = \"cph\",\n comment = \"Bootstrap library\"),\n person(\"Alexander\", \"Farkas\", role = c(\"ctb\", \"cph\"),\n comment = \"html5shiv library\"),\n person(\"Scott\", \"Jehl\", role = c(\"ctb\", \"cph\"),\n comment = \"Respond.js library\"),\n person(\"Stefan\", \"Petre\", role = c(\"ctb\", \"cph\"),\n comment = \"Bootstrap-datepicker library\"),\n person(\"Andrew\", \"Rowls\", role = c(\"ctb\", \"cph\"),\n comment = \"Bootstrap-datepicker library\"),\n person(\"Dave\", \"Gandy\", role = c(\"ctb\", \"cph\"),\n comment = \"Font-Awesome font\"),\n person(\"Brian\", \"Reavis\", role = c(\"ctb\", \"cph\"),\n comment = \"selectize.js library\"),\n person(\"Kristopher Michael\", \"Kowal\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(family = \"es5-shim contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(\"Denis\", \"Ineshin\", role = c(\"ctb\", \"cph\"),\n comment = \"ion.rangeSlider library\"),\n person(\"Sami\", \"Samhuri\", role = c(\"ctb\", \"cph\"),\n comment = \"Javascript strftime library\"),\n person(family = \"SpryMedia Limited\", role = c(\"ctb\", \"cph\"),\n comment = \"DataTables library\"),\n person(\"John\", \"Fraser\", role = c(\"ctb\", \"cph\"),\n comment = \"showdown.js library\"),\n person(\"John\", \"Gruber\", role = c(\"ctb\", \"cph\"),\n comment = \"showdown.js library\"),\n person(\"Ivan\", \"Sagalaev\", role = c(\"ctb\", \"cph\"),\n comment = \"highlight.js library\"),\n person(family = \"R Core Team\", role = c(\"ctb\", \"cph\"),\n comment = \"tar implementation from R\")\n )", "Description": "Makes it incredibly easy to build interactive web\n applications with R. Automatic \"reactive\" binding between inputs and\n outputs and extensive prebuilt widgets make it possible to build\n beautiful, responsive, and powerful applications with minimal effort.", "License": "GPL-3 | file LICENSE", "Depends": "R (>= 3.0.2), methods", "Imports": "utils, grDevices, httpuv (>= 1.5.2), mime (>= 0.3), jsonlite\n(>= 0.9.16), xtable, digest, htmltools (>= 0.4.0.9003), R6 (>=\n2.0), sourcetools, later (>= 1.0.0), promises (>= 1.1.0),\ntools, crayon, rlang (>= 0.4.0), fastmap (>= 1.0.0), withr,\ncommonmark (>= 1.7), glue (>= 1.3.2)", "Suggests": "datasets, Cairo (>= 1.5-5), testthat (>= 2.1.1), knitr (>=\n1.6), markdown, rmarkdown, ggplot2, reactlog (>= 1.0.0),\nmagrittr, shinytest, yaml, future, dygraphs, ragg, showtext", "URL": "http://shiny.rstudio.com", "BugReports": "https://github.com/rstudio/shiny/issues", "Collate": "'app.R' 'app_template.R' 'bookmark-state-local.R' 'stack.R'\n'bookmark-state.R' 'bootstrap-deprecated.R'\n'bootstrap-layout.R' 'globals.R' 'conditions.R' 'map.R'\n'utils.R' 'bootstrap.R' 'cache-context.R' 'cache-disk.R'\n'cache-memory.R' 'cache-utils.R' 'diagnose.R' 'fileupload.R'\n'font-awesome.R' 'graph.R' 'reactives.R' 'reactive-domains.R'\n'history.R' 'hooks.R' 'html-deps.R' 'htmltools.R'\n'image-interact-opts.R' 'image-interact.R' 'imageutils.R'\n'input-action.R' 'input-checkbox.R' 'input-checkboxgroup.R'\n'input-date.R' 'input-daterange.R' 'input-file.R'\n'input-numeric.R' 'input-password.R' 'input-radiobuttons.R'\n'input-select.R' 'input-slider.R' 'input-submit.R'\n'input-text.R' 'input-textarea.R' 'input-utils.R'\n'insert-tab.R' 'insert-ui.R' 'jqueryui.R' 'middleware-shiny.R'\n'middleware.R' 'timer.R' 'shiny.R' 'mock-session.R' 'modal.R'\n'modules.R' 'notifications.R' 'priorityqueue.R' 'progress.R'\n'react.R' 'reexports.R' 'render-cached-plot.R' 'render-plot.R'\n'render-table.R' 'run-url.R' 'serializers.R'\n'server-input-handlers.R' 'server.R' 'shiny-options.R'\n'shinyui.R' 'shinywrappers.R' 'showcase.R' 'snapshot.R' 'tar.R'\n'test-export.R' 'test-server.R' 'test.R' 'update-input.R'", "RoxygenNote": "7.1.0.9000", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-06-19 17:22:05 UTC; winston", "Author": "Winston Chang [aut, cre],\n Joe Cheng [aut],\n JJ Allaire [aut],\n Yihui Xie [aut],\n Jonathan McPherson [aut],\n RStudio [cph],\n jQuery Foundation [cph] (jQuery library and jQuery UI library),\n jQuery contributors [ctb, cph] (jQuery library; authors listed in\n inst/www/shared/jquery-AUTHORS.txt),\n jQuery UI contributors [ctb, cph] (jQuery UI library; authors listed in\n inst/www/shared/jqueryui/AUTHORS.txt),\n Mark Otto [ctb] (Bootstrap library),\n Jacob Thornton [ctb] (Bootstrap library),\n Bootstrap contributors [ctb] (Bootstrap library),\n Twitter, Inc [cph] (Bootstrap library),\n Alexander Farkas [ctb, cph] (html5shiv library),\n Scott Jehl [ctb, cph] (Respond.js library),\n Stefan Petre [ctb, cph] (Bootstrap-datepicker library),\n Andrew Rowls [ctb, cph] (Bootstrap-datepicker library),\n Dave Gandy [ctb, cph] (Font-Awesome font),\n Brian Reavis [ctb, cph] (selectize.js library),\n Kristopher Michael Kowal [ctb, cph] (es5-shim library),\n es5-shim contributors [ctb, cph] (es5-shim library),\n Denis Ineshin [ctb, cph] (ion.rangeSlider library),\n Sami Samhuri [ctb, cph] (Javascript strftime library),\n SpryMedia Limited [ctb, cph] (DataTables library),\n John Fraser [ctb, cph] (showdown.js library),\n John Gruber [ctb, cph] (showdown.js library),\n Ivan Sagalaev [ctb, cph] (highlight.js library),\n R Core Team [ctb, cph] (tar implementation from R)", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2020-06-23 13:30:03 UTC", "Built": "R 3.6.2; ; 2020-06-24 06:30:38 UTC; unix" } }, "sourcetools": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "sourcetools", "Type": "Package", "Title": "Tools for Reading, Tokenizing and Parsing R Code", "Version": "0.1.7", "Author": "Kevin Ushey", "Maintainer": "Kevin Ushey ", "Description": "Tools for the reading and tokenization of R code. The\n 'sourcetools' package provides both an R and C++ interface for the tokenization\n of R code, and helpers for interacting with the tokenized representation of R\n code.", "License": "MIT + file LICENSE", "LazyData": "TRUE", "Depends": "R (>= 3.0.2)", "Suggests": "testthat", "RoxygenNote": "5.0.1", "BugReports": "https://github.com/kevinushey/sourcetools/issues", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2018-04-25 03:19:22 UTC; kevin", "Repository": "CRAN", "Date/Publication": "2018-04-25 03:38:09 UTC", "Built": "R 3.6.0; x86_64-apple-darwin15.6.0; 2019-04-26 19:50:45 UTC; unix" } }, "withr": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "withr", "Title": "Run Code 'With' Temporarily Modified Global State", "Version": "2.2.0", "Authors@R": "\n c(person(given = \"Jim\",\n family = \"Hester\",\n role = c(\"aut\", \"cre\"),\n email = \"james.f.hester@gmail.com\"),\n person(given = \"Kirill\",\n family = \"Müller\",\n role = \"aut\",\n email = \"krlmlr+r@mailbox.org\"),\n person(given = \"Kevin\",\n family = \"Ushey\",\n role = \"aut\",\n email = \"kevinushey@gmail.com\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Winston\",\n family = \"Chang\",\n role = \"aut\"),\n person(given = \"Richard\",\n family = \"Cotton\",\n role = \"ctb\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "A set of functions to run code 'with' safely and\n temporarily modified global state. Many of these functions were\n originally a part of the 'devtools' package, this provides a simple\n package with limited dependencies to provide access to these\n functions.", "License": "GPL (>= 2)", "URL": "http://withr.r-lib.org, http://github.com/r-lib/withr#readme", "BugReports": "http://github.com/r-lib/withr/issues", "Depends": "R (>= 3.2.0)", "Imports": "graphics, grDevices, stats", "Suggests": "covr, DBI, knitr, lattice, methods, rmarkdown, RSQLite,\ntestthat (>= 2.1.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "Collate": "'local_.R' 'with_.R' 'collate.R' 'connection.R' 'db.R'\n'defer.R' 'wrap.R' 'devices.R' 'dir.R' 'env.R' 'file.R'\n'libpaths.R' 'locale.R' 'makevars.R' 'namespace.R' 'options.R'\n'par.R' 'path.R' 'rng.R' 'seed.R' 'sink.R' 'tempfile.R'\n'timezone.R' 'torture.R' 'utils.R' 'with.R'", "NeedsCompilation": "no", "Packaged": "2020-04-20 21:18:04 UTC; jhester", "Author": "Jim Hester [aut, cre],\n Kirill Müller [aut],\n Kevin Ushey [aut],\n Hadley Wickham [aut],\n Winston Chang [aut],\n Richard Cotton [ctb],\n RStudio [cph]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-04-20 22:10:02 UTC", "Built": "R 3.6.2; ; 2020-04-21 06:03:03 UTC; unix" } }, "xtable": { "Source": "CRAN", "Repository": "https://cran.rstudio.com/", "description": { "Package": "xtable", "Version": "1.8-4", "Date": "2019-04-08", "Title": "Export Tables to LaTeX or HTML", "Authors@R": "c(person(\"David B.\", \"Dahl\", role=\"aut\"),\n person(\"David\", \"Scott\", role=c(\"aut\",\"cre\"),\n email=\"d.scott@auckland.ac.nz\"),\n person(\"Charles\", \"Roosen\", role=\"aut\"),\n person(\"Arni\", \"Magnusson\", role=\"aut\"),\n person(\"Jonathan\", \"Swinton\", role=\"aut\"),\n person(\"Ajay\", \"Shah\", role=\"ctb\"),\n person(\"Arne\", \"Henningsen\", role=\"ctb\"),\n person(\"Benno\", \"Puetz\", role=\"ctb\"),\n person(\"Bernhard\", \"Pfaff\", role=\"ctb\"),\n person(\"Claudio\", \"Agostinelli\", role=\"ctb\"),\n person(\"Claudius\", \"Loehnert\", role=\"ctb\"),\n person(\"David\", \"Mitchell\", role=\"ctb\"),\n person(\"David\", \"Whiting\", role=\"ctb\"),\n person(\"Fernando da\", \"Rosa\", role=\"ctb\"),\n person(\"Guido\", \"Gay\", role=\"ctb\"),\n person(\"Guido\", \"Schulz\", role=\"ctb\"),\n person(\"Ian\", \"Fellows\", role=\"ctb\"),\n person(\"Jeff\", \"Laake\", role=\"ctb\"),\n person(\"John\", \"Walker\", role=\"ctb\"),\n person(\"Jun\", \"Yan\", role=\"ctb\"),\n person(\"Liviu\", \"Andronic\", role=\"ctb\"),\n person(\"Markus\", \"Loecher\", role=\"ctb\"),\n person(\"Martin\", \"Gubri\", role=\"ctb\"),\n person(\"Matthieu\", \"Stigler\", role=\"ctb\"),\n person(\"Robert\", \"Castelo\", role=\"ctb\"),\n person(\"Seth\", \"Falcon\", role=\"ctb\"),\n person(\"Stefan\", \"Edwards\", role=\"ctb\"),\n person(\"Sven\", \"Garbade\", role=\"ctb\"),\n person(\"Uwe\", \"Ligges\", role=\"ctb\"))", "Maintainer": "David Scott ", "Imports": "stats, utils", "Suggests": "knitr, plm, zoo, survival", "VignetteBuilder": "knitr", "Description": "Coerce data to LaTeX and HTML tables.", "URL": "http://xtable.r-forge.r-project.org/", "Depends": "R (>= 2.10.0)", "License": "GPL (>= 2)", "Repository": "CRAN", "NeedsCompilation": "no", "Packaged": "2019-04-21 10:56:51 UTC; dsco036", "Author": "David B. Dahl [aut],\n David Scott [aut, cre],\n Charles Roosen [aut],\n Arni Magnusson [aut],\n Jonathan Swinton [aut],\n Ajay Shah [ctb],\n Arne Henningsen [ctb],\n Benno Puetz [ctb],\n Bernhard Pfaff [ctb],\n Claudio Agostinelli [ctb],\n Claudius Loehnert [ctb],\n David Mitchell [ctb],\n David Whiting [ctb],\n Fernando da Rosa [ctb],\n Guido Gay [ctb],\n Guido Schulz [ctb],\n Ian Fellows [ctb],\n Jeff Laake [ctb],\n John Walker [ctb],\n Jun Yan [ctb],\n Liviu Andronic [ctb],\n Markus Loecher [ctb],\n Martin Gubri [ctb],\n Matthieu Stigler [ctb],\n Robert Castelo [ctb],\n Seth Falcon [ctb],\n Stefan Edwards [ctb],\n Sven Garbade [ctb],\n Uwe Ligges [ctb]", "Date/Publication": "2019-04-21 12:20:03 UTC", "Built": "R 3.6.0; ; 2019-04-26 19:49:57 UTC; unix" } } }, "files": { "app.R": { "checksum": "991bd61cc7edc380050b1b8556816546" }, "packrat/desc/base64enc": { "checksum": "4968159e8f1ce5fdd79257f19560b1bd" }, "packrat/desc/BH": { "checksum": "5b39eb49c9837cf1e8766594245d3150" }, "packrat/desc/commonmark": { "checksum": "9185e6d30f52f4a45e76fe40c9eddd0d" }, "packrat/desc/crayon": { "checksum": "b24ecaa8a9670c0265dba74ec47767ba" }, "packrat/desc/digest": { "checksum": "cfdf99fa13a6c8c0af23140d3a6e8fd1" }, "packrat/desc/fastmap": { "checksum": "0c7a7f8a8db47476f86a2762b913369c" }, "packrat/desc/glue": { "checksum": "d0284339ad3ab13884d58dc6f84ba297" }, "packrat/desc/htmltools": { "checksum": "414cae2b2b275c089332f2904a8adc0f" }, "packrat/desc/httpuv": { "checksum": "611a43e785b14f240c50bf4a456e2b06" }, "packrat/desc/jsonlite": { "checksum": "cf1846bc59dda995189783b2d4040738" }, "packrat/desc/later": { "checksum": "63b387eb92a530d89724ca8a48d57156" }, "packrat/desc/magrittr": { "checksum": "e8675129e65235b7fb944a2a5635d1d0" }, "packrat/desc/mime": { "checksum": "fa29cb5a42d6ee931779ae149d514c0f" }, "packrat/desc/pacman": { "checksum": "435662364cfd8206ce47589b7e614a32" }, "packrat/desc/promises": { "checksum": "fde72778fd262ffa84438336cc4282f8" }, "packrat/desc/R6": { "checksum": "1d17f437b61caa09a4dc5853577cb1bc" }, "packrat/desc/Rcpp": { "checksum": "a22bb192c2483379cdde13881e8f04a0" }, "packrat/desc/remotes": { "checksum": "734855db69e67211cc4a8ebc9e122531" }, "packrat/desc/rlang": { "checksum": "87d9851799d1e0b723f5b88004c18793" }, "packrat/desc/shiny": { "checksum": "a94a99be3b2928b57ea3175840622ea5" }, "packrat/desc/sourcetools": { "checksum": "966c4e942ba212e977a95f8fff0eb356" }, "packrat/desc/withr": { "checksum": "c9381be5f182b0ccf32a30e4777bdbf1" }, "packrat/desc/xtable": { "checksum": "526e6f7be3cf3228226e3aab27258be6" }, "packrat/packrat.lock": { "checksum": "3f64e93e53fe91142bae881a4b980d94" } }, "users": null } ================================================ FILE: apps/coffeeApp/app.R ================================================ # Credit: build on the example in https://rstudio.github.io/leaflet/shiny.html library(sf) library(shiny) library(spData) library(leaflet) library(dplyr) world_coffee = left_join(world, coffee_data) pal = colorNumeric(palette = "RdYlBu", domain = c(0, 4000)) ui = fluidPage( sidebarPanel( sliderInput("range", "Coffee Production", 0, 4000, value = c(1000, 4000), step = 100), selectInput("year", "Year", c(2016, 2017)), checkboxInput("legend", "Show legend", FALSE) ), mainPanel( leafletOutput("map") ) ) server = function(input, output, session) { map_centre = st_centroid(world |> filter(name_long == "Brazil")) |> st_coordinates() # This reactive expression returns a character string representing the selected variable yr = reactive({ paste0("coffee_production_", input$year) }) # Reactive expression for the data subset to what the user selected filteredData = reactive({ world_coffee$Production = world_coffee[[yr()]] filter(world_coffee, Production >= input$range[1] & Production <= input$range[2]) }) output$map = renderLeaflet({ # Things that do not change go here: leaflet() |> addTiles() |> setView(lng = map_centre[1], map_centre[2], zoom = 2) }) # Changes to the map performed in an observer observe({ proxy = leafletProxy("map", data = filteredData()) |> clearShapes() # Show or hide legend proxy |> clearControls() |> addPolygons(fillColor = ~pal(Production)) if (input$legend) { proxy |> addLegend(position = "bottomright", pal = pal, values = ~Production) } }) } shinyApp(ui, server) ================================================ FILE: apps/coffeeApp/manifest.json ================================================ { "version": 1, "locale": "en_GB", "platform": "4.0.2", "metadata": { "appmode": "shiny", "primary_rmd": null, "primary_html": null, "content_category": null, "has_parameters": false }, "packages": { "BH": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "BH", "Type": "Package", "Title": "Boost C++ Header Files", "Version": "1.72.0-3", "Date": "2019-12-28", "Author": "Dirk Eddelbuettel, John W. Emerson and Michael J. Kane", "Maintainer": "Dirk Eddelbuettel ", "Description": "Boost provides free peer-reviewed portable C++ source \n libraries. A large part of Boost is provided as C++ template code\n which is resolved entirely at compile-time without linking. This \n package aims to provide the most useful subset of Boost libraries \n for template use among CRAN packages. By placing these libraries in \n this package, we offer a more efficient distribution system for CRAN \n as replication of this code in the sources of other packages is \n avoided. As of release 1.72.0-3, the following Boost libraries are\n included: 'accumulators' 'algorithm' 'align' 'any' 'atomic' 'bimap'\n 'bind' 'circular_buffer' 'compute' 'concept' 'config' 'container'\n 'date_time' 'detail' 'dynamic_bitset' 'exception' 'flyweight'\n 'foreach' 'functional' 'fusion' 'geometry' 'graph' 'heap' 'icl'\n 'integer' 'interprocess' 'intrusive' 'io' 'iostreams' 'iterator'\n 'math' 'move' 'mp11' 'mpl' 'multiprcecision' 'numeric' 'pending'\n 'phoenix' 'polygon' 'preprocessor' 'propery_tree' 'random' 'range'\n 'scope_exit' 'smart_ptr' 'sort' 'spirit' 'tuple' 'type_traits'\n 'typeof' 'unordered' 'utility' 'uuid'.", "License": "BSL-1.0", "URL": "https://github.com/eddelbuettel/bh", "BugReports": "https://github.com/eddelbuettel/bh/issues", "NeedsCompilation": "no", "Packaged": "2019-12-28 14:48:54.73981 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-01-08 23:20:08 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:51:57 +0000\"; unix" } }, "DBI": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "DBI", "Title": "R Database Interface", "Version": "1.1.0", "Date": "2019-12-15", "Authors@R": "\n c(person(given = \"R Special Interest Group on Databases (R-SIG-DB)\",\n role = \"aut\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\"),\n person(given = \"Kirill\",\n family = \"M\\u00fcller\",\n role = c(\"aut\", \"cre\"),\n email = \"krlmlr+r@mailbox.org\",\n comment = c(ORCID = \"0000-0002-1416-3412\")),\n person(given = \"R Consortium\",\n role = \"fnd\"))", "Description": "A database interface definition for communication\n between R and relational database management systems. All classes in\n this package are virtual and need to be extended by the various R/DBMS\n implementations.", "License": "LGPL (>= 2.1)", "URL": "http://r-dbi.github.io/DBI, https://github.com/r-dbi/DBI", "BugReports": "https://github.com/r-dbi/DBI/issues", "Depends": "methods, R (>= 3.0.0)", "Suggests": "blob, covr, hms, knitr, magrittr, rmarkdown, rprojroot,\nRSQLite (>= 1.1-2), testthat, xml2", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "RoxygenNote": "7.0.2", "Collate": "'hidden.R' 'DBObject.R' 'DBDriver.R' 'table.R'\n'DBConnection.R' 'ANSI.R' 'DBConnector.R' 'DBI-package.R'\n'DBResult.R' 'data-types.R' 'data.R' 'deprecated.R'\n'interpolate.R' 'list-pairs.R' 'quote.R' 'rd.R' 'rownames.R'\n'table-create.R' 'table-insert.R' 'transactions.R'", "NeedsCompilation": "no", "Packaged": "2019-12-15 08:59:43 UTC; kirill", "Author": "R Special Interest Group on Databases (R-SIG-DB) [aut],\n Hadley Wickham [aut],\n Kirill Müller [aut, cre] (),\n R Consortium [fnd]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2019-12-15 09:50:02 UTC", "Built": "R 4.0.0; ; 'Wed, 15 Apr 2020 00:41:20 +0000'; unix" } }, "KernSmooth": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "KernSmooth", "Priority": "recommended", "Version": "2.23-17", "Date": "2020-04-26", "Title": "Functions for Kernel Smoothing Supporting Wand & Jones (1995)", "Authors@R": "c(person(\"Matt\", \"Wand\", role = \"aut\",\n\t email = \"Matt.Wand@uts.edu.au\"),\n\t person(\"Cleve\", \"Moler\", role = \"ctb\",\n\t comment = \"LINPACK routines in src/d*\"),\n person(\"Brian\", \"Ripley\", role = c(\"trl\", \"cre\", \"ctb\"),\n email = \"ripley@stats.ox.ac.uk\",\n\t\t comment = \"R port and updates\"))", "Depends": "R (>= 2.5.0), stats", "Suggests": "MASS", "Description": "Functions for kernel smoothing (and density estimation)\n corresponding to the book: \n Wand, M.P. and Jones, M.C. (1995) \"Kernel Smoothing\".", "License": "Unlimited", "ByteCompile": "yes", "NeedsCompilation": "yes", "Packaged": "2020-04-26 09:36:57 UTC; ripley", "Author": "Matt Wand [aut],\n Cleve Moler [ctb] (LINPACK routines in src/d*),\n Brian Ripley [trl, cre, ctb] (R port and updates)", "Maintainer": "Brian Ripley ", "Repository": "CRAN", "Date/Publication": "2020-04-26 10:31:57 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Tue, 28 Apr 2020 17:37:41 +0000\"; unix" } }, "MASS": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "MASS", "Priority": "recommended", "Version": "7.3-53", "Date": "2020-09-06", "Revision": "$Rev: 3531 $", "Depends": "R (>= 3.1.0), grDevices, graphics, stats, utils", "Imports": "methods", "Suggests": "lattice, nlme, nnet, survival", "Authors@R": "c(person(\"Brian\", \"Ripley\", role = c(\"aut\", \"cre\", \"cph\"),\n email = \"ripley@stats.ox.ac.uk\"),\n\t person(\"Bill\", \"Venables\", role = \"ctb\"),\n\t person(c(\"Douglas\", \"M.\"), \"Bates\", role = \"ctb\"),\n\t person(\"Kurt\", \"Hornik\", role = \"trl\",\n comment = \"partial port ca 1998\"),\n\t person(\"Albrecht\", \"Gebhardt\", role = \"trl\",\n comment = \"partial port ca 1998\"),\n\t person(\"David\", \"Firth\", role = \"ctb\"))", "Description": "Functions and datasets to support Venables and Ripley,\n \"Modern Applied Statistics with S\" (4th edition, 2002).", "Title": "Support Functions and Datasets for Venables and Ripley's MASS", "LazyData": "yes", "ByteCompile": "yes", "License": "GPL-2 | GPL-3", "URL": "http://www.stats.ox.ac.uk/pub/MASS4/", "Contact": "", "NeedsCompilation": "yes", "Packaged": "2020-09-09 08:42:51 UTC; ripley", "Author": "Brian Ripley [aut, cre, cph],\n Bill Venables [ctb],\n Douglas M. Bates [ctb],\n Kurt Hornik [trl] (partial port ca 1998),\n Albrecht Gebhardt [trl] (partial port ca 1998),\n David Firth [ctb]", "Maintainer": "Brian Ripley ", "Repository": "CRAN", "Date/Publication": "2020-09-09 11:22:59 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 'Sat, 12 Sep 2020 13:23:38 +0000'; unix" } }, "Matrix": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "Matrix", "Version": "1.2-18", "Date": "2019-11-25", "Priority": "recommended", "Title": "Sparse and Dense Matrix Classes and Methods", "Contact": "Doug and Martin ", "Maintainer": "Martin Maechler ", "Authors@R": "c(person(\"Douglas\",\"Bates\", role=\"aut\")\n , person(\"Martin\",\"Maechler\", role = c(\"aut\",\"cre\"), email=\"mmaechler+Matrix@gmail.com\",\n comment = c(ORCID = \"0000-0002-8685-9910\"))\n , person(\"Timothy A.\", \"Davis\", role=\"ctb\",\n comment = c(\"SuiteSparse and 'cs' C libraries, notably CHOLMOD, AMD;\n\t\t collaborators listed in\n\t\t\tdir(pattern = '^[A-Z]+[.]txt$', full.names=TRUE,\n\t\t\t system.file('doc', 'SuiteSparse', package='Matrix'))\"))\n , person(\"Jens\", \"Oehlschlägel\", role=\"ctb\", comment=\"initial nearPD()\")\n , person(\"Jason\", \"Riedy\", role=\"ctb\",\n comment = c(\"condest() and onenormest() for octave\",\n \t \t \"Copyright: Regents of the University of California\"))\n , person(\"R Core Team\", role = \"ctb\", comment=\"base R matrix implementation\")\n )", "Description": "A rich hierarchy of matrix classes, including triangular,\n symmetric, and diagonal matrices, both dense and sparse and with\n pattern, logical and numeric entries. Numerous methods for and\n operations on these matrices, using 'LAPACK' and 'SuiteSparse' libraries.", "Depends": "R (>= 3.2.0)", "Imports": "methods, graphics, grid, stats, utils, lattice", "Suggests": "expm, MASS", "Enhances": "MatrixModels, graph, SparseM, sfsmisc", "Encoding": "UTF-8", "LazyData": "no", "LazyDataNote": "not possible, since we use data/*.R *and* our classes", "BuildResaveData": "no", "License": "GPL (>= 2) | file LICENCE", "URL": "http://Matrix.R-forge.R-project.org/", "BugReports": "https://r-forge.r-project.org/tracker/?group_id=61", "NeedsCompilation": "yes", "Packaged": "2019-11-26 17:17:19 UTC; maechler", "Author": "Douglas Bates [aut],\n Martin Maechler [aut, cre] (),\n Timothy A. Davis [ctb] (SuiteSparse and 'cs' C libraries, notably\n CHOLMOD, AMD; collaborators listed in dir(pattern =\n '^[A-Z]+[.]txt$', full.names=TRUE, system.file('doc',\n 'SuiteSparse', package='Matrix'))),\n Jens Oehlschlägel [ctb] (initial nearPD()),\n Jason Riedy [ctb] (condest() and onenormest() for octave, Copyright:\n Regents of the University of California),\n R Core Team [ctb] (base R matrix implementation)", "Repository": "CRAN", "Date/Publication": "2019-11-27 15:20:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 03:54:22 +0000\"; unix" } }, "R6": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "R6", "Title": "Encapsulated Classes with Reference Semantics", "Version": "2.4.1", "Authors@R": "person(\"Winston\", \"Chang\", role = c(\"aut\", \"cre\"), email = \"winston@stdout.org\")", "Description": "Creates classes with reference semantics, similar to R's built-in\n reference classes. Compared to reference classes, R6 classes are simpler\n and lighter-weight, and they are not built on S4 classes so they do not\n require the methods package. These classes allow public and private\n members, and they support inheritance, even when the classes are defined in\n different packages.", "Depends": "R (>= 3.0)", "Suggests": "knitr, microbenchmark, pryr, testthat, ggplot2, scales", "License": "MIT + file LICENSE", "URL": "https://r6.r-lib.org, https://github.com/r-lib/R6/", "LazyData": "true", "BugReports": "https://github.com/r-lib/R6/issues", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-11-12 20:00:15 UTC; winston", "Author": "Winston Chang [aut, cre]", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2019-11-12 22:50:03 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:52:51 +0000\"; unix" } }, "RColorBrewer": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "RColorBrewer", "Version": "1.1-2", "Date": "2014-12-07", "Title": "ColorBrewer Palettes", "Authors@R": "c(person(given = \"Erich\", family = \"Neuwirth\", role = c(\"aut\",\n \"cre\"), email = \"erich.neuwirth@univie.ac.at\"))", "Author": "Erich Neuwirth [aut, cre]", "Maintainer": "Erich Neuwirth ", "Depends": "R (>= 2.0.0)", "Description": "Provides color schemes for maps (and other graphics)\n designed by Cynthia Brewer as described at http://colorbrewer2.org", "License": "Apache License 2.0", "Packaged": "2014-12-06 23:59:42 UTC; neuwirth", "NeedsCompilation": "no", "Repository": "CRAN", "Date/Publication": "2014-12-07 08:28:55", "Built": "R 4.0.0; ; 'Mon, 13 Apr 2020 20:53:53 +0000'; unix" } }, "Rcpp": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "Rcpp", "Title": "Seamless R and C++ Integration", "Version": "1.0.5", "Date": "2020-07-01", "Author": "Dirk Eddelbuettel, Romain Francois, JJ Allaire, Kevin Ushey, Qiang Kou,\n Nathan Russell, Douglas Bates and John Chambers", "Maintainer": "Dirk Eddelbuettel ", "Description": "The 'Rcpp' package provides R functions as well as C++ classes which\n offer a seamless integration of R and C++. Many R data types and objects can be\n mapped back and forth to C++ equivalents which facilitates both writing of new\n code as well as easier integration of third-party libraries. Documentation\n about 'Rcpp' is provided by several vignettes included in this package, via the\n 'Rcpp Gallery' site at , the paper by Eddelbuettel and\n Francois (2011, ), the book by Eddelbuettel (2013,\n ) and the paper by Eddelbuettel and Balamuta (2018,\n ); see 'citation(\"Rcpp\")' for details.", "Imports": "methods, utils", "Suggests": "tinytest, inline, rbenchmark, pkgKitten (>= 0.1.2)", "URL": "http://www.rcpp.org, https://dirk.eddelbuettel.com/code/rcpp.html,\nhttps://github.com/RcppCore/Rcpp", "License": "GPL (>= 2)", "BugReports": "https://github.com/RcppCore/Rcpp/issues", "MailingList": "rcpp-devel@lists.r-forge.r-project.org", "RoxygenNote": "6.1.1", "NeedsCompilation": "yes", "Packaged": "2020-07-01 16:50:09.72105 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-07-06 13:40:08 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Mon, 06 Jul 2020 14:35:58 +0000\"; unix" } }, "askpass": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "askpass", "Type": "Package", "Title": "Safe Password Entry for R, Git, and SSH", "Version": "1.1", "Authors@R": "person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), \n email = \"jeroen@berkeley.edu\", comment = c(ORCID = \"0000-0002-4035-0289\"))", "Description": "Cross-platform utilities for prompting the user for credentials or a \n passphrase, for example to authenticate with a server or read a protected key.\n Includes native programs for MacOS and Windows, hence no 'tcltk' is required. \n Password entry can be invoked in two different ways: directly from R via the \n askpass() function, or indirectly as password-entry back-end for 'ssh-agent' \n or 'git-credential' via the SSH_ASKPASS and GIT_ASKPASS environment variables.\n Thereby the user can be prompted for credentials or a passphrase if needed \n when R calls out to git or ssh.", "License": "MIT + file LICENSE", "URL": "https://github.com/jeroen/askpass#readme", "BugReports": "https://github.com/jeroen/askpass/issues", "Encoding": "UTF-8", "LazyData": "true", "Imports": "sys (>= 2.1)", "RoxygenNote": "6.1.1", "Suggests": "testthat", "Language": "en-US", "NeedsCompilation": "yes", "Packaged": "2019-01-13 12:08:17 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] ()", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2019-01-13 12:50:03 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 03:59:29 +0000\"; unix" } }, "assertthat": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "assertthat", "Title": "Easy Pre and Post Assertions", "Version": "0.2.1", "Authors@R": "\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", c(\"aut\", \"cre\"))", "Description": "An extension to stopifnot() that makes it easy to declare \n the pre and post conditions that you code should satisfy, while also \n producing friendly error messages so that your users know what's gone\n wrong.", "License": "GPL-3", "Imports": "tools", "Suggests": "testthat, covr", "RoxygenNote": "6.0.1", "Collate": "'assert-that.r' 'on-failure.r' 'assertions-file.r'\n'assertions-scalar.R' 'assertions.r' 'base.r'\n'base-comparison.r' 'base-is.r' 'base-logical.r' 'base-misc.r'\n'utils.r' 'validate-that.R'", "NeedsCompilation": "no", "Packaged": "2019-03-21 13:11:01 UTC; hadley", "Author": "Hadley Wickham [aut, cre]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2019-03-21 14:53:46 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:49:01 +0000\"; unix" } }, "backports": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "backports", "Type": "Package", "Title": "Reimplementations of Functions Introduced Since R-3.0.0", "Version": "1.1.9", "Authors@R": "c(\n person(\"Michel\", \"Lang\", NULL, \"michellang@gmail.com\",\n role = c(\"cre\", \"aut\"), comment = c(ORCID = \"0000-0001-9754-0393\")),\n person(\"R Core Team\", role = \"aut\"))", "Maintainer": "Michel Lang ", "Description": "\n Functions introduced or changed since R v3.0.0 are re-implemented in this\n package. The backports are conditionally exported in order to let R resolve\n the function name to either the implemented backport, or the respective base\n version, if available. Package developers can make use of new functions or\n arguments by selectively importing specific backports to\n support older installations.", "URL": "https://github.com/r-lib/backports", "BugReports": "https://github.com/r-lib/backports/issues", "License": "GPL-2 | GPL-3", "NeedsCompilation": "yes", "ByteCompile": "yes", "Depends": "R (>= 3.0.0)", "Imports": "utils", "Encoding": "UTF-8", "RoxygenNote": "7.1.1", "Packaged": "2020-08-24 10:54:50 UTC; lang", "Author": "Michel Lang [cre, aut] (),\n R Core Team [aut]", "Repository": "CRAN", "Date/Publication": "2020-08-24 14:30:13 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Tue, 25 Aug 2020 02:17:18 +0000\"; unix" } }, "base64enc": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "base64enc", "Version": "0.1-3", "Title": "Tools for base64 encoding", "Author": "Simon Urbanek ", "Maintainer": "Simon Urbanek ", "Depends": "R (>= 2.9.0)", "Enhances": "png", "Description": "This package provides tools for handling base64 encoding. It is more flexible than the orphaned base64 package.", "License": "GPL-2 | GPL-3", "URL": "http://www.rforge.net/base64enc", "NeedsCompilation": "yes", "Packaged": "2015-02-04 20:31:00 UTC; svnuser", "Repository": "CRAN", "Date/Publication": "2015-07-28 08:03:37", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:41:32 UTC; unix" } }, "blob": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "blob", "Title": "A Simple S3 Class for Representing Vectors of Binary Data\n('BLOBS')", "Version": "1.2.1", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\"),\n person(given = \"Kirill\",\n family = \"Müller\",\n role = \"cre\",\n email = \"krlmlr+r@mailbox.org\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "R's raw vector is useful for storing a single\n binary object. What if you want to put a vector of them in a data\n frame? The 'blob' package provides the blob object, a list of raw\n vectors, suitable for use as a column in data frame.", "License": "GPL-3", "URL": "https://github.com/tidyverse/blob", "BugReports": "https://github.com/tidyverse/blob/issues", "Imports": "methods, rlang, vctrs (>= 0.2.1)", "Suggests": "covr, crayon, pillar (>= 1.2.1), testthat", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.0.2", "NeedsCompilation": "no", "Packaged": "2020-01-20 22:32:38 UTC; kirill", "Author": "Hadley Wickham [aut],\n Kirill Müller [cre],\n RStudio [cph]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2020-01-20 22:50:02 UTC", "Built": "R 4.0.0; ; \"Fri, 17 Apr 2020 20:20:48 +0000\"; unix" } }, "broom": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Type": "Package", "Package": "broom", "Title": "Convert Statistical Objects into Tidy Tibbles", "Version": "0.7.0", "Authors@R": "\n c(person(given = \"David\",\n family = \"Robinson\",\n role = \"aut\",\n email = \"admiral.david@gmail.com\"),\n person(given = \"Alex\",\n family = \"Hayes\",\n role = c(\"aut\", \"cre\"),\n email = \"alexpghayes@gmail.com\",\n comment = c(ORCID = \"0000-0002-4985-5160\")),\n person(given = \"Simon\",\n family = \"Couch\",\n role = \"aut\",\n email = \"simonpatrickcouch@gmail.com\"),\n person(given = \"Indrajeet\",\n family = \"Patil\",\n role = \"ctb\",\n email = \"patilindrajeet.science@gmail.com\",\n comment = c(ORCID = \"0000-0003-1995-6531\")),\n person(given = \"Derek\",\n family = \"Chiu\",\n role = \"ctb\",\n email = \"dchiu@bccrc.ca\"),\n person(given = \"Matthieu\",\n family = \"Gomez\",\n role = \"ctb\",\n email = \"mattg@princeton.edu\"),\n person(given = \"Boris\",\n family = \"Demeshev\",\n role = \"ctb\",\n email = \"boris.demeshev@gmail.com\"),\n person(given = \"Dieter\",\n family = \"Menne\",\n role = \"ctb\",\n email = \"dieter.menne@menne-biomed.de\"),\n person(given = \"Benjamin\",\n family = \"Nutter\",\n role = \"ctb\",\n email = \"nutter@battelle.org\"),\n person(given = \"Luke\",\n family = \"Johnston\",\n role = \"ctb\",\n email = \"luke.johnston@mail.utoronto.ca\"),\n person(given = \"Ben\",\n family = \"Bolker\",\n role = \"ctb\",\n email = \"bolker@mcmaster.ca\"),\n person(given = \"Francois\",\n family = \"Briatte\",\n role = \"ctb\",\n email = \"f.briatte@gmail.com\"),\n person(given = \"Jeffrey\",\n family = \"Arnold\",\n role = \"ctb\",\n email = \"jeffrey.arnold@gmail.com\"),\n person(given = \"Jonah\",\n family = \"Gabry\",\n role = \"ctb\",\n email = \"jsg2201@columbia.edu\"),\n person(given = \"Luciano\",\n family = \"Selzer\",\n role = \"ctb\",\n email = \"luciano.selzer@gmail.com\"),\n person(given = \"Gavin\",\n family = \"Simpson\",\n role = \"ctb\",\n email = \"ucfagls@gmail.com\"),\n person(given = \"Jens\",\n family = \"Preussner\",\n role = \"ctb\",\n email = \" jens.preussner@mpi-bn.mpg.de\"),\n person(given = \"Jay\",\n family = \"Hesselberth\",\n role = \"ctb\",\n email = \"jay.hesselberth@gmail.com\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"ctb\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Matthew\",\n family = \"Lincoln\",\n role = \"ctb\",\n email = \"matthew.d.lincoln@gmail.com\"),\n person(given = \"Alessandro\",\n family = \"Gasparini\",\n role = \"ctb\",\n email = \"ag475@leicester.ac.uk\"),\n person(given = \"Lukasz\",\n family = \"Komsta\",\n role = \"ctb\",\n email = \"lukasz.komsta@umlub.pl\"),\n person(given = \"Frederick\",\n family = \"Novometsky\",\n role = \"ctb\"),\n person(given = \"Wilson\",\n family = \"Freitas\",\n role = \"ctb\"),\n person(given = \"Michelle\",\n family = \"Evans\",\n role = \"ctb\"),\n person(given = \"Jason Cory\",\n family = \"Brunson\",\n role = \"ctb\",\n email = \"cornelioid@gmail.com\"),\n person(given = \"Simon\",\n family = \"Jackson\",\n role = \"ctb\",\n email = \"drsimonjackson@gmail.com\"),\n person(given = \"Ben\",\n family = \"Whalley\",\n role = \"ctb\",\n email = \"ben.whalley@plymouth.ac.uk\"),\n person(given = \"Karissa\",\n family = \"Whiting\",\n role = \"ctb\",\n email = \"karissa.whiting@gmail.com\"),\n person(given = \"Yves\",\n family = \"Rosseel\",\n role = \"ctb\",\n email = \"yrosseel@gmail.com\"),\n person(given = \"Michael\",\n family = \"Kuehn\",\n role = \"ctb\",\n email = \"mkuehn10@gmail.com\"),\n person(given = \"Jorge\",\n family = \"Cimentada\",\n role = \"ctb\",\n email = \"cimentadaj@gmail.com\"),\n person(given = \"Erle\",\n family = \"Holgersen\",\n role = \"ctb\",\n email = \"erle.holgersen@gmail.com\"),\n person(given = \"Karl\",\n family = \"Dunkle Werner\",\n role = \"ctb\",\n comment = c(ORCID = \"0000-0003-0523-7309\")),\n person(given = \"Ethan\",\n family = \"Christensen\",\n role = \"ctb\",\n email = \"christensen.ej@gmail.com\"),\n person(given = \"Steven\",\n family = \"Pav\",\n role = \"ctb\",\n email = \"shabbychef@gmail.com\"),\n person(given = \"Paul\",\n family = \"PJ\",\n role = \"ctb\",\n email = \"pjpaul.stephens@gmail.com\"),\n person(given = \"Ben\",\n family = \"Schneider\",\n role = \"ctb\",\n email = \"benjamin.julius.schneider@gmail.com\"),\n person(given = \"Patrick\",\n family = \"Kennedy\",\n role = \"ctb\",\n email = \"pkqstr@protonmail.com\"),\n person(given = \"Lily\",\n family = \"Medina\",\n role = \"ctb\",\n email = \"lilymiru@gmail.com\"),\n person(given = \"Brian\",\n family = \"Fannin\",\n role = \"ctb\",\n email = \"captain@pirategrunt.com\"),\n person(given = \"Jason\",\n family = \"Muhlenkamp\",\n role = \"ctb\",\n email = \"jason.muhlenkamp@gmail.com\"),\n person(given = \"Matt\",\n family = \"Lehman\",\n role = \"ctb\"),\n person(given = \"Bill\",\n family = \"Denney\",\n role = \"ctb\",\n email = \"wdenney@humanpredictions.com\",\n comment = c(ORCID = \"0000-0002-5759-428X\")),\n person(given = \"Nic\",\n family = \"Crane\",\n role = \"ctb\"),\n person(given = \"Andrew\",\n family = \"Bates\",\n role = \"ctb\"),\n person(given = \"Vincent\",\n family = \"Arel-Bundock\",\n role = \"ctb\",\n email = \"vincent.arel-bundock@umontreal.ca\",\n comment = c(ORCID = \"0000-0003-2042-7063\")),\n person(given = \"Hideaki\",\n family = \"Hayashi\",\n role = \"ctb\"),\n person(given = \"Luis\",\n family = \"Tobalina\",\n role = \"ctb\"),\n person(given = \"Annie\",\n family = \"Wang\",\n role = \"ctb\",\n email = \"anniewang.uc@gmail.com\"),\n person(given = \"Wei Yang\",\n family = \"Tham\",\n role = \"ctb\",\n email = \"weiyang.tham@gmail.com\"),\n person(given = \"Clara\",\n family = \"Wang\",\n role = \"ctb\",\n email = \"clara.wang.94@gmail.com\"),\n person(given = \"Abby\",\n family = \"Smith\",\n role = \"ctb\",\n email = \"als1@u.northwestern.edu\",\n comment = c(ORCID = \"0000-0002-3207-0375\")),\n person(given = \"Jasper\",\n family = \"Cooper\",\n role = \"ctb\",\n email = \"jaspercooper@gmail.com\",\n comment = c(ORCID = \"0000-0002-8639-3188\")),\n person(given = \"E Auden\",\n family = \"Krauska\",\n role = \"ctb\",\n email = \"krauskae@gmail.com\",\n comment = c(ORCID = \"0000-0002-1466-5850\")),\n person(given = \"Alex\",\n family = \"Wang\",\n role = \"ctb\",\n email = \"x249wang@uwaterloo.ca\"),\n person(given = \"Malcolm\",\n family = \"Barrett\",\n role = \"ctb\",\n email = \"malcolmbarrett@gmail.com\",\n comment = c(ORCID = \"0000-0003-0299-5825\")),\n person(given = \"Charles\",\n family = \"Gray\",\n role = \"ctb\",\n email = \"charlestigray@gmail.com\",\n comment = c(ORCID = \"0000-0002-9978-011X\")),\n person(given = \"Jared\",\n family = \"Wilber\",\n role = \"ctb\"),\n person(given = \"Vilmantas\",\n family = \"Gegzna\",\n role = \"ctb\",\n email = \"GegznaV@gmail.com\",\n comment = c(ORCID = \"0000-0002-9500-5167\")),\n person(given = \"Eduard\",\n family = \"Szoecs\",\n role = \"ctb\",\n email = \"eduardszoecs@gmail.com\"),\n person(given = \"Frederik\",\n family = \"Aust\",\n role = \"ctb\",\n email = \"frederik.aust@uni-koeln.de\",\n comment = c(ORCID = \"0000-0003-4900-788X\")),\n person(given = \"Angus\",\n family = \"Moore\",\n role = \"ctb\",\n email = \"angusmoore9@gmail.com\"),\n person(given = \"Nick\",\n family = \"Williams\",\n role = \"ctb\",\n email = \"ntwilliams.personal@gmail.com\"),\n person(given = \"Marius\",\n family = \"Barth\",\n role = \"ctb\",\n email = \"marius.barth.uni.koeln@gmail.com\",\n comment = c(ORCID = \"0000-0002-3421-6665\")),\n person(given = \"Bruna\",\n family = \"Wundervald\",\n role = \"ctb\",\n email = \"brunadaviesw@gmail.com\",\n comment = c(ORCID = \"0000-0001-8163-220X\")),\n person(given = \"Joyce\",\n family = \"Cahoon\",\n role = \"ctb\",\n email = \"joyceyu48@gmail.com\",\n comment = c(ORCID = \"0000-0001-7217-4702\")),\n person(given = \"Grant\",\n family = \"McDermott\",\n role = \"ctb\",\n email = \"grantmcd@uoregon.edu\",\n comment = c(ORCID = \"0000-0001-7883-8573\")),\n person(given = \"Kevin\",\n family = \"Zarca\",\n role = \"ctb\",\n email = \"kevin.zarca@gmail.com\"),\n person(given = \"Shiro\",\n family = \"Kuriwaki\",\n role = \"ctb\",\n email = \"shirokuriwaki@gmail.com\",\n comment = c(ORCID = \"0000-0002-5687-2647\")),\n person(given = \"Lukas\",\n family = \"Wallrich\",\n role = \"ctb\",\n email = \"lukas.wallrich@gmail.com\",\n comment = c(ORCID = \"0000-0003-2121-5177\")),\n person(given = \"James\",\n family = \"Martherus\",\n role = \"ctb\",\n email = \"james@martherus.com\",\n comment = c(ORCID = \"0000-0002-8285-3300\")),\n person(given = \"Chuliang\",\n family = \"Xiao\",\n role = \"ctb\",\n email = \"cxiao@umich.edu\",\n comment = c(ORCID = \"0000-0002-8466-9398\")),\n person(given = \"Joseph\",\n family = \"Larmarange\",\n role = \"ctb\",\n email = \"joseph@larmarange.net\"),\n person(given = \"Max\",\n family = \"Kuhn\",\n role = \"ctb\",\n email = \"max@rstudio.com\"),\n person(given = \"Michal\",\n family = \"Bojanowski\",\n role = \"ctb\",\n email = \"michal2992@gmail.com\"),\n person(given = \"Hakon\",\n family = \"Malmedal\",\n role = \"ctb\",\n email = \"hmalmedal@gmail.com\"),\n person(given = \"Clara\",\n family = \"Wang\",\n role = \"ctb\"),\n person(given = \"Sergio\",\n family = \"Oller\",\n role = \"ctb\",\n email = \"sergioller@gmail.com\"),\n person(given = \"Luke\",\n family = \"Sonnet\",\n role = \"ctb\",\n email = \"luke.sonnet@gmail.com\"),\n person(given = \"Jim\",\n family = \"Hester\",\n role = \"ctb\",\n email = \"jim.hester@rstudio.com\"),\n person(given = \"Cory\",\n family = \"Brunson\",\n role = \"ctb\",\n email = \"cornelioid@gmail.com\"),\n person(given = \"Ben\",\n family = \"Schneider\",\n role = \"ctb\",\n email = \"benjamin.julius.schneider@gmail.com\"),\n person(given = \"Bernie\",\n family = \"Gray\",\n role = \"ctb\",\n email = \"bfgray3@gmail.com\",\n comment = c(ORCID = \"0000-0001-9190-6032\")),\n person(given = \"Mara\",\n family = \"Averick\",\n role = \"ctb\",\n email = \"mara@rstudio.com\"),\n person(given = \"Aaron\",\n family = \"Jacobs\",\n role = \"ctb\",\n email = \"atheriel@gmail.com\"),\n person(given = \"Andreas\",\n family = \"Bender\",\n role = \"ctb\",\n email = \"bender.at.R@gmail.com\"),\n person(given = \"Sven\",\n family = \"Templer\",\n role = \"ctb\",\n email = \"sven.templer@gmail.com\"),\n person(given = \"Paul-Christian\",\n family = \"Buerkner\",\n role = \"ctb\",\n email = \"paul.buerkner@gmail.com\"),\n person(given = \"Matthew\",\n family = \"Kay\",\n role = \"ctb\",\n email = \"mjskay@umich.edu\"),\n person(given = \"Erwan\",\n family = \"Le Pennec\",\n role = \"ctb\",\n email = \"lepennec@gmail.com\"),\n person(given = \"Johan\",\n family = \"Junkka\",\n role = \"ctb\",\n email = \"johan.junkka@umu.se\"),\n person(given = \"Hao\",\n family = \"Zhu\",\n role = \"ctb\",\n email = \"haozhu233@gmail.com\"),\n person(given = \"Benjamin\",\n family = \"Soltoff\",\n role = \"ctb\",\n email = \"soltoffbc@uchicago.edu\"),\n person(given = \"Zoe\",\n family = \"Wilkinson Saldana\",\n role = \"ctb\",\n email = \"zoewsaldana@gmail.com\"),\n person(given = \"Tyler\",\n family = \"Littlefield\",\n role = \"ctb\",\n email = \"tylurp1@gmail.com\"),\n person(given = \"Charles T.\",\n family = \"Gray\",\n role = \"ctb\",\n email = \"charlestigray@gmail.com\"),\n person(given = \"Shabbh E.\",\n family = \"Banks\",\n role = \"ctb\"),\n person(given = \"Serina\",\n family = \"Robinson\",\n role = \"ctb\",\n email = \"robi0916@umn.edu\"),\n person(given = \"Roger\",\n family = \"Bivand\",\n role = \"ctb\",\n email = \"Roger.Bivand@nhh.no\"),\n person(given = \"Riinu\",\n family = \"Ots\",\n role = \"ctb\",\n email = \"riinuots@gmail.com\"),\n person(given = \"Nicholas\",\n family = \"Williams\",\n role = \"ctb\",\n email = \"ntwilliams.personal@gmail.com\"),\n person(given = \"Nina\",\n family = \"Jakobsen\",\n role = \"ctb\"),\n person(given = \"Michael\",\n family = \"Weylandt\",\n role = \"ctb\",\n email = \"michael.weylandt@gmail.com\"),\n person(given = \"Lisa\",\n family = \"Lendway\",\n role = \"ctb\",\n email = \"llendway@macalester.edu\"),\n person(given = \"Karl\",\n family = \"Hailperin\",\n role = \"ctb\",\n email = \"khailper@gmail.com\"),\n person(given = \"Josue\",\n family = \"Rodriguez\",\n role = \"ctb\",\n email = \"jerrodriguez@ucdavis.edu\"),\n person(given = \"Jenny\",\n family = \"Bryan\",\n role = \"ctb\",\n email = \"jenny@rstudio.com\"),\n person(given = \"Chris\",\n family = \"Jarvis\",\n role = \"ctb\",\n email = \"Christopher1.jarvis@gmail.com\"),\n person(given = \"Greg\",\n family = \"Macfarlane\",\n role = \"ctb\",\n email = \"gregmacfarlane@gmail.com\"),\n person(given = \"Brian\",\n family = \"Mannakee\",\n role = \"ctb\",\n email = \"bmannakee@gmail.com\"),\n person(given = \"Drew\",\n family = \"Tyre\",\n role = \"ctb\",\n email = \"atyre2@unl.edu\"),\n person(given = \"Shreyas\",\n family = \"Singh\",\n role = \"ctb\",\n email = \"shreyas.singh.298@gmail.com\"),\n person(given = \"Laurens\",\n family = \"Geffert\",\n role = \"ctb\",\n email = \"laurensgeffert@gmail.com\"),\n person(given = \"Hong\",\n family = \"Ooi\",\n role = \"ctb\",\n email = \"hongooi@microsoft.com\"),\n person(given = \"Henrik\",\n family = \"Bengtsson\",\n role = \"ctb\",\n email = \"henrikb@braju.com\"),\n person(given = \"Eduard\",\n family = \"Szocs\",\n role = \"ctb\",\n email = \"eduardszoecs@gmail.com\"),\n person(given = \"David\",\n family = \"Hugh-Jones\",\n role = \"ctb\",\n email = \"davidhughjones@gmail.com\"),\n person(given = \"Matthieu\",\n family = \"Stigler\",\n role = \"ctb\",\n email = \"Matthieu.Stigler@gmail.com\"))", "Description": "Summarizes key information about statistical\n objects in tidy tibbles. This makes it easy to report results, create\n plots and consistently work with large numbers of models at once.\n Broom provides three verbs that each provide different types of\n information about a model. tidy() summarizes information about model\n components such as coefficients of a regression. glance() reports\n information about an entire model, such as goodness of fit measures\n like AIC and BIC. augment() adds information about individual\n observations to a dataset, such as fitted values or influence\n measures.", "License": "MIT + file LICENSE", "URL": "https://broom.tidymodels.org/, http://github.com/tidymodels/broom", "BugReports": "http://github.com/tidymodels/broom/issues", "Depends": "R (>= 3.1)", "Imports": "backports, dplyr, ellipsis, generics (>= 0.0.2), glue,\nmethods, purrr, rlang, stringr, tibble (>= 3.0.0), tidyr", "Suggests": "AER, akima, AUC, bbmle, betareg, biglm, binGroup, boot,\nbtergm, car, caret, cluster, coda, covr, drc, e1071, emmeans,\nepiR, ergm, fixest (>= 0.3.1), gam (>= 1.15), gamlss,\ngamlss.data, gamlss.dist, gee, geepack, ggplot2, glmnet,\nglmnetUtils, gmm, Hmisc, irlba, joineRML, Kendall, knitr, ks,\nLahman, lavaan, leaps, lfe, lm.beta, lme4, lmodel2, lmtest,\nlsmeans, maps, maptools, MASS, Matrix, mclogit, mclust,\nmediation, metafor, mfx, mgcv, modeldata, modeltests, muhaz,\nmultcomp, network, nnet, orcutt (>= 2.2), ordinal, plm, poLCA,\npsych, quantreg, rgeos, rmarkdown, robust, robustbase, rsample,\nsandwich, sp, spdep, spatialreg, speedglm, spelling,\nstatnet.common, survey, survival, systemfit, testthat (>=\n2.1.0), tseries, zoo", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0.9000", "Language": "en-US", "Collate": "'aaa-documentation-helper.R' 'null-and-default-tidiers.R'\n'aer-tidiers.R' 'auc-tidiers.R' 'base-tidiers.R'\n'bbmle-tidiers.R' 'betareg-tidiers.R' 'biglm-tidiers.R'\n'bingroup-tidiers.R' 'boot-tidiers.R' 'broom-package.R'\n'broom.R' 'btergm-tidiers.R' 'car-tidiers.R' 'caret-tidiers.R'\n'data-frame-tidiers.R' 'deprecated-0-7-0.R' 'drc-tidiers.R'\n'emmeans-tidiers.R' 'epiR-tidiers.R' 'ergm-tidiers.R'\n'fixest-tidiers.R' 'gam-tidiers.R' 'gamlss-tidiers.R' 'gee.R'\n'geepack-tidiers.R' 'glmnet-cv-glmnet-tidiers.R'\n'glmnet-glmnet-tidiers.R' 'gmm-tidiers.R' 'hmisc-tidiers.R'\n'joinerml-tidiers.R' 'kendall-tidiers.R' 'ks-tidiers.R'\n'lavaan-tidiers.R' 'leaps.R' 'lfe-tidiers.R' 'list-irlba.R'\n'list-optim-tidiers.R' 'list-svd-tidiers.R' 'list-tidiers.R'\n'list-xyz-tidiers.R' 'lm-beta-tidiers.R' 'lmodel2-tidiers.R'\n'lmtest-tidiers.R' 'maps-tidiers.R' 'mass-fitdistr-tidiers.R'\n'mass-polr-tidiers.R' 'mass-ridgelm-tidiers.R'\n'stats-lm-tidiers.R' 'mass-rlm-tidiers.R' 'matrix-tidiers.R'\n'mclogit.R' 'mclust-tidiers.R' 'mediate-tidiers.R'\n'mfx-tidiers.R' 'mgcv-tidiers.R' 'muhaz-tidiers.R'\n'multcomp-tidiers.R' 'nnet-tidiers.R' 'nobs.R'\n'orcutt-tidiers.R' 'ordinal-clm-tidiers.R'\n'ordinal-clmm-tidiers.R' 'pam-tidiers.R' 'plm-tidiers.R'\n'polca-tidiers.R' 'psych-tidiers.R' 'stats-nls-tidiers.R'\n'quantreg-nlrq-tidiers.R' 'quantreg-rq-tidiers.R'\n'quantreg-rqs-tidiers.R' 'rma-tidiers.R'\n'robust-glmrob-tidiers.R' 'robust-lmrob-tidiers.R'\n'robustbase-glmrob-tidiers.R' 'robustbase-lmrob-tidiers.R'\n'scam-tidiers.R' 'sp-tidiers.R' 'spdep-tidiers.R'\n'speedglm-speedglm-tidiers.R' 'speedglm-speedlm-tidiers.R'\n'stats-anova-tidiers.R' 'stats-arima-tidiers.R'\n'stats-decompose-tidiers.R' 'stats-factanal-tidiers.R'\n'stats-glm-tidiers.R' 'stats-htest-tidiers.R'\n'stats-kmeans-tidiers.R' 'stats-loess-tidiers.R'\n'stats-mlm-tidiers.R' 'stats-prcomp-tidiers.R'\n'stats-smooth.spline-tidiers.R' 'stats-time-series-tidiers.R'\n'survey-tidiers.R' 'survival-aareg-tidiers.R'\n'survival-cch-tidiers.R' 'survival-coxph-tidiers.R'\n'survival-pyears-tidiers.R' 'survival-survdiff-tidiers.R'\n'survival-survexp-tidiers.R' 'survival-survfit-tidiers.R'\n'survival-survreg-tidiers.R' 'systemfit-tidiers.R'\n'tseries-tidiers.R' 'utilities.R' 'zoo-tidiers.R' 'zzz.R'", "NeedsCompilation": "no", "Packaged": "2020-06-25 19:51:26 UTC; simonpcouch", "Author": "David Robinson [aut],\n Alex Hayes [aut, cre] (),\n Simon Couch [aut],\n Indrajeet Patil [ctb] (),\n Derek Chiu [ctb],\n Matthieu Gomez [ctb],\n Boris Demeshev [ctb],\n Dieter Menne [ctb],\n Benjamin Nutter [ctb],\n Luke Johnston [ctb],\n Ben Bolker [ctb],\n Francois Briatte [ctb],\n Jeffrey Arnold [ctb],\n Jonah Gabry [ctb],\n Luciano Selzer [ctb],\n Gavin Simpson [ctb],\n Jens Preussner [ctb],\n Jay Hesselberth [ctb],\n Hadley Wickham [ctb],\n Matthew Lincoln [ctb],\n Alessandro Gasparini [ctb],\n Lukasz Komsta [ctb],\n Frederick Novometsky [ctb],\n Wilson Freitas [ctb],\n Michelle Evans [ctb],\n Jason Cory Brunson [ctb],\n Simon Jackson [ctb],\n Ben Whalley [ctb],\n Karissa Whiting [ctb],\n Yves Rosseel [ctb],\n Michael Kuehn [ctb],\n Jorge Cimentada [ctb],\n Erle Holgersen [ctb],\n Karl Dunkle Werner [ctb] (),\n Ethan Christensen [ctb],\n Steven Pav [ctb],\n Paul PJ [ctb],\n Ben Schneider [ctb],\n Patrick Kennedy [ctb],\n Lily Medina [ctb],\n Brian Fannin [ctb],\n Jason Muhlenkamp [ctb],\n Matt Lehman [ctb],\n Bill Denney [ctb] (),\n Nic Crane [ctb],\n Andrew Bates [ctb],\n Vincent Arel-Bundock [ctb] (),\n Hideaki Hayashi [ctb],\n Luis Tobalina [ctb],\n Annie Wang [ctb],\n Wei Yang Tham [ctb],\n Clara Wang [ctb],\n Abby Smith [ctb] (),\n Jasper Cooper [ctb] (),\n E Auden Krauska [ctb] (),\n Alex Wang [ctb],\n Malcolm Barrett [ctb] (),\n Charles Gray [ctb] (),\n Jared Wilber [ctb],\n Vilmantas Gegzna [ctb] (),\n Eduard Szoecs [ctb],\n Frederik Aust [ctb] (),\n Angus Moore [ctb],\n Nick Williams [ctb],\n Marius Barth [ctb] (),\n Bruna Wundervald [ctb] (),\n Joyce Cahoon [ctb] (),\n Grant McDermott [ctb] (),\n Kevin Zarca [ctb],\n Shiro Kuriwaki [ctb] (),\n Lukas Wallrich [ctb] (),\n James Martherus [ctb] (),\n Chuliang Xiao [ctb] (),\n Joseph Larmarange [ctb],\n Max Kuhn [ctb],\n Michal Bojanowski [ctb],\n Hakon Malmedal [ctb],\n Clara Wang [ctb],\n Sergio Oller [ctb],\n Luke Sonnet [ctb],\n Jim Hester [ctb],\n Cory Brunson [ctb],\n Ben Schneider [ctb],\n Bernie Gray [ctb] (),\n Mara Averick [ctb],\n Aaron Jacobs [ctb],\n Andreas Bender [ctb],\n Sven Templer [ctb],\n Paul-Christian Buerkner [ctb],\n Matthew Kay [ctb],\n Erwan Le Pennec [ctb],\n Johan Junkka [ctb],\n Hao Zhu [ctb],\n Benjamin Soltoff [ctb],\n Zoe Wilkinson Saldana [ctb],\n Tyler Littlefield [ctb],\n Charles T. Gray [ctb],\n Shabbh E. Banks [ctb],\n Serina Robinson [ctb],\n Roger Bivand [ctb],\n Riinu Ots [ctb],\n Nicholas Williams [ctb],\n Nina Jakobsen [ctb],\n Michael Weylandt [ctb],\n Lisa Lendway [ctb],\n Karl Hailperin [ctb],\n Josue Rodriguez [ctb],\n Jenny Bryan [ctb],\n Chris Jarvis [ctb],\n Greg Macfarlane [ctb],\n Brian Mannakee [ctb],\n Drew Tyre [ctb],\n Shreyas Singh [ctb],\n Laurens Geffert [ctb],\n Hong Ooi [ctb],\n Henrik Bengtsson [ctb],\n Eduard Szocs [ctb],\n David Hugh-Jones [ctb],\n Matthieu Stigler [ctb]", "Maintainer": "Alex Hayes ", "Repository": "CRAN", "Date/Publication": "2020-07-09 12:30:09 UTC", "Built": "R 4.0.2; ; \"Thu, 09 Jul 2020 17:40:08 +0000\"; unix" } }, "callr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "callr", "Title": "Call R from R", "Version": "3.4.4", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\", role = c(\"aut\", \"cre\", \"cph\"),\n email = \"csardi.gabor@gmail.com\",\n\t comment = c(ORCID = \"0000-0001-7098-9676\")),\n person(\"Winston\", \"Chang\", role = \"aut\"),\n person(\"RStudio\", role = c(\"cph\", \"fnd\")),\n person(\"Mango Solutions\", role = c(\"cph\", \"fnd\")))", "Description": "It is sometimes useful to perform a computation in a\n separate R process, without affecting the current R process at all.\n This packages does exactly that.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/callr#readme", "BugReports": "https://github.com/r-lib/callr/issues", "RoxygenNote": "7.1.1", "Imports": "processx (>= 3.4.0), R6, utils", "Suggests": "cliapp, covr, crayon, fansi, pingr, ps, rprojroot, spelling,\ntestthat, tibble, withr", "Encoding": "UTF-8", "Language": "en-US", "NeedsCompilation": "no", "Packaged": "2020-09-07 20:20:04 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre, cph] (),\n Winston Chang [aut],\n RStudio [cph, fnd],\n Mango Solutions [cph, fnd]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2020-09-07 21:20:03 UTC", "Built": "R 4.0.2; ; 2020-09-09 16:25:08 UTC; unix" } }, "cellranger": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "cellranger", "Title": "Translate Spreadsheet Cell Ranges to Rows and Columns", "Version": "1.1.0", "Authors@R": "c(\n person(\"Jennifer\", \"Bryan\", , \"jenny@stat.ubc.ca\", c(\"cre\", \"aut\")),\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", \"ctb\")\n )", "Description": "Helper functions to work with spreadsheets and the \"A1:D10\" style\n of cell range specification.", "Depends": "R (>= 3.0.0)", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/rsheets/cellranger", "BugReports": "https://github.com/rsheets/cellranger/issues", "Suggests": "covr, testthat (>= 1.0.0), knitr, rmarkdown", "RoxygenNote": "5.0.1.9000", "VignetteBuilder": "knitr", "Imports": "rematch, tibble", "NeedsCompilation": "no", "Packaged": "2016-07-26 06:50:00 UTC; jenny", "Author": "Jennifer Bryan [cre, aut],\n Hadley Wickham [ctb]", "Maintainer": "Jennifer Bryan ", "Repository": "CRAN", "Date/Publication": "2016-07-27 03:17:48", "Built": "R 4.0.0; ; 'Sat, 18 Apr 2020 13:41:21 -0400'; unix" } }, "class": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "class", "Priority": "recommended", "Version": "7.3-17", "Date": "2020-04-26", "Depends": "R (>= 3.0.0), stats, utils", "Imports": "MASS", "Authors@R": "c(person(\"Brian\", \"Ripley\", role = c(\"aut\", \"cre\", \"cph\"),\n email = \"ripley@stats.ox.ac.uk\"),\n person(\"William\", \"Venables\", role = \"cph\"))", "Description": "Various functions for classification, including k-nearest\n neighbour, Learning Vector Quantization and Self-Organizing Maps.", "Title": "Functions for Classification", "ByteCompile": "yes", "License": "GPL-2 | GPL-3", "URL": "http://www.stats.ox.ac.uk/pub/MASS4/", "NeedsCompilation": "yes", "Packaged": "2020-04-26 09:33:24 UTC; ripley", "Author": "Brian Ripley [aut, cre, cph],\n William Venables [cph]", "Maintainer": "Brian Ripley ", "Repository": "CRAN", "Date/Publication": "2020-04-26 10:31:55 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; 'Tue, 28 Apr 2020 17:37:37 +0000'; unix" } }, "classInt": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "classInt", "Version": "0.4-3", "Date": "2020-04-05", "Title": "Choose Univariate Class Intervals", "Authors@R": "c(\n person(\"Roger\", \"Bivand\", role=c(\"aut\", \"cre\"), email=\"Roger.Bivand@nhh.no\", comment=c(ORCID=\"0000-0003-2392-6140\")),\n person(\"Hisaji\", \"Ono\", role=\"ctb\"),\n person(\"Richard\", \"Dunlap\", role=\"ctb\"),\n person(\"Matthieu\", \"Stigler\", role=\"ctb\"),\n person(\"Bill\", \"Denney\", role=\"ctb\", email=\"wdenney@humanpredictions.com\", comment=c(ORCID=\"0000-0002-5759-428X\")),\n person(\"Diego\", \"Hernangómez\", role=\"ctb\", email=\"diego.hernangomezherrero@gmail.com\", comment=c(ORCID=\"0000-0001-8457-4658\")))", "Depends": "R (>= 2.2)", "Imports": "grDevices, stats, graphics, e1071, class, KernSmooth", "Suggests": "spData (>= 0.2.6.2), units, knitr, rmarkdown", "NeedsCompilation": "yes", "Description": "Selected commonly used methods for choosing univariate class intervals for mapping or other graphics purposes.", "License": "GPL (>= 2)", "URL": "https://r-spatial.github.io/classInt/,\nhttps://github.com/r-spatial/classInt/", "BugReports": "https://github.com/r-spatial/classInt/issues/", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "VignetteBuilder": "knitr", "Packaged": "2020-04-05 16:34:38 UTC; rsb", "Author": "Roger Bivand [aut, cre] (),\n Hisaji Ono [ctb],\n Richard Dunlap [ctb],\n Matthieu Stigler [ctb],\n Bill Denney [ctb] (),\n Diego Hernangómez [ctb] ()", "Maintainer": "Roger Bivand ", "Repository": "CRAN", "Date/Publication": "2020-04-07 11:10:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:36:09 UTC; unix" } }, "cli": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "cli", "Title": "Helpers for Developing Command Line Interfaces", "Version": "2.0.2", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", role = c(\"ctb\")),\n person(\"Kirill\", \"Müller\", role = c(\"ctb\"))\n )", "Description": "A suite of tools to build attractive command line interfaces\n ('CLIs'), from semantic elements: headings, lists, alerts, paragraphs,\n etc. Supports custom themes via a 'CSS'-like language. It also contains a\n number of lower level 'CLI' elements: rules, boxes, trees, and\n 'Unicode' symbols with 'ASCII' alternatives. It integrates with the\n 'crayon' package to support 'ANSI' terminal colors.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/cli#readme", "BugReports": "https://github.com/r-lib/cli/issues", "RoxygenNote": "7.0.2", "Depends": "R (>= 2.10)", "Imports": "assertthat, crayon (>= 1.3.4), glue, methods, utils, fansi", "Suggests": "callr, covr, htmlwidgets, knitr, mockery, rmarkdown,\nrstudioapi, prettycode (>= 1.1.0), testthat, withr", "Encoding": "UTF-8", "VignetteBuilder": "cli", "NeedsCompilation": "no", "Packaged": "2020-02-27 13:43:04 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre],\n Hadley Wickham [ctb],\n Kirill Müller [ctb]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2020-02-28 12:10:13 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 23:17:56 +0000\"; unix" } }, "clipr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Type": "Package", "Package": "clipr", "Title": "Read and Write from the System Clipboard", "Version": "0.7.0", "Authors@R": "\n c(person(given = \"Matthew\",\n family = \"Lincoln\",\n role = c(\"aut\", \"cre\"),\n email = \"matthew.d.lincoln@gmail.com\",\n comment = c(ORCID = \"0000-0002-4387-3384\")),\n person(given = \"Louis\",\n family = \"Maddox\",\n role = \"ctb\"),\n person(given = \"Steve\",\n family = \"Simpson\",\n role = \"ctb\"),\n person(given = \"Jennifer\",\n family = \"Bryan\",\n role = \"ctb\"))", "Description": "Simple utility functions to read from and write to\n the Windows, OS X, and X11 clipboards.", "License": "GPL-3", "URL": "https://github.com/mdlincoln/clipr", "BugReports": "https://github.com/mdlincoln/clipr/issues", "Imports": "utils", "Suggests": "covr, knitr, rmarkdown, rstudioapi (>= 0.5), testthat (>=\n2.0.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "Language": "en-US", "LazyData": "TRUE", "RoxygenNote": "6.1.1", "SystemRequirements": "xclip (https://github.com/astrand/xclip) or xsel\n(http://www.vergenet.net/~conrad/software/xsel/) for accessing\nthe X11 clipboard", "NeedsCompilation": "no", "Packaged": "2019-07-23 02:33:48 UTC; admin", "Author": "Matthew Lincoln [aut, cre] (),\n Louis Maddox [ctb],\n Steve Simpson [ctb],\n Jennifer Bryan [ctb]", "Maintainer": "Matthew Lincoln ", "Repository": "CRAN", "Date/Publication": "2019-07-23 05:00:03 UTC", "Built": "R 4.0.0; ; \"Sat, 18 Apr 2020 00:17:35 +0000\"; unix" } }, "colorspace": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "colorspace", "Version": "1.4-1", "Date": "2019-03-18", "Title": "A Toolbox for Manipulating and Assessing Colors and Palettes", "Authors@R": "c(person(given = \"Ross\", family = \"Ihaka\", role = \"aut\", email = \"ihaka@stat.auckland.ac.nz\"),\n person(given = \"Paul\", family = \"Murrell\", role = \"aut\", email = \"paul@stat.auckland.ac.nz\",\n comment = c(ORCID = \"0000-0002-3224-8858\")),\n person(given = \"Kurt\", family = \"Hornik\", role = \"aut\", email = \"Kurt.Hornik@R-project.org\",\n\t\t comment = c(ORCID = \"0000-0003-4198-9911\")),\n person(given = c(\"Jason\", \"C.\"), family = \"Fisher\", role = \"aut\", email = \"jfisher@usgs.gov\",\n comment = c(ORCID = \"0000-0001-9032-8912\")),\n person(given = \"Reto\", family = \"Stauffer\", role = \"aut\", email = \"Reto.Stauffer@uibk.ac.at\",\n comment = c(ORCID = \"0000-0002-3798-5507\")),\n person(given = c(\"Claus\", \"O.\"), family = \"Wilke\", role = \"aut\", email = \"wilke@austin.utexas.edu\",\n comment = c(ORCID = \"0000-0002-7470-9261\")),\n person(given = c(\"Claire\", \"D.\"), family = \"McWhite\", role = \"aut\", email = \"claire.mcwhite@utmail.utexas.edu\",\n comment = c(ORCID = \"0000-0001-7346-3047\")),\n person(given = \"Achim\", family = \"Zeileis\", role = c(\"aut\", \"cre\"), email = \"Achim.Zeileis@R-project.org\",\n comment = c(ORCID = \"0000-0003-0918-3766\")))", "Description": "Carries out mapping between assorted color spaces including RGB, HSV, HLS,\n CIEXYZ, CIELUV, HCL (polar CIELUV), CIELAB and polar CIELAB.\n\t Qualitative, sequential, and diverging color palettes based on HCL colors\n\t are provided along with corresponding ggplot2 color scales.\n\t Color palette choice is aided by an interactive app (with either a Tcl/Tk\n\t or a shiny GUI) and shiny apps with an HCL color picker and a\n\t color vision deficiency emulator. Plotting functions for displaying\n\t and assessing palettes include color swatches, visualizations of the\n\t HCL space, and trajectories in HCL and/or RGB spectrum. Color manipulation\n\t functions include: desaturation, lightening/darkening, mixing, and\n\t simulation of color vision deficiencies (deutanomaly, protanomaly, tritanomaly).", "Depends": "R (>= 3.0.0), methods", "Imports": "graphics, grDevices, stats", "Suggests": "datasets, utils, KernSmooth, MASS, kernlab, mvtnorm, vcd,\ntcltk, shiny, shinyjs, ggplot2, dplyr, scales, grid, png, jpeg,\nknitr, rmarkdown, RColorBrewer, rcartocolor, scico, viridis,\nwesanderson", "VignetteBuilder": "knitr", "License": "BSD_3_clause + file LICENSE", "URL": "http://colorspace.R-Forge.R-project.org, http://hclwizard.org/", "BugReports": "http://colorspace.R-Forge.R-project.org/contact.html", "LazyData": "yes", "RoxygenNote": "6.1.1", "NeedsCompilation": "yes", "Packaged": "2019-03-18 09:11:41 UTC; zeileis", "Author": "Ross Ihaka [aut],\n Paul Murrell [aut] (),\n Kurt Hornik [aut] (),\n Jason C. Fisher [aut] (),\n Reto Stauffer [aut] (),\n Claus O. Wilke [aut] (),\n Claire D. McWhite [aut] (),\n Achim Zeileis [aut, cre] ()", "Maintainer": "Achim Zeileis ", "Repository": "CRAN", "Date/Publication": "2019-03-18 14:43:29 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 20:50:03 +0000\"; unix" } }, "commonmark": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "commonmark", "Type": "Package", "Title": "High Performance CommonMark and Github Markdown Rendering in R", "Version": "1.7", "Authors@R": "c(\n person(\"Jeroen\", \"Ooms\", ,\"jeroen@berkeley.edu\", role = c(\"aut\", \"cre\")),\n person(\"John MacFarlane\", role = \"cph\", comment = \"Author of cmark\"))", "URL": "http://github.com/jeroen/commonmark (devel)\nhttps://github.github.com/gfm/ (spec)", "BugReports": "http://github.com/jeroen/commonmark/issues", "Description": "The CommonMark specification defines a rationalized version of markdown\n syntax. This package uses the 'cmark' reference implementation for converting\n markdown text into various formats including html, latex and groff man. In\n addition it exposes the markdown parse tree in xml format. Also includes opt-in\n support for GFM extensions including tables, autolinks, and strikethrough text.", "License": "BSD_2_clause + file LICENSE", "Suggests": "curl, testthat, xml2", "RoxygenNote": "6.0.1", "NeedsCompilation": "yes", "Packaged": "2018-12-01 11:57:14 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre],\n John MacFarlane [cph] (Author of cmark)", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2018-12-01 12:30:03 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sat, 18 Apr 2020 00:19:16 +0000\"; unix" } }, "cpp11": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "cpp11", "Title": "A C++11 Interface for R's C Interface", "Version": "0.2.1", "Authors@R": "\n c(person(given = \"Jim\",\n family = \"Hester\",\n role = c(\"aut\", \"cre\"),\n email = \"jim.hester@rstudio.com\",\n comment = c(ORCID = \"0000-0002-2739-7082\")),\n person(given = \"Romain\",\n family = \"François\",\n role = \"ctb\"),\n person(given = \"Benjamin\",\n family = \"Kietzman\",\n role = \"ctb\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "Provides a header only, C++11 interface to R's C\n interface. Compared to other approaches 'cpp11' strives to be safe\n against long jumps from the C API as well as C++ exceptions, conform\n to normal R function semantics and supports interaction with 'ALTREP'\n vectors.", "License": "MIT + file LICENSE", "URL": "https://github.com/r-lib/cpp11", "BugReports": "https://github.com/r-lib/cpp11/issues", "Suggests": "bench, brio, callr, cli, covr, decor, desc, ggplot2, glue,\nknitr, lobstr, mockery, progress, rmarkdown, scales, testthat,\ntibble, utils, vctrs, withr", "VignetteBuilder": "knitr", "Config/testthat/edition": "3", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "SystemRequirements": "C++11", "NeedsCompilation": "no", "Packaged": "2020-08-11 13:59:26 UTC; jhester", "Author": "Jim Hester [aut, cre] (),\n Romain François [ctb],\n Benjamin Kietzman [ctb],\n RStudio [cph, fnd]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-08-11 14:30:17 UTC", "Built": "R 4.0.2; ; \"Wed, 12 Aug 2020 13:04:52 +0000\"; unix" } }, "crayon": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "crayon", "Title": "Colored Terminal Output", "Version": "1.3.4", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\",\n role = c(\"aut\", \"cre\")),\n person(\n \"Brodie\", \"Gaslam\", email=\"brodie.gaslam@yahoo.com\",\n role=c(\"ctb\"))\n )", "Description": "Colored terminal output on terminals that support 'ANSI'\n color and highlight codes. It also works in 'Emacs' 'ESS'. 'ANSI'\n color support is automatically detected. Colors and highlighting can\n be combined and nested. New styles can also be created easily.\n This package was inspired by the 'chalk' 'JavaScript' project.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/crayon#readme", "BugReports": "https://github.com/r-lib/crayon/issues", "Collate": "'ansi-256.r' 'combine.r' 'string.r' 'utils.r'\n'crayon-package.r' 'disposable.r' 'has_ansi.r' 'has_color.r'\n'styles.r' 'machinery.r' 'parts.r' 'print.r' 'style-var.r'\n'show.r' 'string_operations.r'", "Imports": "grDevices, methods, utils", "Suggests": "mockery, rstudioapi, testthat, withr", "RoxygenNote": "6.0.1.9000", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2017-09-15 18:14:04 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre],\n Brodie Gaslam [ctb]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2017-09-16 19:49:46 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:33:13 +0000\"; unix" } }, "crosstalk": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "crosstalk", "Type": "Package", "Title": "Inter-Widget Interactivity for HTML Widgets", "Version": "1.1.0.1", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = c(\"aut\", \"cre\"), email = \"joe@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\"),\n person(family = \"jQuery Foundation\", role = \"cph\",\n comment = \"jQuery library and jQuery UI library\"),\n person(family = \"jQuery contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt\"),\n person(\"Mark\", \"Otto\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(\"Jacob\", \"Thornton\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Bootstrap contributors\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Twitter, Inc\", role = \"cph\",\n comment = \"Bootstrap library\"),\n person(\"Brian\", \"Reavis\", role = c(\"ctb\", \"cph\"),\n comment = \"selectize.js library\"),\n person(\"Kristopher Michael\", \"Kowal\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(family = \"es5-shim contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(\"Denis\", \"Ineshin\", role = c(\"ctb\", \"cph\"),\n comment = \"ion.rangeSlider library\"),\n person(\"Sami\", \"Samhuri\", role = c(\"ctb\", \"cph\"),\n comment = \"Javascript strftime library\")\n )", "Description": "Provides building blocks for allowing HTML widgets to communicate\n with each other, with Shiny or without (i.e. static .html files). Currently\n supports linked brushing and filtering.", "License": "MIT + file LICENSE", "Imports": "htmltools (>= 0.3.6), jsonlite, lazyeval, R6", "Suggests": "shiny, ggplot2, testthat (>= 2.1.0)", "URL": "https://rstudio.github.io/crosstalk/", "BugReports": "https://github.com/rstudio/crosstalk/issues", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-03-12 23:34:05 UTC; jcheng", "Author": "Joe Cheng [aut, cre],\n RStudio [cph],\n jQuery Foundation [cph] (jQuery library and jQuery UI library),\n jQuery contributors [ctb, cph] (jQuery library; authors listed in\n inst/www/shared/jquery-AUTHORS.txt),\n Mark Otto [ctb] (Bootstrap library),\n Jacob Thornton [ctb] (Bootstrap library),\n Bootstrap contributors [ctb] (Bootstrap library),\n Twitter, Inc [cph] (Bootstrap library),\n Brian Reavis [ctb, cph] (selectize.js library),\n Kristopher Michael Kowal [ctb, cph] (es5-shim library),\n es5-shim contributors [ctb, cph] (es5-shim library),\n Denis Ineshin [ctb, cph] (ion.rangeSlider library),\n Sami Samhuri [ctb, cph] (Javascript strftime library)", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2020-03-13 10:20:06 UTC", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 01:34:35 +0000\"; unix" } }, "curl": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "curl", "Type": "Package", "Title": "A Modern and Flexible Web Client for R", "Version": "4.3", "Authors@R": "c(\n person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), email = \"jeroen@berkeley.edu\",\n comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", role = \"ctb\"),\n person(\"RStudio\", role = \"cph\")\n )", "Description": "The curl() and curl_download() functions provide highly\n configurable drop-in replacements for base url() and download.file() with\n better performance, support for encryption (https, ftps), gzip compression,\n authentication, and other 'libcurl' goodies. The core of the package implements a\n framework for performing fully customized requests where data can be processed\n either in memory, on disk, or streaming via the callback or connection\n interfaces. Some knowledge of 'libcurl' is recommended; for a more-user-friendly\n web client see the 'httr' package which builds on this package with http\n specific tools and logic.", "License": "MIT + file LICENSE", "SystemRequirements": "libcurl: libcurl-devel (rpm) or\nlibcurl4-openssl-dev (deb).", "URL": "https://jeroen.cran.dev/curl (docs)\nhttps://github.com/jeroen/curl#readme (devel)\nhttps://curl.haxx.se/libcurl/ (upstream)", "BugReports": "https://github.com/jeroen/curl/issues", "Suggests": "spelling, testthat (>= 1.0.0), knitr, jsonlite, rmarkdown,\nmagrittr, httpuv (>= 1.4.4), webutils", "VignetteBuilder": "knitr", "Depends": "R (>= 3.0.0)", "LazyData": "true", "RoxygenNote": "7.0.1", "Encoding": "UTF-8", "Language": "en-US", "NeedsCompilation": "yes", "Packaged": "2019-12-02 12:33:03 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] (),\n Hadley Wickham [ctb],\n RStudio [cph]", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2019-12-02 14:00:03 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 16:51:05 -0400\"; unix" } }, "dbplyr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Type": "Package", "Package": "dbplyr", "Title": "A 'dplyr' Back End for Databases", "Version": "1.4.4", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"Edgar\",\n family = \"Ruiz\",\n role = \"aut\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "A 'dplyr' back end for databases that allows you to\n work with remote database tables as if they are in-memory data frames.\n Basic features works with any database that has a 'DBI' back end; more\n advanced features require 'SQL' translation to be provided by the\n package author.", "License": "MIT + file LICENSE", "URL": "https://dbplyr.tidyverse.org/, https://github.com/tidyverse/dbplyr", "BugReports": "https://github.com/tidyverse/dbplyr/issues", "Depends": "R (>= 3.1)", "Imports": "assertthat (>= 0.2.0), DBI (>= 1.0.0), dplyr (>= 0.8.0), glue\n(>= 1.2.0), lifecycle, magrittr, methods, purrr (>= 0.2.5), R6\n(>= 2.2.2), rlang (>= 0.2.0), tibble (>= 1.4.2), tidyselect (>=\n0.2.4), blob (>= 1.2.0), utils", "Suggests": "bit64, covr, knitr, Lahman, nycflights13, odbc, RMariaDB (>=\n1.0.2), rmarkdown, RPostgres (>= 1.1.3), RSQLite (>= 2.1.0),\ntestthat (>= 2.0.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "Language": "en-gb", "LazyData": "yes", "RoxygenNote": "7.1.0", "Collate": "'utils.R' 'sql.R' 'escape.R' 'translate-sql-quantile.R'\n'translate-sql-string.R' 'translate-sql-paste.R'\n'translate-sql-helpers.R' 'translate-sql-window.R'\n'translate-sql-conditional.R' 'backend-.R' 'backend-access.R'\n'backend-athena.R' 'backend-hive.R' 'backend-impala.R'\n'backend-mssql.R' 'backend-mysql.R' 'backend-odbc.R'\n'backend-oracle.R' 'backend-postgres.R' 'backend-presto.R'\n'backend-sqlite.R' 'backend-teradata.R' 'build-sql.R'\n'data-cache.R' 'data-lahman.R' 'data-nycflights13.R' 'dbplyr.R'\n'explain.R' 'ident.R' 'lazy-ops.R' 'memdb.R' 'partial-eval.R'\n'progress.R' 'query-join.R' 'query-select.R'\n'query-semi-join.R' 'query-set-op.R' 'query.R' 'reexport.R'\n'remote.R' 'schema.R' 'simulate.R' 'sql-build.R' 'sql-expr.R'\n'src-sql.R' 'src_dbi.R' 'tbl-lazy.R' 'tbl-sql.R' 'test-frame.R'\n'testthat.R' 'translate-sql-clause.R' 'translate-sql.R'\n'utils-format.R' 'verb-arrange.R' 'verb-compute.R'\n'verb-copy-to.R' 'verb-distinct.R' 'verb-do-query.R'\n'verb-do.R' 'verb-filter.R' 'verb-group_by.R' 'verb-head.R'\n'verb-joins.R' 'verb-mutate.R' 'verb-pull.R' 'verb-select.R'\n'verb-set-ops.R' 'verb-summarise.R' 'verb-window.R' 'zzz.R'", "RdMacros": "lifecycle", "NeedsCompilation": "no", "Packaged": "2020-05-26 22:24:37 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n Edgar Ruiz [aut],\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-05-27 05:30:05 UTC", "Built": "R 4.0.0; ; \"Wed, 27 May 2020 20:19:07 +0000\"; unix" } }, "desc": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "desc", "Title": "Manipulate DESCRIPTION Files", "Version": "1.2.0", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\",, \"csardi.gabor@gmail.com\", role = c(\"aut\", \"cre\")),\n person(\"Kirill\", \"Müller\", role = c(\"aut\")),\n person(\"Jim\", \"Hester\", email = \"james.f.hester@gmail.com\", role = c(\"aut\")))", "Maintainer": "Gábor Csárdi ", "Description": "Tools to read, write, create, and manipulate DESCRIPTION files.\n It is intended for packages that create or manipulate other packages.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/desc#readme", "BugReports": "https://github.com/r-lib/desc/issues", "Depends": "R (>= 3.1.0)", "Suggests": "covr, testthat, whoami, withr", "Imports": "assertthat, utils, R6, crayon, rprojroot", "Encoding": "UTF-8", "RoxygenNote": "6.0.1.9000", "Collate": "'assertions.R' 'authors-at-r.R' 'built.R' 'classes.R'\n'collate.R' 'constants.R' 'deps.R' 'description.R' 'encoding.R'\n'latex.R' 'non-oo-api.R' 'package-archives.R' 'read.R'\n'remotes.R' 'str.R' 'syntax_checks.R' 'urls.R' 'utils.R'\n'validate.R' 'version.R'", "NeedsCompilation": "no", "Packaged": "2018-05-01 20:22:08 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre],\n Kirill Müller [aut],\n Jim Hester [aut]", "Repository": "CRAN", "Date/Publication": "2018-05-01 20:48:05 UTC", "Built": "R 4.0.2; ; 2020-08-25 21:12:34 UTC; unix" } }, "digest": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "digest", "Author": "Dirk Eddelbuettel with contributions \n by Antoine Lucas, Jarek Tuszynski, Henrik Bengtsson, Simon Urbanek,\n Mario Frasca, Bryan Lewis, Murray Stokely, Hannes Muehleisen,\n Duncan Murdoch, Jim Hester, Wush Wu, Qiang Kou, Thierry Onkelinx, \n Michel Lang, Viliam Simko, Kurt Hornik, Radford Neal, Kendon Bell,\n Matthew de Queljoe, Ion Suruceanu, and Bill Denney.", "Version": "0.6.25", "Date": "2020-02-22", "Maintainer": "Dirk Eddelbuettel ", "Title": "Create Compact Hash Digests of R Objects", "Description": "Implementation of a function 'digest()' for the creation \n of hash digests of arbitrary R objects (using the 'md5', 'sha-1', 'sha-256', \n 'crc32', 'xxhash', 'murmurhash' and 'spookyhash' algorithms) permitting easy\n comparison of R language objects, as well as functions such as'hmac()' to\n create hash-based message authentication code. Please note that this package\n is not meant to be deployed for cryptographic purposes for which more\n comprehensive (and widely tested) libraries such as 'OpenSSL' should be\n used.", "URL": "http://dirk.eddelbuettel.com/code/digest.html", "BugReports": "https://github.com/eddelbuettel/digest/issues", "Depends": "R (>= 3.1.0)", "Imports": "utils", "License": "GPL (>= 2)", "Suggests": "tinytest, knitr, rmarkdown", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-02-22 14:55:28.282493 UTC; edd", "Repository": "CRAN", "Date/Publication": "2020-02-23 00:10:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 20:54:13 +0000\"; unix" } }, "dplyr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Type": "Package", "Package": "dplyr", "Title": "A Grammar of Data Manipulation", "Version": "1.0.2", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\",\n comment = c(ORCID = \"0000-0003-4757-117X\")),\n person(given = \"Romain\",\n family = \"François\",\n role = \"aut\",\n comment = c(ORCID = \"0000-0002-2444-4226\")),\n person(given = \"Lionel\",\n family = \"\n Henry\",\n role = \"aut\"),\n person(given = \"Kirill\",\n family = \"Müller\",\n role = \"aut\",\n comment = c(ORCID = \"0000-0002-1416-3412\")),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "A fast, consistent tool for working with data frame\n like objects, both in memory and out of memory.", "License": "MIT + file LICENSE", "URL": "https://dplyr.tidyverse.org, https://github.com/tidyverse/dplyr", "BugReports": "https://github.com/tidyverse/dplyr/issues", "Depends": "R (>= 3.2.0)", "Imports": "ellipsis, generics, glue (>= 1.3.2), lifecycle (>= 0.2.0),\nmagrittr (>= 1.5), methods, R6, rlang (>= 0.4.7), tibble (>=\n2.1.3), tidyselect (>= 1.1.0), utils, vctrs (>= 0.3.2)", "Suggests": "bench, broom, callr, covr, DBI, dbplyr (>= 1.4.3), knitr,\nLahman, lobstr, microbenchmark, nycflights13, purrr, rmarkdown,\nRMySQL, RPostgreSQL, RSQLite, testthat (>= 2.1.0), withr", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "yes", "RoxygenNote": "7.1.1", "NeedsCompilation": "yes", "Packaged": "2020-08-12 15:16:36 UTC; romainfrancois", "Author": "Hadley Wickham [aut, cre] (),\n Romain François [aut] (),\n Lionel Henry [aut],\n Kirill Müller [aut] (),\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-08-18 12:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Wed, 19 Aug 2020 12:24:23 +0000\"; unix" } }, "e1071": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "e1071", "Version": "1.7-3", "Title": "Misc Functions of the Department of Statistics, Probability\nTheory Group (Formerly: E1071), TU Wien", "Imports": "graphics, grDevices, class, stats, methods, utils", "Suggests": "cluster, mlbench, nnet, randomForest, rpart, SparseM, xtable,\nMatrix, MASS, slam", "Authors@R": "c(person(given = \"David\", family = \"Meyer\", role = c(\"aut\", \"cre\"), email = \"David.Meyer@R-project.org\"),\n person(given = \"Evgenia\", family = \"Dimitriadou\", role = c(\"aut\",\"cph\")),\n person(given = \"Kurt\", family = \"Hornik\", role = \"aut\"),\n person(given = \"Andreas\", family = \"Weingessel\", role = \"aut\"),\n person(given = \"Friedrich\", family = \"Leisch\", role = \"aut\"),\n person(given = \"Chih-Chung\", family = \"Chang\", role = c(\"ctb\",\"cph\"), comment = \"libsvm C++-code\"),\n person(given = \"Chih-Chen\", family = \"Lin\", role = c(\"ctb\",\"cph\"), comment = \"libsvm C++-code\"))", "Description": "Functions for latent class analysis, short time Fourier\n\t transform, fuzzy clustering, support vector machines,\n\t shortest path computation, bagged clustering, naive Bayes\n\t classifier, ...", "License": "GPL-2 | GPL-3", "LazyLoad": "yes", "NeedsCompilation": "yes", "Packaged": "2019-11-25 17:01:14 UTC; meyer", "Author": "David Meyer [aut, cre],\n Evgenia Dimitriadou [aut, cph],\n Kurt Hornik [aut],\n Andreas Weingessel [aut],\n Friedrich Leisch [aut],\n Chih-Chung Chang [ctb, cph] (libsvm C++-code),\n Chih-Chen Lin [ctb, cph] (libsvm C++-code)", "Maintainer": "David Meyer ", "Repository": "CRAN", "Date/Publication": "2019-11-26 18:29:09 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:36:03 UTC; unix" } }, "ellipsis": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "ellipsis", "Version": "0.3.1", "Title": "Tools for Working with ...", "Description": "The ellipsis is a powerful tool for extending functions. Unfortunately \n this power comes at a cost: misspelled arguments will be silently ignored. \n The ellipsis package provides a collection of functions to catch problems\n and alert the user.", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"RStudio\", role = \"cph\")\n )", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "URL": "https://ellipsis.r-lib.org, https://github.com/r-lib/ellipsis", "BugReports": "https://github.com/r-lib/ellipsis/issues", "Depends": "R (>= 3.2)", "Imports": "rlang (>= 0.3.0)", "Suggests": "covr, testthat", "NeedsCompilation": "yes", "Packaged": "2020-05-15 05:57:33 UTC; lionel", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-05-15 14:10:06 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sat, 16 May 2020 22:54:59 +0000\"; unix" } }, "evaluate": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "evaluate", "Type": "Package", "Title": "Parsing and Evaluation Tools that Provide More Details than the\nDefault", "Version": "0.14", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", role = \"aut\"),\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Michael\", \"Lawrence\", role = \"ctb\"),\n person(\"Thomas\", \"Kluyver\", role = \"ctb\"),\n person(\"Jeroen\", \"Ooms\", role = \"ctb\"),\n person(\"Barret\", \"Schloerke\", role = \"ctb\"),\n person(\"Adam\", \"Ryczkowski\", role = \"ctb\"),\n person(\"Hiroaki\", \"Yutani\", role = \"ctb\"),\n person(\"Michel\", \"Lang\", role = \"ctb\"),\n person(\"Karolis\", \"Koncevičius\", role = \"ctb\")\n )", "Description": "Parsing and evaluation tools that make it easy to recreate the\n command line behaviour of R.", "License": "MIT + file LICENSE", "URL": "https://github.com/r-lib/evaluate", "BugReports": "https://github.com/r-lib/evaluate/issues", "Depends": "R (>= 3.0.2)", "Imports": "methods", "Suggests": "testthat, lattice, ggplot2", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2019-05-28 15:30:02 UTC; yihui", "Author": "Hadley Wickham [aut],\n Yihui Xie [aut, cre] (),\n Michael Lawrence [ctb],\n Thomas Kluyver [ctb],\n Jeroen Ooms [ctb],\n Barret Schloerke [ctb],\n Adam Ryczkowski [ctb],\n Hiroaki Yutani [ctb],\n Michel Lang [ctb],\n Karolis Koncevičius [ctb]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2019-05-28 15:50:02 UTC", "Built": "R 4.0.0; ; \"Tue, 14 Apr 2020 00:18:01 +0000\"; unix" } }, "fansi": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "fansi", "Title": "ANSI Control Sequence Aware String Functions", "Description": "Counterparts to R string manipulation functions that account for\n the effects of ANSI text formatting control sequences.", "Version": "0.4.1", "Authors@R": "c(\n person(\"Brodie\", \"Gaslam\", email=\"brodie.gaslam@yahoo.com\",\n role=c(\"aut\", \"cre\")),\n person(\"Elliott\", \"Sales De Andrade\", role=\"ctb\"),\n person(family=\"R Core Team\",\n email=\"R-core@r-project.org\", role=\"cph\",\n comment=\"UTF8 byte length calcs from src/util.c\"\n ))", "Depends": "R (>= 3.1.0)", "License": "GPL (>= 2)", "LazyData": "true", "URL": "https://github.com/brodieG/fansi", "BugReports": "https://github.com/brodieG/fansi/issues", "VignetteBuilder": "knitr", "Suggests": "unitizer, knitr, rmarkdown", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "Collate": "'constants.R' 'fansi-package.R' 'has.R' 'internal.R' 'load.R'\n'misc.R' 'nchar.R' 'strip.R' 'strwrap.R' 'strtrim.R'\n'strsplit.R' 'substr2.R' 'tohtml.R' 'unhandled.R'", "NeedsCompilation": "yes", "Packaged": "2020-01-06 01:58:57 UTC; bg", "Author": "Brodie Gaslam [aut, cre],\n Elliott Sales De Andrade [ctb],\n R Core Team [cph] (UTF8 byte length calcs from src/util.c)", "Maintainer": "Brodie Gaslam ", "Repository": "CRAN", "Date/Publication": "2020-01-08 23:01:29 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 00:35:08 +0000\"; unix" } }, "farver": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "farver", "Type": "Package", "Title": "High Performance Colour Space Manipulation", "Version": "2.0.3", "Authors@R": "\n c(person(given = \"Thomas Lin\",\n family = \"Pedersen\",\n role = c(\"cre\", \"aut\"),\n email = \"thomasp85@gmail.com\",\n comment = c(ORCID = \"0000-0002-5147-4711\")),\n person(given = \"Berendea\",\n family = \"Nicolae\",\n role = \"aut\",\n comment = \"Author of the ColorSpace C++ library\"),\n person(given = \"Romain\", \n family = \"François\", \n role = \"aut\", \n email = \"romain@purrple.cat\",\n comment = c(ORCID = \"0000-0002-2444-4226\")) \n )", "Maintainer": "Thomas Lin Pedersen ", "Description": "The encoding of colour can be handled in many different ways, using\n different colour spaces. As different colour spaces have different uses,\n efficient conversion between these representations are important. The \n 'farver' package provides a set of functions that gives access to very fast\n colour space conversion and comparisons implemented in C++, and offers \n speed improvements over the 'convertColor' function in the 'grDevices' \n package.", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "SystemRequirements": "C++11", "RoxygenNote": "7.0.2", "URL": "https://farver.data-imaginist.com,\nhttps://github.com/thomasp85/farver", "BugReports": "https://github.com/thomasp85/farver/issues", "Suggests": "testthat (>= 2.1.0), covr", "NeedsCompilation": "yes", "Packaged": "2020-01-16 10:42:44 UTC; thomas", "Author": "Thomas Lin Pedersen [cre, aut]\n (),\n Berendea Nicolae [aut] (Author of the ColorSpace C++ library),\n Romain François [aut] ()", "Repository": "CRAN", "Date/Publication": "2020-01-16 13:40:07 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 00:34:47 +0000\"; unix" } }, "fastmap": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "fastmap", "Title": "Fast Implementation of a Key-Value Store", "Version": "1.0.1", "Authors@R": "c(\n person(\"Winston\", \"Chang\", email = \"winston@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(given = \"RStudio\", role = c(\"cph\", \"fnd\")),\n person(given = \"Tessil\", role = \"cph\", comment = \"hopscotch_map library\")\n )", "Description": "Fast implementation of a key-value store. Environments are commonly\n used as key-value stores, but every time a new key is used, it is added to\n R's global symbol table, causing a small amount of memory leakage. This can\n be problematic in cases where many different keys are used. Fastmap avoids\n this memory leak issue by implementing the map using data structures in C++.", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "6.1.1", "Suggests": "testthat (>= 2.1.1)", "URL": "https://r-lib.github.io/fastmap/, https://github.com/r-lib/fastmap", "BugReports": "https://github.com/r-lib/fastmap/issues", "NeedsCompilation": "yes", "Packaged": "2019-10-07 23:14:48 UTC; winston", "Author": "Winston Chang [aut, cre],\n RStudio [cph, fnd],\n Tessil [cph] (hopscotch_map library)", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2019-10-08 05:20:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 00:37:53 +0000\"; unix" } }, "forcats": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "forcats", "Title": "Tools for Working with Categorical Variables (Factors)", "Version": "0.5.0", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "Helpers for reordering factor levels (including\n moving specified levels to front, ordering by first appearance,\n reversing, and randomly shuffling), and tools for modifying factor\n levels (including collapsing rare levels into other, 'anonymising',\n and manually 'recoding').", "License": "GPL-3", "URL": "http://forcats.tidyverse.org, https://github.com/tidyverse/forcats", "BugReports": "https://github.com/tidyverse/forcats/issues", "Depends": "R (>= 3.2)", "Imports": "ellipsis, magrittr, rlang, tibble", "Suggests": "covr, ggplot2, testthat, readr, knitr, rmarkdown, dplyr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.0.2", "VignetteBuilder": "knitr", "NeedsCompilation": "no", "Packaged": "2020-03-01 17:20:37 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-03-01 18:10:02 UTC", "Built": "R 4.0.0; ; \"Sat, 18 Apr 2020 16:13:28 +0000\"; unix" } }, "fs": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "fs", "Title": "Cross-Platform File System Operations Based on 'libuv'", "Version": "1.5.0", "Authors@R": "c(\n person(\"Jim\", \"Hester\", email = \"james.f.hester@gmail.com\", role = c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", \"aut\"),\n person(\"libuv project contributors\", role = \"cph\", comment = \"libuv library\"),\n person(\"Joyent, Inc. and other Node contributors\", role = \"cph\", comment = \"libuv library\"),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Description": "A cross-platform interface to file system operations, built on\n top of the 'libuv' C library.", "Depends": "R (>= 3.1)", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "ByteCompile": "true", "Imports": "methods", "SystemRequirements": "GNU make", "RoxygenNote": "7.1.1", "URL": "http://fs.r-lib.org, https://github.com/r-lib/fs", "BugReports": "https://github.com/r-lib/fs/issues", "Copyright": "file COPYRIGHTS", "Suggests": "testthat, covr, pillar (>= 1.0.0), tibble (>= 1.1.0), crayon,\nrmarkdown, knitr, withr, spelling, vctrs (>= 0.3.0)", "VignetteBuilder": "knitr", "Language": "en-US", "NeedsCompilation": "yes", "Packaged": "2020-07-31 21:00:55 UTC; jhester", "Author": "Jim Hester [aut, cre],\n Hadley Wickham [aut],\n libuv project contributors [cph] (libuv library),\n Joyent, Inc. and other Node contributors [cph] (libuv library),\n RStudio [cph, fnd]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-07-31 21:30:03 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sat, 01 Aug 2020 20:23:47 +0000\"; unix" } }, "generics": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "generics", "Version": "0.0.2", "Title": "Common S3 Generics not Provided by Base R Methods Related to\nModel Fitting", "Description": "In order to reduce potential package dependencies and conflicts, \n generics provides a number of commonly used S3 generics.", "Authors@R": "c(\n person(\"Max\", \"Kuhn\", , \"max@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", \"aut\"),\n person(\"Davis\", \"Vaughan\", , \"davis@rstudio.com\", \"aut\"),\n person(\"RStudio\", role = \"cph\"))", "License": "GPL-2", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "6.1.1", "URL": "https://github.com/r-lib/generics", "BugReports": "https://github.com/r-lib/generics", "Depends": "R (>= 3.1)", "Suggests": "covr, pkgload, testthat, tibble", "Imports": "methods", "NeedsCompilation": "no", "Packaged": "2018-11-29 13:00:32 UTC; max", "Author": "Max Kuhn [aut, cre],\n Hadley Wickham [aut],\n Davis Vaughan [aut],\n RStudio [cph]", "Maintainer": "Max Kuhn ", "Repository": "CRAN", "Date/Publication": "2018-11-29 13:20:03 UTC", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 00:36:30 +0000\"; unix" } }, "ggplot2": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "ggplot2", "Version": "3.3.2", "Title": "Create Elegant Data Visualisations Using the Grammar of Graphics", "Description": "A system for 'declaratively' creating graphics,\n based on \"The Grammar of Graphics\". You provide the data, tell 'ggplot2'\n how to map variables to aesthetics, what graphical primitives to use,\n and it takes care of the details.", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", \"aut\",\n comment = c(ORCID = \"0000-0003-4757-117X\")),\n person(\"Winston\", \"Chang\", , role = \"aut\",\n comment = c(ORCID = \"0000-0002-1576-2126\")),\n person(\"Lionel\", \"Henry\", , role = \"aut\"),\n person(\"Thomas Lin\", \"Pedersen\", , \"thomas.pedersen@rstudio.com\", \n role = c(\"aut\", \"cre\"), comment = c(ORCID = \"0000-0002-5147-4711\")),\n person(\"Kohske\", \"Takahashi\", role = \"aut\"),\n person(\"Claus\", \"Wilke\", role = \"aut\",\n comment = c(ORCID = \"0000-0002-7470-9261\")),\n person(\"Kara\", \"Woo\", role = \"aut\",\n comment = c(ORCID = \"0000-0002-5125-4188\")),\n person(\"Hiroaki\", \"Yutani\", role = \"aut\",\n comment = c(ORCID = \"0000-0002-3385-7233\")),\n person(\"Dewey\", \"Dunnington\", role = \"aut\",\n comment = c(ORCID = \"0000-0002-9415-4582\")),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Depends": "R (>= 3.2)", "Imports": "digest, glue, grDevices, grid, gtable (>= 0.1.1), isoband,\nMASS, mgcv, rlang (>= 0.3.0), scales (>= 0.5.0), stats, tibble,\nwithr (>= 2.0.0)", "Suggests": "covr, dplyr, ggplot2movies, hexbin, Hmisc, knitr, lattice,\nmapproj, maps, maptools, multcomp, munsell, nlme, profvis,\nquantreg, RColorBrewer, rgeos, rmarkdown, rpart, sf (>= 0.7-3),\nsvglite (>= 1.2.0.9001), testthat (>= 2.1.0), vdiffr (>= 0.3.0)", "Enhances": "sp", "License": "GPL-2 | file LICENSE", "URL": "http://ggplot2.tidyverse.org, https://github.com/tidyverse/ggplot2", "BugReports": "https://github.com/tidyverse/ggplot2/issues", "LazyData": "true", "Collate": "'ggproto.r' 'ggplot-global.R' 'aaa-.r'\n'aes-colour-fill-alpha.r' 'aes-evaluation.r'\n'aes-group-order.r' 'aes-linetype-size-shape.r'\n'aes-position.r' 'compat-plyr.R' 'utilities.r' 'aes.r'\n'legend-draw.r' 'geom-.r' 'annotation-custom.r'\n'annotation-logticks.r' 'geom-polygon.r' 'geom-map.r'\n'annotation-map.r' 'geom-raster.r' 'annotation-raster.r'\n'annotation.r' 'autolayer.r' 'autoplot.r' 'axis-secondary.R'\n'backports.R' 'bench.r' 'bin.R' 'coord-.r' 'coord-cartesian-.r'\n'coord-fixed.r' 'coord-flip.r' 'coord-map.r' 'coord-munch.r'\n'coord-polar.r' 'coord-quickmap.R' 'coord-sf.R'\n'coord-transform.r' 'data.R' 'facet-.r' 'facet-grid-.r'\n'facet-null.r' 'facet-wrap.r' 'fortify-lm.r' 'fortify-map.r'\n'fortify-multcomp.r' 'fortify-spatial.r' 'fortify.r' 'stat-.r'\n'geom-abline.r' 'geom-rect.r' 'geom-bar.r' 'geom-bin2d.r'\n'geom-blank.r' 'geom-boxplot.r' 'geom-col.r' 'geom-path.r'\n'geom-contour.r' 'geom-count.r' 'geom-crossbar.r'\n'geom-segment.r' 'geom-curve.r' 'geom-defaults.r'\n'geom-ribbon.r' 'geom-density.r' 'geom-density2d.r'\n'geom-dotplot.r' 'geom-errorbar.r' 'geom-errorbarh.r'\n'geom-freqpoly.r' 'geom-function.R' 'geom-hex.r'\n'geom-histogram.r' 'geom-hline.r' 'geom-jitter.r'\n'geom-label.R' 'geom-linerange.r' 'geom-point.r'\n'geom-pointrange.r' 'geom-quantile.r' 'geom-rug.r' 'geom-sf.R'\n'geom-smooth.r' 'geom-spoke.r' 'geom-text.r' 'geom-tile.r'\n'geom-violin.r' 'geom-vline.r' 'ggplot2.r' 'grob-absolute.r'\n'grob-dotstack.r' 'grob-null.r' 'grouping.r' 'guide-bins.R'\n'guide-colorbar.r' 'guide-colorsteps.R' 'guide-legend.r'\n'guides-.r' 'guides-axis.r' 'guides-grid.r' 'guides-none.r'\n'hexbin.R' 'labeller.r' 'labels.r' 'layer.r' 'layer-sf.R'\n'layout.R' 'limits.r' 'margins.R' 'performance.R'\n'plot-build.r' 'plot-construction.r' 'plot-last.r' 'plot.r'\n'position-.r' 'position-collide.r' 'position-dodge.r'\n'position-dodge2.r' 'position-identity.r' 'position-jitter.r'\n'position-jitterdodge.R' 'position-nudge.R' 'position-stack.r'\n'quick-plot.r' 'range.r' 'reshape-add-margins.R' 'save.r'\n'scale-.r' 'scale-alpha.r' 'scale-binned.R' 'scale-brewer.r'\n'scale-colour.r' 'scale-continuous.r' 'scale-date.r'\n'scale-discrete-.r' 'scale-expansion.r' 'scale-gradient.r'\n'scale-grey.r' 'scale-hue.r' 'scale-identity.r'\n'scale-linetype.r' 'scale-manual.r' 'scale-shape.r'\n'scale-size.r' 'scale-steps.R' 'scale-type.R' 'scale-view.r'\n'scale-viridis.r' 'scales-.r' 'stat-bin.r' 'stat-bin2d.r'\n'stat-bindot.r' 'stat-binhex.r' 'stat-boxplot.r'\n'stat-contour.r' 'stat-count.r' 'stat-density-2d.r'\n'stat-density.r' 'stat-ecdf.r' 'stat-ellipse.R'\n'stat-function.r' 'stat-identity.r' 'stat-qq-line.R'\n'stat-qq.r' 'stat-quantile.r' 'stat-sf-coordinates.R'\n'stat-sf.R' 'stat-smooth-methods.r' 'stat-smooth.r'\n'stat-sum.r' 'stat-summary-2d.r' 'stat-summary-bin.R'\n'stat-summary-hex.r' 'stat-summary.r' 'stat-unique.r'\n'stat-ydensity.r' 'summarise-plot.R' 'summary.r'\n'theme-elements.r' 'theme.r' 'theme-defaults.r'\n'theme-current.R' 'translate-qplot-ggplot.r'\n'translate-qplot-lattice.r' 'utilities-break.r'\n'utilities-grid.r' 'utilities-help.r' 'utilities-matrix.r'\n'utilities-resolution.r' 'utilities-table.r'\n'utilities-tidy-eval.R' 'zxx.r' 'zzz.r'", "VignetteBuilder": "knitr", "RoxygenNote": "7.1.0.9000", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-06-17 06:03:58 UTC; thomas", "Author": "Hadley Wickham [aut] (),\n Winston Chang [aut] (),\n Lionel Henry [aut],\n Thomas Lin Pedersen [aut, cre]\n (),\n Kohske Takahashi [aut],\n Claus Wilke [aut] (),\n Kara Woo [aut] (),\n Hiroaki Yutani [aut] (),\n Dewey Dunnington [aut] (),\n RStudio [cph, fnd]", "Maintainer": "Thomas Lin Pedersen ", "Repository": "CRAN", "Date/Publication": "2020-06-19 13:00:03 UTC", "Built": "R 4.0.1; ; \"Sat, 20 Jun 2020 22:34:00 +0000\"; unix" } }, "glue": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "glue", "Title": "Interpreted String Literals", "Version": "1.4.2", "Authors@R": "person(\"Jim\", \"Hester\", email = \"james.f.hester@gmail.com\", role = c(\"aut\", \"cre\"))", "Description": "An implementation of interpreted string literals, inspired by\n Python's Literal String Interpolation and Docstrings\n and Julia's Triple-Quoted String Literals\n .", "Depends": "R (>= 3.2)", "Imports": "methods", "Suggests": "testthat, covr, magrittr, crayon, knitr, rmarkdown, DBI,\nRSQLite, R.utils, forcats, microbenchmark, rprintf, stringr,\nggplot2, dplyr, withr, vctrs (>= 0.3.0)", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "URL": "https://github.com/tidyverse/glue, https://glue.tidyverse.org/", "BugReports": "https://github.com/tidyverse/glue/issues", "VignetteBuilder": "knitr", "ByteCompile": "true", "NeedsCompilation": "yes", "Packaged": "2020-08-26 17:24:06 UTC; jhester", "Author": "Jim Hester [aut, cre]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-08-27 13:50:06 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Fri, 28 Aug 2020 23:38:56 +0000\"; unix" } }, "gridExtra": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "gridExtra", "Authors@R": "c(person(\"Baptiste\", \"Auguie\",\n email = \"baptiste.auguie@gmail.com\",\n role = c(\"aut\", \"cre\")), person(\"Anton\", \"Antonov\",\n email = \"tonytonov@gmail.com\",\n role = c(\"ctb\")))", "License": "GPL (>= 2)", "Title": "Miscellaneous Functions for \"Grid\" Graphics", "Type": "Package", "Description": "Provides a number of user-level functions to work with \"grid\"\n graphics, notably to arrange multiple grid-based plots on a page, and draw\n tables.", "Version": "2.3", "VignetteBuilder": "knitr", "Imports": "gtable, grid, grDevices, graphics, utils", "Suggests": "ggplot2, egg, lattice, knitr, testthat", "RoxygenNote": "6.0.1", "NeedsCompilation": "no", "Packaged": "2017-09-08 22:52:09 UTC; baptiste", "Author": "Baptiste Auguie [aut, cre],\n Anton Antonov [ctb]", "Maintainer": "Baptiste Auguie ", "Repository": "CRAN", "Date/Publication": "2017-09-09 14:12:08 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:36:01 UTC; unix" } }, "gtable": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "gtable", "Title": "Arrange 'Grobs' in Tables", "Version": "0.3.0", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"Thomas Lin\",\n family = \"Pedersen\",\n role = \"aut\",\n email = \"thomas.pedersen@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Tools to make it easier to work with \"tables\" of\n 'grobs'. The 'gtable' package defines a 'gtable' grob class that specifies a\n grid along with a list of grobs and their placement in the grid. Further the\n package makes it easy to manipulate and combine 'gtable' objects so that \n complex compositions can be build up sequentially.", "License": "GPL-2", "Depends": "R (>= 3.0)", "Imports": "grid", "Suggests": "covr, testthat, knitr, rmarkdown, ggplot2, profvis", "Encoding": "UTF-8", "RoxygenNote": "6.1.1", "Collate": "'new-data-frame.r' 'add-grob.r' 'add-rows-cols.r'\n'add-space.r' 'grid.r' 'gtable-layouts.r' 'gtable-package.R'\n'gtable.r' 'rbind-cbind.r' 'utils.r' 'trim.r' 'filter.r'\n'align.r' 'padding.r' 'z.r'", "URL": "https://github.com/r-lib/gtable", "BugReports": "https://github.com/r-lib/gtable/issues", "VignetteBuilder": "knitr", "NeedsCompilation": "no", "Packaged": "2019-03-25 14:56:43 UTC; thomas", "Author": "Hadley Wickham [aut, cre],\n Thomas Lin Pedersen [aut],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2019-03-25 19:50:02 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:54:56 +0000\"; unix" } }, "haven": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "haven", "Title": "Import and Export 'SPSS', 'Stata' and 'SAS' Files", "Version": "2.3.1", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"Evan\",\n family = \"Miller\",\n role = c(\"aut\", \"cph\"),\n comment = \"Author of included ReadStat code\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "Import foreign statistical formats into R via the\n embedded 'ReadStat' C library,\n .", "License": "MIT + file LICENSE", "URL": "http://haven.tidyverse.org, https://github.com/tidyverse/haven,\nhttps://github.com/WizardMac/ReadStat", "BugReports": "https://github.com/tidyverse/haven/issues", "Depends": "R (>= 3.2)", "Imports": "forcats (>= 0.2.0), hms, methods, Rcpp (>= 0.11.4), readr (>=\n0.1.0), rlang (>= 0.4.0), tibble, tidyselect, vctrs (>= 0.3.0)", "Suggests": "covr, fs, knitr, rmarkdown, testthat, pillar (>= 1.4.0), cli,\ncrayon", "LinkingTo": "Rcpp", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "SystemRequirements": "GNU make", "NeedsCompilation": "yes", "Packaged": "2020-06-01 14:26:57 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n Evan Miller [aut, cph] (Author of included ReadStat code),\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-06-01 15:00:06 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Tue, 02 Jun 2020 15:25:11 +0000\"; unix" } }, "highr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "highr", "Type": "Package", "Title": "Syntax Highlighting for R Source Code", "Version": "0.8", "Authors@R": "c(person(\"Christopher\", \"Gandrud\", role = \"ctb\"),\n person(\"Qiang\", \"Li\", role = \"ctb\"),\n person(\"Yixuan\", \"Qiu\", role = \"aut\"),\n person(\"Yihui\", \"Xie\", email = \"xie@yihui.name\", role = c(\"aut\", \"cre\")))", "Maintainer": "Yihui Xie ", "Description": "Provides syntax highlighting for R source code. Currently it\n supports LaTeX and HTML output. Source code of other languages is supported\n via Andre Simon's highlight package ().", "Depends": "R (>= 3.2.3)", "Suggests": "knitr, testit", "License": "GPL", "URL": "https://github.com/yihui/highr", "BugReports": "https://github.com/yihui/highr/issues", "VignetteBuilder": "knitr", "RoxygenNote": "6.0.1", "NeedsCompilation": "no", "Packaged": "2019-03-20 18:58:08 UTC; yihui", "Author": "Christopher Gandrud [ctb],\n Qiang Li [ctb],\n Yixuan Qiu [aut],\n Yihui Xie [aut, cre]", "Repository": "CRAN", "Date/Publication": "2019-03-20 21:10:33 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:56:43 +0000\"; unix" } }, "hms": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "hms", "Title": "Pretty Time of Day", "Date": "2020-01-07", "Version": "0.5.3", "Authors@R": "c(\n person(\"Kirill\", \"Müller\", role = c(\"aut\", \"cre\"), email = \"krlmlr+r@mailbox.org\"),\n person(\"The R Consortium\", role = \"fnd\"),\n person(\"RStudio\", role = \"fnd\")\n )", "Description": "Implements an S3 class for storing and formatting time-of-day\n values, based on the 'difftime' class.", "Imports": "methods, pkgconfig, rlang, vctrs (>= 0.2.1)", "Suggests": "crayon, lubridate, pillar (>= 1.1.0), testthat", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "URL": "https://hms.tidyverse.org/, https://github.com/tidyverse/hms", "BugReports": "https://github.com/tidyverse/hms/issues", "RoxygenNote": "7.0.2", "NeedsCompilation": "no", "Packaged": "2020-01-07 16:20:04 UTC; kirill", "Author": "Kirill Müller [aut, cre],\n The R Consortium [fnd],\n RStudio [fnd]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2020-01-08 23:01:22 UTC", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 00:54:22 +0000\"; unix" } }, "htmltools": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "htmltools", "Type": "Package", "Title": "Tools for HTML", "Version": "0.5.0", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = \"aut\", email = \"joe@rstudio.com\"),\n person(\"Carson\", \"Sievert\", role = c(\"aut\", \"cre\"), email = \"carson@rstudio.com\", comment = c(ORCID = \"0000-0002-4958-2844\")),\n person(\"Winston\", \"Chang\", role = \"aut\", email = \"winston@rstudio.com\"),\n person(\"Yihui\", \"Xie\", role = \"aut\", email = \"yihui@rstudio.com\"),\n person(\"Jeff\", \"Allen\", role = \"aut\", email = \"jeff@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\")\n )", "Description": "Tools for HTML generation and output.", "Depends": "R (>= 2.14.1)", "Imports": "utils, digest, grDevices, base64enc, rlang", "Suggests": "markdown, testthat, withr, Cairo, ragg", "Enhances": "knitr", "License": "GPL (>= 2)", "URL": "https://github.com/rstudio/htmltools", "BugReports": "https://github.com/rstudio/htmltools/issues", "RoxygenNote": "7.1.0.9000", "Encoding": "UTF-8", "Collate": "'colors.R' 'html_dependency.R' 'html_escape.R' 'html_print.R'\n'images.R' 'known_tags.R' 'shim.R' 'utils.R' 'tags.R'\n'template.R'", "NeedsCompilation": "yes", "Packaged": "2020-06-15 14:46:19 UTC; cpsievert", "Author": "Joe Cheng [aut],\n Carson Sievert [aut, cre] (),\n Winston Chang [aut],\n Yihui Xie [aut],\n Jeff Allen [aut],\n RStudio [cph]", "Maintainer": "Carson Sievert ", "Repository": "CRAN", "Date/Publication": "2020-06-16 14:10:10 UTC", "Built": "R 4.0.1; x86_64-pc-linux-gnu; \"Wed, 17 Jun 2020 13:06:05 +0000\"; unix" } }, "htmlwidgets": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "htmlwidgets", "Type": "Package", "Title": "HTML Widgets for R", "Version": "1.5.1", "Authors@R": "c(\n person(\"Ramnath\", \"Vaidyanathan\", role = c(\"aut\", \"cph\")),\n person(\"Yihui\", \"Xie\", role = c(\"aut\")),\n person(\"JJ\", \"Allaire\", role = c(\"aut\")),\n person(\"Joe\", \"Cheng\", role = c(\"aut\", \"cre\"), email = \"joe@rstudio.com\"),\n person(\"Kenton\", \"Russell\", role = c(\"aut\", \"cph\")),\n person(family = \"RStudio\", role = \"cph\")\n )", "Description": "A framework for creating HTML widgets that render in various\n contexts including the R console, 'R Markdown' documents, and 'Shiny'\n web applications.", "License": "MIT + file LICENSE", "VignetteBuilder": "knitr", "Imports": "grDevices, htmltools (>= 0.3), jsonlite (>= 0.9.16), yaml", "Suggests": "knitr (>= 1.8)", "Enhances": "shiny (>= 1.1)", "URL": "https://github.com/ramnathv/htmlwidgets", "BugReports": "https://github.com/ramnathv/htmlwidgets/issues", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-10-07 21:55:31 UTC; jcheng", "Author": "Ramnath Vaidyanathan [aut, cph],\n Yihui Xie [aut],\n JJ Allaire [aut],\n Joe Cheng [aut, cre],\n Kenton Russell [aut, cph],\n RStudio [cph]", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2019-10-08 08:40:02 UTC", "Built": "R 4.0.0; ; \"Tue, 14 Apr 2020 00:47:21 +0000\"; unix" } }, "httpuv": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "httpuv", "Type": "Package", "Encoding": "UTF-8", "Title": "HTTP and WebSocket Server Library", "Version": "1.5.4", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = c(\"aut\"), email = \"joe@rstudio.com\"),\n person(\"Winston\", \"Chang\", role = c(\"aut\", \"cre\"), email = \"winston@rstudio.com\"),\n person(family = \"RStudio, PBC\", role = \"cph\"),\n person(\"Hector\", \"Corrada Bravo\", role = \"ctb\"),\n person(\"Jeroen\", \"Ooms\", role = \"ctb\")\n )", "Copyright": "RStudio, PBC; Joyent, Inc.; Nginx Inc.; Igor Sysoev; Niels\nProvos; Internet Systems Consortium, Inc.; Alexander Chemeris;\nBerkeley Software Design; Google Inc.; Sony Mobile\nCommunications AB; Alexander Peslyak; Free Software Foundation,\nInc.; X Consortium; Ben Noordhuis; StrongLoop, Inc.; Saúl\nIbarra Corretgé; Bert Belder; Fedor Indutny; libuv project;\nRefael Ackermann; Kenneth MacKay; Emergya; Diego Pettenò; xine\nproject, The Regents of the University of California, Dariusz\nDwornikowski", "Description": "Provides low-level socket and protocol support for handling\n HTTP and WebSocket requests directly from within R. It is primarily\n intended as a building block for other packages, rather than making it\n particularly easy to create complete web applications using httpuv alone.\n httpuv is built on top of the libuv and http-parser C libraries, both of\n which were developed by Joyent, Inc. (See LICENSE file for libuv and\n http-parser license information.)", "License": "GPL (>= 2) | file LICENSE", "Depends": "R (>= 2.15.1)", "Imports": "Rcpp (>= 0.11.0), utils, R6, promises, later (>= 0.8.0)", "LinkingTo": "Rcpp, BH, later", "URL": "https://github.com/rstudio/httpuv", "SystemRequirements": "GNU make", "RoxygenNote": "7.1.0", "Suggests": "testthat, callr, curl, websocket", "Collate": "'RcppExports.R' 'httpuv.R' 'random_port.R' 'server.R'\n'static_paths.R' 'utils.R'", "NeedsCompilation": "yes", "Packaged": "2020-06-05 19:56:39 UTC; winston", "Author": "Joe Cheng [aut],\n Winston Chang [aut, cre],\n RStudio, PBC [cph],\n Hector Corrada Bravo [ctb],\n Jeroen Ooms [ctb]", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2020-06-06 05:00:06 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sun, 07 Jun 2020 00:08:17 +0000\"; unix" } }, "httr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "httr", "Title": "Tools for Working with URLs and HTTP", "Version": "1.4.2", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Useful tools for working with HTTP organised by\n HTTP verbs (GET(), POST(), etc). Configuration functions make it easy\n to control additional request components (authenticate(),\n add_headers() and so on).", "License": "MIT + file LICENSE", "URL": "https://httr.r-lib.org/, https://github.com/r-lib/httr", "BugReports": "https://github.com/r-lib/httr/issues", "Depends": "R (>= 3.2)", "Imports": "curl (>= 3.0.0), jsonlite, mime, openssl (>= 0.8), R6", "Suggests": "covr, httpuv, jpeg, knitr, png, readr, rmarkdown, testthat\n(>= 0.8.0), xml2", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "RoxygenNote": "7.1.1", "NeedsCompilation": "no", "Packaged": "2020-07-20 14:19:08 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-07-20 23:40:04 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:43:05 UTC; unix" } }, "isoband": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "isoband", "Title": "Generate Isolines and Isobands from Regularly Spaced Elevation\nGrids", "Version": "0.2.2", "Authors@R": "\n c(person(given = \"Claus O.\",\n family = \"Wilke\",\n role = c(\"aut\", \"cre\"),\n email = \"wilke@austin.utexas.edu\",\n comment = c(ORCID = \"0000-0002-7470-9261\")),\n person(given = \"Thomas Lin\",\n family = \"Pedersen\",\n role = \"aut\",\n email = \"thomasp85@gmail.com\",\n comment = c(ORCID = \"0000-0002-5147-4711\")))", "Description": "A fast C++ implementation to generate contour lines (isolines) and\n contour polygons (isobands) from regularly spaced grids containing elevation data.", "URL": "https://github.com/wilkelab/isoband", "BugReports": "https://github.com/wilkelab/isoband/issues", "License": "MIT + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "LinkingTo": "testthat", "Imports": "grid, utils", "RoxygenNote": "7.1.0", "Suggests": "covr, ggplot2, knitr, magick, microbenchmark, rmarkdown, sf,\ntestthat", "SystemRequirements": "C++11", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-06-20 17:41:18 UTC; clauswilke", "Author": "Claus O. Wilke [aut, cre] (),\n Thomas Lin Pedersen [aut] ()", "Maintainer": "Claus O. Wilke ", "Repository": "CRAN", "Date/Publication": "2020-06-20 18:20:03 UTC", "Built": "R 4.0.1; x86_64-pc-linux-gnu; \"Sun, 21 Jun 2020 14:37:05 +0000\"; unix" } }, "jsonlite": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "jsonlite", "Version": "1.7.1", "Title": "A Simple and Robust JSON Parser and Generator for R", "License": "MIT + file LICENSE", "Depends": "methods", "Authors@R": "c(\n person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), email = \"jeroen@berkeley.edu\",\n comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(\"Duncan\", \"Temple Lang\", role = \"ctb\"),\n person(\"Lloyd\", \"Hilaiel\", role = \"cph\", comment=\"author of bundled libyajl\"))", "URL": "https://arxiv.org/abs/1403.2805 (paper)", "BugReports": "https://github.com/jeroen/jsonlite/issues", "Maintainer": "Jeroen Ooms ", "VignetteBuilder": "knitr, R.rsp", "Description": "A reasonably fast JSON parser and generator, optimized for statistical \n data and the web. Offers simple, flexible tools for working with JSON in R, and\n is particularly powerful for building pipelines and interacting with a web API. \n The implementation is based on the mapping described in the vignette (Ooms, 2014).\n In addition to converting JSON data from/to R objects, 'jsonlite' contains \n functions to stream, validate, and prettify JSON data. The unit tests included \n with the package verify that all edge cases are encoded and decoded consistently \n for use with dynamic data in systems and applications.", "Suggests": "httr, curl, plyr, testthat, knitr, rmarkdown, R.rsp, sf", "RoxygenNote": "7.1.0", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-09-05 10:16:12 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] (),\n Duncan Temple Lang [ctb],\n Lloyd Hilaiel [cph] (author of bundled libyajl)", "Repository": "CRAN", "Date/Publication": "2020-09-07 06:50:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-09-07 16:16:58 UTC; unix" } }, "knitr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "knitr", "Type": "Package", "Title": "A General-Purpose Package for Dynamic Report Generation in R", "Version": "1.29", "Authors@R": "c(\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Adam\", \"Vogt\", role = \"ctb\"),\n person(\"Alastair\", \"Andrew\", role = \"ctb\"),\n person(\"Alex\", \"Zvoleff\", role = \"ctb\"),\n person(\"Andre\", \"Simon\", role = \"ctb\", comment = \"the CSS files under inst/themes/ were derived from the Highlight package http://www.andre-simon.de\"),\n person(\"Aron\", \"Atkins\", role = \"ctb\"),\n person(\"Aaron\", \"Wolen\", role = \"ctb\"),\n person(\"Ashley\", \"Manton\", role = \"ctb\"),\n person(\"Atsushi\", \"Yasumoto\", role = \"ctb\", comment = c(ORCID = \"0000-0002-8335-495X\")),\n person(\"Ben\", \"Baumer\", role = \"ctb\"),\n person(\"Brian\", \"Diggs\", role = \"ctb\"),\n person(\"Brian\", \"Zhang\", role = \"ctb\"),\n person(\"Cassio\", \"Pereira\", role = \"ctb\"),\n person(\"Christophe\", \"Dervieux\", role = \"ctb\"),\n person(\"David\", \"Hugh-Jones\", role = \"ctb\"),\n person(\"David\", \"Robinson\", role = \"ctb\"),\n person(\"Doug\", \"Hemken\", role = \"ctb\"),\n person(\"Duncan\", \"Murdoch\", role = \"ctb\"),\n person(\"Elio\", \"Campitelli\", role = \"ctb\"),\n person(\"Ellis\", \"Hughes\", role = \"ctb\"),\n person(\"Emily\", \"Riederer\", role = \"ctb\"),\n person(\"Fabian\", \"Hirschmann\", role = \"ctb\"),\n person(\"Fitch\", \"Simeon\", role = \"ctb\"),\n person(\"Forest\", \"Fang\", role = \"ctb\"),\n person(c(\"Frank\", \"E\", \"Harrell\", \"Jr\"), role = \"ctb\", comment = \"the Sweavel package at inst/misc/Sweavel.sty\"),\n person(\"Garrick\", \"Aden-Buie\", role = \"ctb\"),\n person(\"Gregoire\", \"Detrez\", role = \"ctb\"),\n person(\"Hadley\", \"Wickham\", role = \"ctb\"),\n person(\"Hao\", \"Zhu\", role = \"ctb\"),\n person(\"Heewon\", \"Jeon\", role = \"ctb\"),\n person(\"Henrik\", \"Bengtsson\", role = \"ctb\"),\n person(\"Hiroaki\", \"Yutani\", role = \"ctb\"),\n person(\"Ian\", \"Lyttle\", role = \"ctb\"),\n person(\"Hodges\", \"Daniel\", role = \"ctb\"),\n person(\"Jake\", \"Burkhead\", role = \"ctb\"),\n person(\"James\", \"Manton\", role = \"ctb\"),\n person(\"Jared\", \"Lander\", role = \"ctb\"),\n person(\"Jason\", \"Punyon\", role = \"ctb\"),\n person(\"Javier\", \"Luraschi\", role = \"ctb\"),\n person(\"Jeff\", \"Arnold\", role = \"ctb\"),\n person(\"Jenny\", \"Bryan\", role = \"ctb\"),\n person(\"Jeremy\", \"Ashkenas\", role = c(\"ctb\", \"cph\"), comment = \"the CSS file at inst/misc/docco-classic.css\"),\n person(\"Jeremy\", \"Stephens\", role = \"ctb\"),\n person(\"Jim\", \"Hester\", role = \"ctb\"),\n person(\"Joe\", \"Cheng\", role = \"ctb\"),\n person(\"Johannes\", \"Ranke\", role = \"ctb\"),\n person(\"John\", \"Honaker\", role = \"ctb\"),\n person(\"John\", \"Muschelli\", role = \"ctb\"),\n person(\"Jonathan\", \"Keane\", role = \"ctb\"),\n person(\"JJ\", \"Allaire\", role = \"ctb\"),\n person(\"Johan\", \"Toloe\", role = \"ctb\"),\n person(\"Jonathan\", \"Sidi\", role = \"ctb\"),\n person(\"Joseph\", \"Larmarange\", role = \"ctb\"),\n person(\"Julien\", \"Barnier\", role = \"ctb\"),\n person(\"Kaiyin\", \"Zhong\", role = \"ctb\"),\n person(\"Kamil\", \"Slowikowski\", role = \"ctb\"),\n person(\"Karl\", \"Forner\", role = \"ctb\"),\n person(c(\"Kevin\", \"K.\"), \"Smith\", role = \"ctb\"),\n person(\"Kirill\", \"Mueller\", role = \"ctb\"),\n person(\"Kohske\", \"Takahashi\", role = \"ctb\"),\n person(\"Lorenz\", \"Walthert\", role = \"ctb\"),\n person(\"Lucas\", \"Gallindo\", role = \"ctb\"),\n person(\"Marius\", \"Hofert\", role = \"ctb\"),\n person(\"Martin\", \"Modrák\", role = \"ctb\"),\n person(\"Michael\", \"Chirico\", role = \"ctb\"),\n person(\"Michael\", \"Friendly\", role = \"ctb\"),\n person(\"Michal\", \"Bojanowski\", role = \"ctb\"),\n person(\"Michel\", \"Kuhlmann\", role = \"ctb\"),\n person(\"Miller\", \"Patrick\", role = \"ctb\"),\n person(\"Nacho\", \"Caballero\", role = \"ctb\"),\n person(\"Nick\", \"Salkowski\", role = \"ctb\"),\n person(\"Niels Richard\", \"Hansen\", role = \"ctb\"),\n person(\"Noam\", \"Ross\", role = \"ctb\"),\n person(\"Obada\", \"Mahdi\", role = \"ctb\"),\n person(\"Qiang\", \"Li\", role = \"ctb\"),\n person(\"Ramnath\", \"Vaidyanathan\", role = \"ctb\"),\n person(\"Richard\", \"Cotton\", role = \"ctb\"),\n person(\"Robert\", \"Krzyzanowski\", role = \"ctb\"),\n person(\"Romain\", \"Francois\", role = \"ctb\"),\n person(\"Ruaridh\", \"Williamson\", role = \"ctb\"),\n person(\"Scott\", \"Kostyshak\", role = \"ctb\"),\n person(\"Sebastian\", \"Meyer\", role = \"ctb\"),\n person(\"Sietse\", \"Brouwer\", role = \"ctb\"),\n person(c(\"Simon\", \"de\"), \"Bernard\", role = \"ctb\"),\n person(\"Sylvain\", \"Rousseau\", role = \"ctb\"),\n person(\"Taiyun\", \"Wei\", role = \"ctb\"),\n person(\"Thibaut\", \"Assus\", role = \"ctb\"),\n person(\"Thibaut\", \"Lamadon\", role = \"ctb\"),\n person(\"Thomas\", \"Leeper\", role = \"ctb\"),\n person(\"Tim\", \"Mastny\", role = \"ctb\"),\n person(\"Tom\", \"Torsney-Weir\", role = \"ctb\"),\n person(\"Trevor\", \"Davis\", role = \"ctb\"),\n person(\"Viktoras\", \"Veitas\", role = \"ctb\"),\n person(\"Weicheng\", \"Zhu\", role = \"ctb\"),\n person(\"Wush\", \"Wu\", role = \"ctb\"),\n person(\"Zachary\", \"Foster\", role = \"ctb\")\n )", "Description": "Provides a general-purpose tool for dynamic report generation in R\n using Literate Programming techniques.", "Depends": "R (>= 3.2.3)", "Imports": "evaluate (>= 0.10), highr, markdown, stringr (>= 0.6), yaml\n(>= 2.1.19), methods, xfun (>= 0.15), tools", "Suggests": "formatR, testit, digest, rgl (>= 0.95.1201), codetools,\nrmarkdown, htmlwidgets (>= 0.7), webshot, tikzDevice (>= 0.10),\ntinytex, reticulate (>= 1.4), JuliaCall (>= 0.11.1), magick,\npng, jpeg, gifski, xml2 (>= 1.2.0), httr, DBI (>= 0.4-1),\nshowtext, tibble, sass, ragg, styler (>= 1.2.0)", "License": "GPL", "URL": "https://yihui.org/knitr/", "BugReports": "https://github.com/yihui/knitr/issues", "Encoding": "UTF-8", "VignetteBuilder": "knitr", "SystemRequirements": "Package vignettes based on R Markdown v2 or\nreStructuredText require Pandoc (http://pandoc.org). The\nfunction rst2pdf() require rst2pdf\n(https://github.com/rst2pdf/rst2pdf).", "Collate": "'block.R' 'cache.R' 'utils.R' 'citation.R' 'hooks-html.R'\n'plot.R' 'defaults.R' 'concordance.R' 'engine.R' 'highlight.R'\n'themes.R' 'header.R' 'hooks-asciidoc.R' 'hooks-chunk.R'\n'hooks-extra.R' 'hooks-latex.R' 'hooks-md.R' 'hooks-rst.R'\n'hooks-textile.R' 'hooks.R' 'output.R' 'package.R' 'pandoc.R'\n'params.R' 'parser.R' 'pattern.R' 'rocco.R' 'spin.R' 'table.R'\n'template.R' 'utils-conversion.R' 'utils-rd2html.R'\n'utils-sweave.R' 'utils-upload.R' 'utils-vignettes.R' 'zzz.R'", "RoxygenNote": "7.1.0", "NeedsCompilation": "no", "Packaged": "2020-06-23 04:37:45 UTC; yihui", "Author": "Yihui Xie [aut, cre] (),\n Adam Vogt [ctb],\n Alastair Andrew [ctb],\n Alex Zvoleff [ctb],\n Andre Simon [ctb] (the CSS files under inst/themes/ were derived from\n the Highlight package http://www.andre-simon.de),\n Aron Atkins [ctb],\n Aaron Wolen [ctb],\n Ashley Manton [ctb],\n Atsushi Yasumoto [ctb] (),\n Ben Baumer [ctb],\n Brian Diggs [ctb],\n Brian Zhang [ctb],\n Cassio Pereira [ctb],\n Christophe Dervieux [ctb],\n David Hugh-Jones [ctb],\n David Robinson [ctb],\n Doug Hemken [ctb],\n Duncan Murdoch [ctb],\n Elio Campitelli [ctb],\n Ellis Hughes [ctb],\n Emily Riederer [ctb],\n Fabian Hirschmann [ctb],\n Fitch Simeon [ctb],\n Forest Fang [ctb],\n Frank E Harrell Jr [ctb] (the Sweavel package at inst/misc/Sweavel.sty),\n Garrick Aden-Buie [ctb],\n Gregoire Detrez [ctb],\n Hadley Wickham [ctb],\n Hao Zhu [ctb],\n Heewon Jeon [ctb],\n Henrik Bengtsson [ctb],\n Hiroaki Yutani [ctb],\n Ian Lyttle [ctb],\n Hodges Daniel [ctb],\n Jake Burkhead [ctb],\n James Manton [ctb],\n Jared Lander [ctb],\n Jason Punyon [ctb],\n Javier Luraschi [ctb],\n Jeff Arnold [ctb],\n Jenny Bryan [ctb],\n Jeremy Ashkenas [ctb, cph] (the CSS file at\n inst/misc/docco-classic.css),\n Jeremy Stephens [ctb],\n Jim Hester [ctb],\n Joe Cheng [ctb],\n Johannes Ranke [ctb],\n John Honaker [ctb],\n John Muschelli [ctb],\n Jonathan Keane [ctb],\n JJ Allaire [ctb],\n Johan Toloe [ctb],\n Jonathan Sidi [ctb],\n Joseph Larmarange [ctb],\n Julien Barnier [ctb],\n Kaiyin Zhong [ctb],\n Kamil Slowikowski [ctb],\n Karl Forner [ctb],\n Kevin K. Smith [ctb],\n Kirill Mueller [ctb],\n Kohske Takahashi [ctb],\n Lorenz Walthert [ctb],\n Lucas Gallindo [ctb],\n Marius Hofert [ctb],\n Martin Modrák [ctb],\n Michael Chirico [ctb],\n Michael Friendly [ctb],\n Michal Bojanowski [ctb],\n Michel Kuhlmann [ctb],\n Miller Patrick [ctb],\n Nacho Caballero [ctb],\n Nick Salkowski [ctb],\n Niels Richard Hansen [ctb],\n Noam Ross [ctb],\n Obada Mahdi [ctb],\n Qiang Li [ctb],\n Ramnath Vaidyanathan [ctb],\n Richard Cotton [ctb],\n Robert Krzyzanowski [ctb],\n Romain Francois [ctb],\n Ruaridh Williamson [ctb],\n Scott Kostyshak [ctb],\n Sebastian Meyer [ctb],\n Sietse Brouwer [ctb],\n Simon de Bernard [ctb],\n Sylvain Rousseau [ctb],\n Taiyun Wei [ctb],\n Thibaut Assus [ctb],\n Thibaut Lamadon [ctb],\n Thomas Leeper [ctb],\n Tim Mastny [ctb],\n Tom Torsney-Weir [ctb],\n Trevor Davis [ctb],\n Viktoras Veitas [ctb],\n Weicheng Zhu [ctb],\n Wush Wu [ctb],\n Zachary Foster [ctb]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2020-06-23 06:20:05 UTC", "Built": "R 4.0.2; ; \"Tue, 23 Jun 2020 14:43:55 +0000\"; unix" } }, "labeling": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "labeling", "Type": "Package", "Title": "Axis Labeling", "Version": "0.3", "Date": "2014-08-22", "Author": "Justin Talbot", "Maintainer": "Justin Talbot ", "Description": "Provides a range of axis labeling algorithms", "License": "MIT + file LICENSE | Unlimited", "LazyLoad": "no", "Collate": "'labeling.R'", "Repository": "CRAN", "Repository/R-Forge/Project": "labeling", "Repository/R-Forge/Revision": "16", "Repository/R-Forge/DateTimeStamp": "2014-08-23 05:53:23", "Date/Publication": "2014-08-23 14:57:53", "Packaged": "2014-08-23 06:15:07 UTC; rforge", "NeedsCompilation": "no", "Built": "R 4.0.2; ; 2020-09-03 09:08:02 UTC; unix" } }, "later": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "later", "Type": "Package", "Title": "Utilities for Scheduling Functions to Execute Later with Event\nLoops", "Version": "1.1.0.1", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", role = c(\"aut\", \"cre\"), email = \"joe@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\"),\n person(\"Winston\", \"Chang\", role = c(\"aut\"), email = \"winston@rstudio.com\"),\n person(\"Marcus\", \"Geelnard\", role = c(\"ctb\", \"cph\"), comment = \"TinyCThread library, https://tinycthread.github.io/\"),\n person(\"Evan\", \"Nemerson\", role = c(\"ctb\", \"cph\"), comment = \"TinyCThread library, https://tinycthread.github.io/\")\n )", "Description": "Executes arbitrary R or C functions some time after the current\n time, after the R execution stack has emptied. The functions are scheduled\n in an event loop.", "URL": "https://github.com/r-lib/later", "BugReports": "https://github.com/r-lib/later/issues", "License": "GPL (>= 2)", "Imports": "Rcpp (>= 0.12.9), rlang", "LinkingTo": "Rcpp, BH", "RoxygenNote": "7.1.0", "Suggests": "knitr, rmarkdown, testthat", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-06-02 13:23:01 UTC; winston", "Author": "Joe Cheng [aut, cre],\n RStudio [cph],\n Winston Chang [aut],\n Marcus Geelnard [ctb, cph] (TinyCThread library,\n https://tinycthread.github.io/),\n Evan Nemerson [ctb, cph] (TinyCThread library,\n https://tinycthread.github.io/)", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2020-06-05 10:10:13 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sat, 06 Jun 2020 23:03:19 +0000\"; unix" } }, "lattice": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "lattice", "Version": "0.20-41", "Date": "2020-04-01", "Priority": "recommended", "Title": "Trellis Graphics for R", "Authors@R": "c(person(\"Deepayan\", \"Sarkar\", role = c(\"aut\", \"cre\"),\n\t email = \"deepayan.sarkar@r-project.org\",\n\t\t comment = c(ORCID = \"0000-0003-4107-1553\")),\n person(\"Felix\", \"Andrews\", role = \"ctb\"),\n\t person(\"Kevin\", \"Wright\", role = \"ctb\", comment = \"documentation\"),\n\t person(\"Neil\", \"Klepeis\", role = \"ctb\"),\n person(\"Paul\", \"Murrell\", role = \"ctb\", email = \"paul@stat.auckland.ac.nz\"))", "Description": "A powerful and elegant high-level data visualization\n system inspired by Trellis graphics, with an emphasis on\n multivariate data. Lattice is sufficient for typical graphics needs,\n and is also flexible enough to handle most nonstandard requirements.\n See ?Lattice for an introduction.", "Depends": "R (>= 3.0.0)", "Suggests": "KernSmooth, MASS, latticeExtra", "Imports": "grid, grDevices, graphics, stats, utils", "Enhances": "chron", "LazyLoad": "yes", "LazyData": "yes", "License": "GPL (>= 2)", "URL": "http://lattice.r-forge.r-project.org/", "BugReports": "https://github.com/deepayan/lattice/issues", "NeedsCompilation": "yes", "Packaged": "2020-04-02 10:52:37 UTC; deepayan", "Author": "Deepayan Sarkar [aut, cre] (),\n Felix Andrews [ctb],\n Kevin Wright [ctb] (documentation),\n Neil Klepeis [ctb],\n Paul Murrell [ctb]", "Maintainer": "Deepayan Sarkar ", "Repository": "CRAN", "Date/Publication": "2020-04-02 12:00:06 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 03:24:10 +0000\"; unix" } }, "lazyeval": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "lazyeval", "Version": "0.2.2", "Title": "Lazy (Non-Standard) Evaluation", "Description": "An alternative approach to non-standard evaluation using\n formulas. Provides a full implementation of LISP style 'quasiquotation',\n making it easier to generate code with other code.", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", ,\"hadley@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"RStudio\", role = \"cph\")\n )", "License": "GPL-3", "LazyData": "true", "Depends": "R (>= 3.1.0)", "Suggests": "knitr, rmarkdown (>= 0.2.65), testthat, covr", "VignetteBuilder": "knitr", "RoxygenNote": "6.1.1", "NeedsCompilation": "yes", "Packaged": "2019-03-15 14:18:01 UTC; lionel", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2019-03-15 17:50:07 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 20:55:18 +0000\"; unix" } }, "leaflet": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "leaflet", "Type": "Package", "Title": "Create Interactive Web Maps with the JavaScript 'Leaflet'\nLibrary", "Version": "2.0.3", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", email = \"joe@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"Bhaskar\", \"Karambelkar\", role = c(\"aut\")),\n person(\"Yihui\", \"Xie\", role = c(\"aut\")),\n person(\"Hadley\", \"Wickham\", role = c(\"ctb\")),\n person(\"Kenton\", \"Russell\", role = c(\"ctb\")),\n person(\"Kent\", \"Johnson\", role = c(\"ctb\")),\n person(\"Barret\", \"Schloerke\", role = c(\"ctb\")),\n person(\"jQuery Foundation and contributors\", role = c(\"ctb\", \"cph\"), comment = \"jQuery library\"),\n person(\"Vladimir\", \"Agafonkin\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet library\"),\n person(\"CloudMade\", role = c(\"cph\"), comment = \"Leaflet library\"),\n person(\"Leaflet contributors\", role = c(\"ctb\"), comment = \"Leaflet library\"),\n person(\"Brandon Copeland\", role = c(\"ctb\", \"cph\"), comment = \"leaflet-measure plugin\"),\n person(\"Joerg Dietrich\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet.Terminator plugin\"),\n person(\"Benjamin Becquet\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet.MagnifyingGlass plugin\"),\n person(\"Norkart AS\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet.MiniMap plugin\"),\n person(\"L. Voogdt\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet.awesome-markers plugin\"),\n person(\"Daniel Montague\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet.EasyButton plugin\"),\n person(\"Kartena AB\", role = c(\"ctb\", \"cph\"), comment = \"Proj4Leaflet plugin\"),\n person(\"Robert Kajic\", role = c(\"ctb\", \"cph\"), comment = \"leaflet-locationfilter plugin\"),\n person(\"Mapbox\", role = c(\"ctb\", \"cph\"), comment = \"leaflet-omnivore plugin\"),\n person(\"Michael Bostock\", role = c(\"ctb\", \"cph\"), comment = \"topojson\"),\n person(\"RStudio\", role = c(\"cph\"))\n )", "Description": "Create and customize interactive maps using the 'Leaflet'\n JavaScript library and the 'htmlwidgets' package. These maps can be used\n directly from the R console, from 'RStudio', in Shiny applications and R Markdown\n documents.", "License": "GPL-3", "URL": "http://rstudio.github.io/leaflet/", "BugReports": "https://github.com/rstudio/leaflet/issues", "Depends": "R (>= 3.1.0)", "Imports": "base64enc, crosstalk, htmlwidgets, htmltools, magrittr,\nmarkdown, methods, png, RColorBrewer, raster, scales (>=\n1.0.0), sp, stats, viridis (>= 0.5.1), leaflet.providers (>=\n1.8.0)", "Suggests": "knitr, maps, sf, shiny, rgdal, rgeos, R6, RJSONIO, purrr,\ntestthat", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "LazyData": "true", "NeedsCompilation": "no", "Packaged": "2019-11-14 18:00:03 UTC; barret", "Author": "Joe Cheng [aut, cre],\n Bhaskar Karambelkar [aut],\n Yihui Xie [aut],\n Hadley Wickham [ctb],\n Kenton Russell [ctb],\n Kent Johnson [ctb],\n Barret Schloerke [ctb],\n jQuery Foundation and contributors [ctb, cph] (jQuery library),\n Vladimir Agafonkin [ctb, cph] (Leaflet library),\n CloudMade [cph] (Leaflet library),\n Leaflet contributors [ctb] (Leaflet library),\n Brandon Copeland [ctb, cph] (leaflet-measure plugin),\n Joerg Dietrich [ctb, cph] (Leaflet.Terminator plugin),\n Benjamin Becquet [ctb, cph] (Leaflet.MagnifyingGlass plugin),\n Norkart AS [ctb, cph] (Leaflet.MiniMap plugin),\n L. Voogdt [ctb, cph] (Leaflet.awesome-markers plugin),\n Daniel Montague [ctb, cph] (Leaflet.EasyButton plugin),\n Kartena AB [ctb, cph] (Proj4Leaflet plugin),\n Robert Kajic [ctb, cph] (leaflet-locationfilter plugin),\n Mapbox [ctb, cph] (leaflet-omnivore plugin),\n Michael Bostock [ctb, cph] (topojson),\n RStudio [cph]", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2019-11-16 05:50:05 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:41:40 UTC; unix" } }, "leaflet.providers": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "leaflet.providers", "Type": "Package", "Title": "Leaflet Providers", "Version": "1.9.0", "Authors@R": "c(\n person(\"Leslie\", \"Huang\", email = \"lesliehuang@nyu.edu\", role = c(\"aut\")),\n person(\"Barret\", \"Schloerke\", email = \"barret@rstudio.com\", role = c(\"ctb\", \"cre\"),\n comment = c(ORCID = \"0000-0001-9986-114X\")),\n person(\"Leaflet Providers contributors\", role = c(\"ctb\", \"cph\"), comment = \"Leaflet Providers plugin\"),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Description": "Contains third-party map tile provider information from\n 'Leaflet.js', , to be\n used with the 'leaflet' R package. Additionally, 'leaflet.providers'\n enables users to retrieve up-to-date provider information between package\n updates.", "License": "BSD_2_clause + file LICENSE", "Encoding": "UTF-8", "LazyData": "true", "Depends": "R (>= 2.10)", "Suggests": "V8, jsonlite, testthat (>= 2.1.0)", "Language": "en-US", "RoxygenNote": "6.1.1", "URL": "https://github.com/rstudio/leaflet.providers", "BugReports": "https://github.com/rstudio/leaflet.providers/issues", "Collate": "'providers_data.R' 'get_current_providers.R'", "NeedsCompilation": "no", "Packaged": "2019-11-08 22:12:22 UTC; barret", "Author": "Leslie Huang [aut],\n Barret Schloerke [ctb, cre] (),\n Leaflet Providers contributors [ctb, cph] (Leaflet Providers plugin),\n RStudio [cph, fnd]", "Maintainer": "Barret Schloerke ", "Repository": "CRAN", "Date/Publication": "2019-11-09 23:40:09 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:36:01 UTC; unix" } }, "lifecycle": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "lifecycle", "Title": "Manage the Life Cycle of your Package Functions", "Version": "0.2.0", "Authors@R": "\n c(\n person(given = \"Lionel\", family = \"Henry\", role = c(\"aut\", \"cre\"), email = \"lionel@rstudio.com\"),\n person(given = \"RStudio\", role = \"cph\")\n )", "Description": "Manage the life cycle of your exported functions with\n shared conventions, documentation badges, and non-invasive\n deprecation warnings. The 'lifecycle' package defines four\n development stages (experimental, maturing, stable, and\n questioning) and three deprecation stages (soft-deprecated,\n deprecated, and defunct). It makes it easy to insert badges\n corresponding to these stages in your documentation. Usage of\n deprecated functions are signalled with increasing levels of\n non-invasive verbosity.", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "Depends": "R (>= 3.2)", "Imports": "glue, rlang (>= 0.4.0)", "Suggests": "covr, crayon, knitr, rmarkdown, testthat (>= 2.1.0)", "RoxygenNote": "7.0.2", "URL": "https://lifecycle.r-lib.org/, https://github.com/r-lib/lifecycle", "BugReports": "https://github.com/r-lib/lifecycle/issues", "VignetteBuilder": "knitr", "NeedsCompilation": "no", "Packaged": "2020-03-06 15:50:11 UTC; lionel", "Author": "Lionel Henry [aut, cre],\n RStudio [cph]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-03-06 16:20:02 UTC", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 00:44:31 +0000\"; unix" } }, "lubridate": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Type": "Package", "Package": "lubridate", "Title": "Make Dealing with Dates a Little Easier", "Version": "1.7.9", "Authors@R": "\n c(person(given = \"Vitalie\",\n family = \"Spinu\",\n role = c(\"aut\", \"cre\"),\n email = \"spinuvit@gmail.com\"),\n person(given = \"Garrett\",\n family = \"Grolemund\",\n role = \"aut\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\"),\n person(given = \"Ian\",\n family = \"Lyttle\",\n role = \"ctb\"),\n person(given = \"Imanuel\",\n family = \"Costigan\",\n role = \"ctb\"),\n person(given = \"Jason\",\n family = \"Law\",\n role = \"ctb\"),\n person(given = \"Doug\",\n family = \"Mitarotonda\",\n role = \"ctb\"),\n person(given = \"Joseph\",\n family = \"Larmarange\",\n role = \"ctb\"),\n person(given = \"Jonathan\",\n family = \"Boiser\",\n role = \"ctb\"),\n person(given = \"Chel Hee\",\n family = \"Lee\",\n role = \"ctb\"))", "Maintainer": "Vitalie Spinu ", "Description": "Functions to work with date-times and time-spans:\n fast and user friendly parsing of date-time data, extraction and\n updating of components of a date-time (years, months, days, hours,\n minutes, and seconds), algebraic manipulation on date-time and\n time-span objects. The 'lubridate' package has a consistent and\n memorable syntax that makes working with dates easy and fun. Parts of\n the 'CCTZ' source code, released under the Apache 2.0 License, are\n included in this package. See for\n more details.", "License": "GPL (>= 2)", "URL": "http://lubridate.tidyverse.org,\nhttps://github.com/tidyverse/lubridate", "BugReports": "https://github.com/tidyverse/lubridate/issues", "Depends": "methods, R (>= 3.2)", "Imports": "generics, Rcpp (>= 0.12.13)", "Suggests": "covr, knitr, testthat (>= 2.1.0), vctrs (>= 0.3.0)", "Enhances": "chron, timeDate, tis, zoo", "LinkingTo": "Rcpp", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "SystemRequirements": "A system with zoneinfo data (e.g.\n/usr/share/zoneinfo) as well as a recent-enough C++11 compiler\n(such as g++-4.8 or later). On Windows the zoneinfo included\nwith R is used.", "Collate": "'Dates.r' 'POSIXt.r' 'RcppExports.R' 'util.r' 'parse.r'\n'timespans.r' 'intervals.r' 'difftimes.r' 'durations.r'\n'periods.r' 'accessors-date.R' 'accessors-day.r'\n'accessors-dst.r' 'accessors-hour.r' 'accessors-minute.r'\n'accessors-month.r' 'accessors-quarter.r' 'accessors-second.r'\n'accessors-tz.r' 'accessors-week.r' 'accessors-year.r'\n'am-pm.r' 'time-zones.r' 'numeric.r' 'coercion.r' 'constants.r'\n'cyclic_encoding.r' 'data.r' 'decimal-dates.r' 'deprecated.r'\n'format_ISO8601.r' 'guess.r' 'hidden.r' 'instants.r'\n'leap-years.r' 'ops-addition.r' 'ops-compare.r'\n'ops-division.r' 'ops-integer-division.r' 'ops-m+.r'\n'ops-modulo.r' 'ops-multiplication.r' 'ops-subtraction.r'\n'package.r' 'pretty.r' 'round.r' 'stamp.r' 'update.r' 'vctrs.R'\n'zzz.R'", "NeedsCompilation": "yes", "Packaged": "2020-06-03 11:12:59 UTC; vspinu", "Author": "Vitalie Spinu [aut, cre],\n Garrett Grolemund [aut],\n Hadley Wickham [aut],\n Ian Lyttle [ctb],\n Imanuel Costigan [ctb],\n Jason Law [ctb],\n Doug Mitarotonda [ctb],\n Joseph Larmarange [ctb],\n Jonathan Boiser [ctb],\n Chel Hee Lee [ctb]", "Repository": "CRAN", "Date/Publication": "2020-06-08 15:40:02 UTC", "Built": "R 4.0.1; x86_64-pc-linux-gnu; \"Tue, 09 Jun 2020 13:26:25 +0000\"; unix" } }, "magrittr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "magrittr", "Type": "Package", "Title": "A Forward-Pipe Operator for R", "Version": "1.5", "Author": "Stefan Milton Bache and\n Hadley Wickham ", "Maintainer": "Stefan Milton Bache ", "Description": "Provides a mechanism for chaining commands with a\n new forward-pipe operator, %>%. This operator will forward a\n value, or the result of an expression, into the next function\n call/expression. There is flexible support for the type\n of right-hand side expressions. For more information, see\n package vignette.\n To quote Rene Magritte, \"Ceci n'est pas un pipe.\"", "Suggests": "testthat, knitr", "VignetteBuilder": "knitr", "License": "MIT + file LICENSE", "ByteCompile": "Yes", "Packaged": "2014-11-22 08:50:53 UTC; shb", "NeedsCompilation": "no", "Repository": "CRAN", "Date/Publication": "2014-11-22 19:15:57", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:51:15 +0000\"; unix" } }, "markdown": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "markdown", "Type": "Package", "Title": "Render Markdown with the C Library 'Sundown'", "Version": "1.1", "Authors@R": "c(\n person(\"JJ\", \"Allaire\", role = \"aut\"),\n person(\"Jeffrey\", \"Horner\", role = \"aut\"),\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Henrik\", \"Bengtsson\", role = \"ctb\"),\n person(\"Jim\", \"Hester\", role = \"ctb\"),\n person(\"Yixuan\", \"Qiu\", role = \"ctb\"),\n person(\"Kohske\", \"Takahashi\", role = \"ctb\"),\n person(\"Adam\", \"November\", role = \"ctb\"),\n person(\"Nacho\", \"Caballero\", role = \"ctb\"),\n person(\"Jeroen\", \"Ooms\", role = \"ctb\"),\n person(\"Thomas\", \"Leeper\", role = \"ctb\"),\n person(\"Joe\", \"Cheng\", role = \"ctb\"),\n person(\"Andrzej\", \"Oles\", role = \"ctb\"),\n person(\"Vicent\", \"Marti\", role = c(\"aut\", \"cph\"), comment = \"The Sundown library\"),\n person(\"Natacha\", \"Porte\", role = c(\"aut\", \"cph\"), comment = \"The Sundown library\"),\n person(family = \"RStudio\", role = \"cph\")\n )", "Description": "Provides R bindings to the 'Sundown' Markdown rendering library\n (). Markdown is a plain-text formatting\n syntax that can be converted to 'XHTML' or other formats. See\n for more information about Markdown.", "Depends": "R (>= 2.11.1)", "Imports": "utils, xfun, mime (>= 0.3)", "Suggests": "knitr, RCurl", "License": "GPL-2", "URL": "https://github.com/rstudio/markdown", "BugReports": "https://github.com/rstudio/markdown/issues", "VignetteBuilder": "knitr", "RoxygenNote": "6.1.1", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2019-08-07 15:59:28 UTC; yihui", "Author": "JJ Allaire [aut],\n Jeffrey Horner [aut],\n Yihui Xie [aut, cre] (),\n Henrik Bengtsson [ctb],\n Jim Hester [ctb],\n Yixuan Qiu [ctb],\n Kohske Takahashi [ctb],\n Adam November [ctb],\n Nacho Caballero [ctb],\n Jeroen Ooms [ctb],\n Thomas Leeper [ctb],\n Joe Cheng [ctb],\n Andrzej Oles [ctb],\n Vicent Marti [aut, cph] (The Sundown library),\n Natacha Porte [aut, cph] (The Sundown library),\n RStudio [cph]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2019-08-07 16:30:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 22:37:17 +0000\"; unix" } }, "mgcv": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "mgcv", "Version": "1.8-33", "Author": "Simon Wood ", "Maintainer": "Simon Wood ", "Title": "Mixed GAM Computation Vehicle with Automatic Smoothness\nEstimation", "Description": "Generalized additive (mixed) models, some of their extensions and \n other generalized ridge regression with multiple smoothing \n parameter estimation by (Restricted) Marginal Likelihood, \n Generalized Cross Validation and similar, or using iterated \n nested Laplace approximation for fully Bayesian inference. See \n Wood (2017) for an overview. \n Includes a gam() function, a wide variety of smoothers, 'JAGS' \n support and distributions beyond the exponential family. ", "Priority": "recommended", "Depends": "R (>= 2.14.0), nlme (>= 3.1-64)", "Imports": "methods, stats, graphics, Matrix, splines, utils", "Suggests": "parallel, survival, MASS", "LazyLoad": "yes", "ByteCompile": "yes", "License": "GPL (>= 2)", "NeedsCompilation": "yes", "Packaged": "2020-08-24 19:32:32 UTC; sw283", "Repository": "CRAN", "Date/Publication": "2020-08-27 08:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sat, 29 Aug 2020 15:51:59 +0000\"; unix" } }, "mime": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "mime", "Type": "Package", "Title": "Map Filenames to MIME Types", "Version": "0.9", "Authors@R": "c(\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Jeffrey\", \"Horner\", role = \"ctb\"),\n person(\"Beilei\", \"Bian\", role = \"ctb\")\n )", "Description": "Guesses the MIME type from a filename extension using the data\n derived from /etc/mime.types in UNIX-type systems.", "Imports": "tools", "License": "GPL", "URL": "https://github.com/yihui/mime", "BugReports": "https://github.com/yihui/mime/issues", "LazyData": "TRUE", "RoxygenNote": "7.0.2", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-02-04 17:11:45 UTC; yihui", "Author": "Yihui Xie [aut, cre] (),\n Jeffrey Horner [ctb],\n Beilei Bian [ctb]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2020-02-04 18:20:06 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 20:54:34 +0000\"; unix" } }, "modelr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "modelr", "Title": "Modelling Functions that Work with the Pipe", "Version": "0.1.8", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Functions for modelling that help you seamlessly\n integrate modelling into a pipeline of data manipulation and\n visualisation.", "License": "GPL-3", "URL": "https://modelr.tidyverse.org, https://github.com/tidyverse/modelr", "BugReports": "https://github.com/tidyverse/modelr/issues", "Depends": "R (>= 3.2)", "Imports": "broom, magrittr, purrr (>= 0.2.2), rlang (>= 0.2.0), tibble,\ntidyr (>= 0.8.0), tidyselect, vctrs", "Suggests": "compiler, covr, ggplot2, testthat", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "NeedsCompilation": "no", "Packaged": "2020-05-19 18:05:04 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-05-19 20:00:35 UTC", "Built": "R 4.0.0; ; \"Wed, 20 May 2020 15:03:06 +0000\"; unix" } }, "munsell": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "munsell", "Type": "Package", "Title": "Utilities for Using Munsell Colours", "Version": "0.5.0", "Author": "Charlotte Wickham ", "Maintainer": "Charlotte Wickham ", "Description": "Provides easy access to, and manipulation of, the Munsell \n colours. Provides a mapping between Munsell's \n original notation (e.g. \"5R 5/10\") and hexadecimal strings suitable \n for use directly in R graphics. Also provides utilities \n to explore slices through the Munsell colour tree, to transform \n Munsell colours and display colour palettes.", "Suggests": "ggplot2, testthat", "Imports": "colorspace, methods", "License": "MIT + file LICENSE", "URL": "https://cran.r-project.org/package=munsell,\nhttps://github.com/cwickham/munsell/", "RoxygenNote": "6.0.1", "NeedsCompilation": "no", "Packaged": "2018-06-11 23:15:15 UTC; wickhamc", "Repository": "CRAN", "Date/Publication": "2018-06-12 04:29:06 UTC", "Built": "R 4.0.2; ; 2020-08-25 21:01:57 UTC; unix" } }, "nlme": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "nlme", "Version": "3.1-149", "Date": "2020-08-21", "Priority": "recommended", "Title": "Linear and Nonlinear Mixed Effects Models", "Authors@R": "c(person(\"José\", \"Pinheiro\", role = \"aut\", comment = \"S version\"),\n person(\"Douglas\", \"Bates\", role = \"aut\", comment = \"up to 2007\"),\n person(\"Saikat\", \"DebRoy\", role = \"ctb\", comment = \"up to 2002\"),\n person(\"Deepayan\", \"Sarkar\", role = \"ctb\", comment = \"up to 2005\"),\n person(\"EISPACK authors\", role = \"ctb\", comment = \"src/rs.f\"),\n\t person(\"Siem\", \"Heisterkamp\", role = \"ctb\", comment = \"Author fixed sigma\"),\n person(\"Bert\", \"Van Willigen\",role = \"ctb\", comment = \"Programmer fixed sigma\"),\n\t person(\"R-core\", email = \"R-core@R-project.org\",\n role = c(\"aut\", \"cre\")))", "Contact": "see 'MailingList'", "Description": "Fit and compare Gaussian linear and nonlinear mixed-effects models.", "Depends": "R (>= 3.4.0)", "Imports": "graphics, stats, utils, lattice", "Suggests": "Hmisc, MASS", "LazyData": "yes", "ByteCompile": "yes", "Encoding": "UTF-8", "License": "GPL (>= 2) | file LICENCE", "BugReports": "https://bugs.r-project.org", "MailingList": "R-help@r-project.org", "URL": "https://svn.r-project.org/R-packages/trunk/nlme/", "NeedsCompilation": "yes", "Packaged": "2020-08-21 12:48:52 UTC; ripley", "Author": "José Pinheiro [aut] (S version),\n Douglas Bates [aut] (up to 2007),\n Saikat DebRoy [ctb] (up to 2002),\n Deepayan Sarkar [ctb] (up to 2005),\n EISPACK authors [ctb] (src/rs.f),\n Siem Heisterkamp [ctb] (Author fixed sigma),\n Bert Van Willigen [ctb] (Programmer fixed sigma),\n R-core [aut, cre]", "Maintainer": "R-core ", "Repository": "CRAN", "Date/Publication": "2020-08-23 05:23:49 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 'Tue, 25 Aug 2020 15:49:57 +0000'; unix" } }, "openssl": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "openssl", "Type": "Package", "Title": "Toolkit for Encryption, Signatures and Certificates Based on\nOpenSSL", "Version": "1.4.2", "Authors@R": "c(person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), email = \"jeroen@berkeley.edu\",\n comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(\"Oliver\", \"Keyes\", role = \"ctb\"))", "Description": "Bindings to OpenSSL libssl and libcrypto, plus custom SSH key parsers.\n Supports RSA, DSA and EC curves P-256, P-384, P-521, and curve25519. Cryptographic\n signatures can either be created and verified manually or via x509 certificates. \n AES can be used in cbc, ctr or gcm mode for symmetric encryption; RSA for asymmetric\n (public key) encryption or EC for Diffie Hellman. High-level envelope functions \n combine RSA and AES for encrypting arbitrary sized data. Other utilities include key\n generators, hash functions (md5, sha1, sha256, etc), base64 encoder, a secure random\n number generator, and 'bignum' math methods for manually performing crypto \n calculations on large multibyte integers.", "License": "MIT + file LICENSE", "URL": "https://github.com/jeroen/openssl#readme", "BugReports": "https://github.com/jeroen/openssl/issues", "SystemRequirements": "OpenSSL >= 1.0.1", "VignetteBuilder": "knitr", "Imports": "askpass", "Suggests": "testthat (>= 2.1.0), digest, knitr, rmarkdown, jsonlite,\njose, sodium", "RoxygenNote": "7.1.1", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-06-27 13:18:44 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] (),\n Oliver Keyes [ctb]", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2020-06-27 15:00:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sun, 28 Jun 2020 16:58:06 +0000\"; unix" } }, "pillar": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "pillar", "Title": "Coloured Formatting for Columns", "Version": "1.4.6", "Authors@R": "\n c(person(given = \"Kirill\",\n family = \"M\\u00fcller\",\n role = c(\"aut\", \"cre\"),\n email = \"krlmlr+r@mailbox.org\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Provides 'pillar' and 'colonnade' generics designed\n for formatting columns of data using the full range of colours\n provided by modern terminals.", "License": "GPL-3", "URL": "https://github.com/r-lib/pillar", "BugReports": "https://github.com/r-lib/pillar/issues", "Imports": "cli, crayon (>= 1.3.4), ellipsis, fansi, lifecycle, rlang (>=\n0.3.0), utf8 (>= 1.1.0), vctrs (>= 0.2.0)", "Suggests": "bit64, knitr, lubridate, testthat (>= 2.0.0), withr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1.9000", "NeedsCompilation": "no", "Packaged": "2020-07-10 13:36:04 UTC; kirill", "Author": "Kirill Müller [aut, cre],\n Hadley Wickham [aut],\n RStudio [cph]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2020-07-10 17:10:13 UTC", "Built": "R 4.0.2; ; \"Sun, 12 Jul 2020 01:56:20 +0000\"; unix" } }, "pkgbuild": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "pkgbuild", "Title": "Find Tools Needed to Build R Packages", "Version": "1.1.0", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", role = \"aut\"),\n person(\"Jim\", \"Hester\", , \"jim.hester@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"RStudio\", role = \"cph\")\n )", "Description": "Provides functions used to build R packages. Locates compilers\n needed to build R packages on various platforms and ensures the PATH is\n configured appropriately so R can use them.", "Imports": "callr (>= 3.2.0), cli, crayon, desc, prettyunits, R6,\nrprojroot, withr (>= 2.1.2)", "Suggests": "Rcpp, cpp11, testthat, covr", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "URL": "https://github.com/r-lib/pkgbuild", "BugReports": "https://github.com/r-lib/pkgbuild/issues", "Depends": "R (>= 3.1)", "NeedsCompilation": "no", "Packaged": "2020-07-13 15:26:18 UTC; jhester", "Author": "Hadley Wickham [aut],\n Jim Hester [aut, cre],\n RStudio [cph]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-07-13 17:30:02 UTC", "Built": "R 4.0.2; ; \"Tue, 14 Jul 2020 13:44:48 +0000\"; unix" } }, "pkgconfig": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "pkgconfig", "Title": "Private Configuration for 'R' Packages", "Version": "2.0.3", "Author": "Gábor Csárdi", "Maintainer": "Gábor Csárdi ", "Description": "Set configuration options on a per-package basis.\n Options set by a given package only apply to that package,\n other packages are unaffected.", "License": "MIT + file LICENSE", "LazyData": "true", "Imports": "utils", "Suggests": "covr, testthat, disposables (>= 1.0.3)", "URL": "https://github.com/r-lib/pkgconfig#readme", "BugReports": "https://github.com/r-lib/pkgconfig/issues", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2019-09-22 08:42:40 UTC; gaborcsardi", "Repository": "CRAN", "Date/Publication": "2019-09-22 09:20:02 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 21:02:37 +0000\"; unix" } }, "pkgload": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "pkgload", "Title": "Simulate Package Installation and Attach", "Version": "1.1.0", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", role = \"aut\"),\n person(\"Jim\", \"Hester\", , \"jim.hester@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"Winston\", \"Chang\", role = \"aut\"),\n person(\"RStudio\", role = \"cph\"),\n person(\"R Core team\", role = \"ctb\",\n comment = \"Some namespace and vignette code extracted from base R\")\n )", "Description": "Simulates the process of installing a package\n and then attaching it. This is a key part of the 'devtools' package as it\n allows you to rapidly iterate while developing a package.", "License": "GPL-3", "URL": "https://github.com/r-lib/pkgload", "BugReports": "https://github.com/r-lib/pkgload/issues", "Imports": "cli, crayon, desc, methods, pkgbuild, rlang, rprojroot,\nrstudioapi, utils, withr", "Suggests": "bitops, covr, Rcpp, testthat", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "NeedsCompilation": "yes", "Packaged": "2020-05-28 17:51:35 UTC; jhester", "Author": "Hadley Wickham [aut],\n Jim Hester [aut, cre],\n Winston Chang [aut],\n RStudio [cph],\n R Core team [ctb] (Some namespace and vignette code extracted from base\n R)", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-05-29 05:10:08 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Fri, 29 May 2020 16:59:56 +0000\"; unix" } }, "png": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "png", "Version": "0.1-7", "Title": "Read and write PNG images", "Author": "Simon Urbanek ", "Maintainer": "Simon Urbanek ", "Depends": "R (>= 2.9.0)", "Description": "This package provides an easy and simple way to read, write and display bitmap images stored in the PNG format. It can read and write both files and in-memory raw vectors.", "License": "GPL-2 | GPL-3", "SystemRequirements": "libpng", "URL": "http://www.rforge.net/png/", "Packaged": "2013-12-03 20:09:14 UTC; svnuser", "NeedsCompilation": "yes", "Repository": "CRAN", "Date/Publication": "2013-12-03 22:25:05", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:36:01 UTC; unix" } }, "praise": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "praise", "Title": "Praise Users", "Version": "1.0.0", "Author": "Gabor Csardi, Sindre Sorhus", "Maintainer": "Gabor Csardi ", "Description": "Build friendly R packages that\n praise their users if they have done something\n good, or they just need it to feel better.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/gaborcsardi/praise", "BugReports": "https://github.com/gaborcsardi/praise/issues", "Suggests": "testthat", "Collate": "'adjective.R' 'adverb.R' 'exclamation.R' 'verb.R' 'rpackage.R'\n'package.R'", "NeedsCompilation": "no", "Packaged": "2015-08-11 02:01:43 UTC; gaborcsardi", "Repository": "CRAN", "Date/Publication": "2015-08-11 08:22:28", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 00:35:49 +0000\"; unix" } }, "prettyunits": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "prettyunits", "Title": "Pretty, Human Readable Formatting of Quantities", "Version": "1.1.1", "Author": "Gabor Csardi", "Maintainer": "Gabor Csardi ", "Description": "Pretty, human readable formatting of quantities.\n Time intervals: '1337000' -> '15d 11h 23m 20s'.\n Vague time intervals: '2674000' -> 'about a month ago'.\n Bytes: '1337' -> '1.34 kB'.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/gaborcsardi/prettyunits", "BugReports": "https://github.com/gaborcsardi/prettyunits/issues", "Suggests": "codetools, covr, testthat", "RoxygenNote": "7.0.2", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-01-24 02:16:46 UTC; gaborcsardi", "Repository": "CRAN", "Date/Publication": "2020-01-24 06:50:07 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 23:18:37 +0000\"; unix" } }, "processx": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "processx", "Title": "Execute and Control System Processes", "Version": "3.4.4", "Authors@R": "c(\n person(\"Gábor\", \"Csárdi\", role = c(\"aut\", \"cre\", \"cph\"),\n email = \"csardi.gabor@gmail.com\",\n\t comment = c(ORCID = \"0000-0001-7098-9676\")),\n person(\"Winston\", \"Chang\", role = \"aut\"),\n person(\"RStudio\", role = c(\"cph\", \"fnd\")),\n person(\"Mango Solutions\", role = c(\"cph\", \"fnd\")))", "Description": "Tools to run system processes in the background.\n It can check if a background process is running; wait on a background\n process to finish; get the exit status of finished processes; kill\n background processes. It can read the standard output and error of\n the processes, using non-blocking connections. 'processx' can poll\n a process for standard output or error, with a timeout. It can also\n poll several processes at once.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://processx.r-lib.org,\nhttps://github.com/r-lib/processx#readme", "BugReports": "https://github.com/r-lib/processx/issues", "RoxygenNote": "7.1.1", "Imports": "ps (>= 1.2.0), R6, utils", "Suggests": "callr (>= 3.2.0), codetools, covr, crayon, curl, debugme,\nparallel, testthat, withr", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-09-03 12:21:42 UTC; gaborcsardi", "Author": "Gábor Csárdi [aut, cre, cph] (),\n Winston Chang [aut],\n RStudio [cph, fnd],\n Mango Solutions [cph, fnd]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2020-09-03 13:00:03 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sat, 05 Sep 2020 00:38:04 +0000\"; unix" } }, "progress": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "progress", "Title": "Terminal Progress Bars", "Version": "1.2.2", "Author": "Gábor Csárdi [aut, cre], Rich FitzJohn [aut]", "Maintainer": "Gábor Csárdi ", "Description": "Configurable Progress bars, they may include percentage,\n elapsed time, and/or the estimated completion time. They work in\n terminals, in 'Emacs' 'ESS', 'RStudio', 'Windows' 'Rgui' and the\n 'macOS' 'R.app'. The package also provides a 'C++' 'API', that works\n with or without 'Rcpp'.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/r-lib/progress#readme", "BugReports": "https://github.com/r-lib/progress/issues", "Imports": "hms, prettyunits, R6, crayon", "Suggests": "Rcpp, testthat, withr", "RoxygenNote": "6.1.0", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2019-05-15 20:28:47 UTC; gaborcsardi", "Repository": "CRAN", "Date/Publication": "2019-05-16 21:30:03 UTC", "Built": "R 4.0.0; ; \"Tue, 14 Apr 2020 00:19:29 +0000\"; unix" } }, "promises": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "promises", "Type": "Package", "Title": "Abstractions for Promise-Based Asynchronous Programming", "Version": "1.1.1", "Authors@R": "c(\n person(\"Joe\", \"Cheng\", email = \"joe@rstudio.com\", role = c(\"aut\", \"cre\")),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Description": "Provides fundamental abstractions for doing asynchronous programming\n in R using promises. Asynchronous programming is useful for allowing a single\n R process to orchestrate multiple tasks in the background while also attending\n to something else. Semantics are similar to 'JavaScript' promises, but with a\n syntax that is idiomatic R.", "License": "MIT + file LICENSE", "Imports": "R6, Rcpp, later, rlang, stats, magrittr", "Suggests": "testthat, future, knitr, rmarkdown", "LinkingTo": "later, Rcpp", "RoxygenNote": "7.1.0", "Encoding": "UTF-8", "LazyData": "true", "VignetteBuilder": "knitr", "URL": "https://rstudio.github.io/promises,\nhttps://github.com/rstudio/promises", "BugReports": "https://github.com/rstudio/promises/issues", "NeedsCompilation": "yes", "Packaged": "2020-06-09 21:23:18 UTC; winston", "Author": "Joe Cheng [aut, cre],\n RStudio [cph, fnd]", "Maintainer": "Joe Cheng ", "Repository": "CRAN", "Date/Publication": "2020-06-09 21:50:02 UTC", "Built": "R 4.0.1; x86_64-pc-linux-gnu; \"Wed, 10 Jun 2020 14:33:24 +0000\"; unix" } }, "ps": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "ps", "Version": "1.3.4", "Title": "List, Query, Manipulate System Processes", "Description": "List, query and manipulate all system processes, on 'Windows',\n 'Linux' and 'macOS'.", "Authors@R": "c(\n person(\"Jay\", \"Loden\", role = \"aut\"),\n person(\"Dave\", \"Daeschler\", role = \"aut\"),\n person(\"Giampaolo\", \"Rodola'\", role = \"aut\"),\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", c(\"aut\", \"cre\")),\n person(\"RStudio\", role = \"cph\"))", "License": "BSD_3_clause + file LICENSE", "URL": "https://github.com/r-lib/ps#readme", "BugReports": "https://github.com/r-lib/ps/issues", "Encoding": "UTF-8", "Depends": "R (>= 3.1)", "Imports": "utils", "Suggests": "callr, covr, curl, pingr, processx (>= 3.1.0), R6, rlang,\ntestthat, tibble", "RoxygenNote": "7.0.2.9000", "Biarch": "true", "NeedsCompilation": "yes", "Packaged": "2020-08-11 10:42:55 UTC; gaborcsardi", "Author": "Jay Loden [aut],\n Dave Daeschler [aut],\n Giampaolo Rodola' [aut],\n Gábor Csárdi [aut, cre],\n RStudio [cph]", "Maintainer": "Gábor Csárdi ", "Repository": "CRAN", "Date/Publication": "2020-08-11 14:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Wed, 12 Aug 2020 13:04:30 +0000\"; unix" } }, "purrr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "purrr", "Title": "Functional Programming Tools", "Version": "0.3.4", "Authors@R": "\n c(person(given = \"Lionel\",\n family = \"Henry\",\n role = c(\"aut\", \"cre\"),\n email = \"lionel@rstudio.com\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "A complete and consistent functional programming\n toolkit for R.", "License": "GPL-3 | file LICENSE", "URL": "http://purrr.tidyverse.org, https://github.com/tidyverse/purrr", "BugReports": "https://github.com/tidyverse/purrr/issues", "Depends": "R (>= 3.2)", "Imports": "magrittr (>= 1.5), rlang (>= 0.3.1)", "Suggests": "covr, crayon, dplyr (>= 0.7.8), knitr, rmarkdown, testthat,\ntibble, tidyselect", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "NeedsCompilation": "yes", "Packaged": "2020-04-16 21:54:48 UTC; hadley", "Author": "Lionel Henry [aut, cre],\n Hadley Wickham [aut],\n RStudio [cph, fnd]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-04-17 12:10:07 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 20 Apr 2020 01:21:15 +0000\"; unix" } }, "raster": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "raster", "Type": "Package", "Title": "Geographic Data Analysis and Modeling", "Version": "3.3-13", "Date": "2020-07-16", "Depends": "sp (>= 1.4.1), R (>= 3.5.0)", "Suggests": "rgdal (>= 1.5-8), rgeos (>= 0.3-8), ncdf4, igraph, tcltk,\nparallel, rasterVis, MASS, sf, tinytest, gstat, fields", "LinkingTo": "Rcpp", "Imports": "Rcpp, methods", "SystemRequirements": "C++11", "Description": "Reading, writing, manipulating, analyzing and modeling of spatial data. The package implements basic and high-level functions for raster data and for vector data operations such as intersections. See the manual and tutorials on to get started.", "License": "GPL (>= 3)", "URL": "https://rspatial.org/raster", "BugReports": "https://github.com/rspatial/raster/issues/", "Authors@R": "c(\n\tperson(\"Robert J.\", \"Hijmans\", role = c(\"cre\", \"aut\"), \n\t\t\temail = \"r.hijmans@gmail.com\", \n\t\t\tcomment = c(ORCID = \"0000-0001-5872-2872\")),\n\tperson(\"Jacob\", \"van Etten\", role = \"ctb\"),\n\tperson(\"Michael\", \"Sumner\", role = \"ctb\"),\n\tperson(\"Joe\", \"Cheng\", role = \"ctb\"),\n\tperson(\"Dan\", \"Baston\", role = \"ctb\"),\n\tperson(\"Andrew\", \"Bevan\", role = \"ctb\"),\n\tperson(\"Roger\", \"Bivand\", role = \"ctb\"),\n\tperson(\"Lorenzo\", \"Busetto\", role = \"ctb\"),\n\tperson(\"Mort\", \"Canty\", role = \"ctb\"),\n\tperson(\"David\", \"Forrest\", role = \"ctb\"),\n\tperson(\"Aniruddha\", \"Ghosh\", role = \"ctb\"),\n\tperson(\"Duncan\", \"Golicher\", role = \"ctb\"),\n\tperson(\"Josh\", \"Gray\", role = \"ctb\"),\n\tperson(\"Jonathan A.\", \"Greenberg\", role = \"ctb\"),\n\tperson(\"Paul\", \"Hiemstra\", role = \"ctb\"),\n\tperson(\"Kassel\", \"Hingee\", role = \"ctb\"),\n\tperson(\"Institute for Mathematics Applied Geosciences\", role=\"cph\"),\n\tperson(\"Charles\", \"Karney\", role = \"ctb\"),\n\tperson(\"Matteo\", \"Mattiuzzi\", role = \"ctb\"),\n\tperson(\"Steven\", \"Mosher\", role = \"ctb\"),\n\tperson(\"Jakub\", \"Nowosad\", role = \"ctb\"),\n\tperson(\"Edzer\", \"Pebesma\", role = \"ctb\"),\n\tperson(\"Oscar\", \"Perpinan Lamigueiro\", role = \"ctb\"),\n\tperson(\"Etienne B.\", \"Racine\", role = \"ctb\"),\n\tperson(\"Barry\", \"Rowlingson\", role = \"ctb\"),\n\tperson(\"Ashton\", \"Shortridge\", role = \"ctb\"),\n\tperson(\"Bill\", \"Venables\", role = \"ctb\"),\n\tperson(\"Rafael\", \"Wueest\", role = \"ctb\")\n\t)", "NeedsCompilation": "yes", "Packaged": "2020-07-17 03:11:36 UTC; rhijm", "Author": "Robert J. Hijmans [cre, aut] (),\n Jacob van Etten [ctb],\n Michael Sumner [ctb],\n Joe Cheng [ctb],\n Dan Baston [ctb],\n Andrew Bevan [ctb],\n Roger Bivand [ctb],\n Lorenzo Busetto [ctb],\n Mort Canty [ctb],\n David Forrest [ctb],\n Aniruddha Ghosh [ctb],\n Duncan Golicher [ctb],\n Josh Gray [ctb],\n Jonathan A. Greenberg [ctb],\n Paul Hiemstra [ctb],\n Kassel Hingee [ctb],\n Institute for Mathematics Applied Geosciences [cph],\n Charles Karney [ctb],\n Matteo Mattiuzzi [ctb],\n Steven Mosher [ctb],\n Jakub Nowosad [ctb],\n Edzer Pebesma [ctb],\n Oscar Perpinan Lamigueiro [ctb],\n Etienne B. Racine [ctb],\n Barry Rowlingson [ctb],\n Ashton Shortridge [ctb],\n Bill Venables [ctb],\n Rafael Wueest [ctb]", "Maintainer": "Robert J. Hijmans ", "Repository": "CRAN", "Date/Publication": "2020-07-17 06:40:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:36:01 UTC; unix" } }, "readr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "readr", "Version": "1.3.1", "Title": "Read Rectangular Text Data", "Description": "The goal of 'readr' is to provide a fast and friendly way to read\n rectangular data (like 'csv', 'tsv', and 'fwf'). It is designed to flexibly\n parse many types of data found in the wild, while still cleanly failing when\n data unexpectedly changes.", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", \"aut\"),\n person(\"Jim\", \"Hester\", , \"james.hester@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"Romain\", \"Francois\", role = \"aut\"),\n person(\"R Core Team\", role = \"ctb\", comment = \"Date time code adapted from R\"),\n person(\"RStudio\", role = c(\"cph\", \"fnd\")),\n person(\"Jukka\", \"Jylänki\", role = c(\"ctb\", \"cph\"), comment = \"grisu3 implementation\"),\n person(\"Mikkel\", \"Jørgensen\", role = c(\"ctb\", \"cph\"), comment = \"grisu3 implementation\"))", "Encoding": "UTF-8", "Depends": "R (>= 3.1)", "LinkingTo": "Rcpp, BH", "Imports": "Rcpp (>= 0.12.0.5), tibble, hms (>= 0.4.1), R6, clipr, crayon,\nmethods", "Suggests": "curl, testthat, knitr, rmarkdown, stringi, covr, spelling", "License": "GPL (>= 2) | file LICENSE", "BugReports": "https://github.com/tidyverse/readr/issues", "URL": "http://readr.tidyverse.org, https://github.com/tidyverse/readr", "VignetteBuilder": "knitr", "RoxygenNote": "6.1.1", "SystemRequirements": "GNU make", "Language": "en-US", "NeedsCompilation": "yes", "Packaged": "2018-12-20 16:06:40 UTC; jhester", "Author": "Hadley Wickham [aut],\n Jim Hester [aut, cre],\n Romain Francois [aut],\n R Core Team [ctb] (Date time code adapted from R),\n RStudio [cph, fnd],\n Jukka Jylänki [ctb, cph] (grisu3 implementation),\n Mikkel Jørgensen [ctb, cph] (grisu3 implementation)", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2018-12-21 09:40:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sat, 18 Apr 2020 16:13:49 +0000\"; unix" } }, "readxl": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "readxl", "Title": "Read Excel Files", "Version": "1.3.1", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\",\n comment = c(ORCID = \"0000-0003-4757-117X\")),\n person(given = \"Jennifer\",\n family = \"Bryan\",\n role = c(\"aut\", \"cre\"),\n email = \"jenny@rstudio.com\",\n comment = c(ORCID = \"0000-0002-6983-2759\")),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\"),\n comment = \"Copyright holder of all R code and all C/C++ code without explicit copyright attribution\"),\n person(given = \"Marcin\",\n family = \"Kalicinski\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included RapidXML code\"),\n person(given = \"Komarov Valery\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included libxls code\"),\n person(given = \"Christophe Leitienne\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included libxls code\"),\n person(given = \"Bob Colbert\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included libxls code\"),\n person(given = \"David Hoerl\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included libxls code\"),\n person(given = \"Evan Miller\",\n role = c(\"ctb\", \"cph\"),\n comment = \"Author of included libxls code\"))", "Description": "Import excel files into R. Supports '.xls' via the\n embedded 'libxls' C library and\n '.xlsx' via the embedded 'RapidXML' C++ library\n . Works on Windows, Mac and Linux\n without external dependencies.", "License": "GPL-3", "URL": "https://readxl.tidyverse.org, https://github.com/tidyverse/readxl", "BugReports": "https://github.com/tidyverse/readxl/issues", "Imports": "cellranger, Rcpp (>= 0.12.18), tibble (>= 1.3.1), utils", "Suggests": "covr, knitr, rmarkdown, rprojroot (>= 1.1), testthat", "LinkingTo": "progress, Rcpp", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "Note": "libxls-SHA cef1393", "RoxygenNote": "6.1.1", "NeedsCompilation": "yes", "Packaged": "2019-03-13 16:01:23 UTC; jenny", "Author": "Hadley Wickham [aut] (),\n Jennifer Bryan [aut, cre] (),\n RStudio [cph, fnd] (Copyright holder of all R code and all C/C++ code\n without explicit copyright attribution),\n Marcin Kalicinski [ctb, cph] (Author of included RapidXML code),\n Komarov Valery [ctb, cph] (Author of included libxls code),\n Christophe Leitienne [ctb, cph] (Author of included libxls code),\n Bob Colbert [ctb, cph] (Author of included libxls code),\n David Hoerl [ctb, cph] (Author of included libxls code),\n Evan Miller [ctb, cph] (Author of included libxls code)", "Maintainer": "Jennifer Bryan ", "Repository": "CRAN", "Date/Publication": "2019-03-13 16:30:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Sat, 18 Apr 2020 16:52:43 +0000\"; unix" } }, "rematch": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rematch", "Title": "Match Regular Expressions with a Nicer 'API'", "Version": "1.0.1", "Author": "Gabor Csardi", "Maintainer": "Gabor Csardi ", "Description": "A small wrapper on 'regexpr' to extract the matches and\n captured groups from the match of a regular expression to a character\n vector.", "License": "MIT + file LICENSE", "LazyData": "true", "URL": "https://github.com/MangoTheCat/rematch", "BugReports": "https://github.com/MangoTheCat/rematch/issues", "RoxygenNote": "5.0.1.9000", "Suggests": "covr, testthat", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2016-04-20 19:54:37 UTC; gaborcsardi", "Repository": "CRAN", "Date/Publication": "2016-04-21 08:20:46", "Built": "R 3.5.0; ; \"Tue, 15 May 2018 02:20:16 +0000\"; unix" } }, "reprex": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "reprex", "Title": "Prepare Reproducible Example Code via the Clipboard", "Version": "0.3.0", "Authors@R": "c(\n person(\"Jennifer\", \"Bryan\", , \"jenny@rstudio.com\", role = c(\"aut\", \"cre\"),\n comment = c(ORCID = \"0000-0002-6983-2759\")\n ),\n person(\"Jim\", \"Hester\", , \"james.hester@rstudio.com\", role = \"aut\",\n comment = c(ORCID = \"0000-0002-2739-7082\")\n ),\n person(\"David\", \"Robinson\", , \"admiral.david@gmail.com\", role = \"aut\"),\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", role = \"aut\",\n comment = c(ORCID = \"0000-0003-4757-117X\")\n ),\n person(\"RStudio\", role = c(\"cph\", \"fnd\"))\n )", "Description": "Convenience wrapper that uses the 'rmarkdown' package to render\n small snippets of code to target formats that include both code and output.\n The goal is to encourage the sharing of small, reproducible, and runnable\n examples on code-oriented websites, such as and\n , or in email. The user's clipboard is the default source\n of input code and the default target for rendered output. 'reprex' also\n extracts clean, runnable R code from various common formats, such as\n copy/paste from an R session. ", "License": "MIT + file LICENSE", "URL": "https://reprex.tidyverse.org,\nhttps://github.com/tidyverse/reprex#readme", "BugReports": "https://github.com/tidyverse/reprex/issues", "Depends": "R (>= 3.1)", "Imports": "callr (>= 2.0.0), clipr (>= 0.4.0), fs, rlang, rmarkdown,\nutils, whisker, withr", "Suggests": "covr, devtools, fortunes, knitr, miniUI, rprojroot,\nrstudioapi, shiny, styler (>= 1.0.2), testthat (>= 2.0.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "6.1.1", "SystemRequirements": "pandoc (>= 1.12.3) - http://pandoc.org", "NeedsCompilation": "no", "Packaged": "2019-05-16 13:41:23 UTC; jenny", "Author": "Jennifer Bryan [aut, cre] (),\n Jim Hester [aut] (),\n David Robinson [aut],\n Hadley Wickham [aut] (),\n RStudio [cph, fnd]", "Maintainer": "Jennifer Bryan ", "Repository": "CRAN", "Date/Publication": "2019-05-16 16:20:05 UTC", "Built": "R 4.0.0; ; \"Wed, 22 Apr 2020 14:40:35 +0000\"; unix" } }, "rlang": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rlang", "Version": "0.4.7", "Title": "Functions for Base Types and Core R and 'Tidyverse' Features", "Description": "A toolbox for working with base types, core R features\n like the condition system, and core 'Tidyverse' features like tidy\n evaluation.", "Authors@R": "c(\n person(\"Lionel\", \"Henry\", ,\"lionel@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", ,\"hadley@rstudio.com\", \"aut\"),\n person(\"RStudio\", role = \"cph\")\n )", "License": "GPL-3", "LazyData": "true", "ByteCompile": "true", "Biarch": "true", "Depends": "R (>= 3.2.0)", "Suggests": "cli, covr, crayon, glue, magrittr, methods, pillar,\nrmarkdown, testthat (>= 2.3.0), vctrs (>= 0.2.3), withr", "Encoding": "UTF-8", "RoxygenNote": "7.1.1", "URL": "http://rlang.r-lib.org, https://github.com/r-lib/rlang", "BugReports": "https://github.com/r-lib/rlang/issues", "NeedsCompilation": "yes", "Packaged": "2020-07-09 12:06:27 UTC; lionel", "Author": "Lionel Henry [aut, cre],\n Hadley Wickham [aut],\n RStudio [cph]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-07-09 23:00:18 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Fri, 10 Jul 2020 12:54:08 +0000\"; unix" } }, "rmarkdown": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rmarkdown", "Type": "Package", "Title": "Dynamic Documents for R", "Version": "2.3", "Authors@R": "c(\n person(\"JJ\", \"Allaire\", role = \"aut\", email = \"jj@rstudio.com\"),\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Jonathan\", \"McPherson\", role = \"aut\", email = \"jonathan@rstudio.com\"),\n person(\"Javier\", \"Luraschi\", role = \"aut\", email = \"javier@rstudio.com\"),\n person(\"Kevin\", \"Ushey\", role = \"aut\", email = \"kevin@rstudio.com\"),\n person(\"Aron\", \"Atkins\", role = \"aut\", email = \"aron@rstudio.com\"),\n person(\"Hadley\", \"Wickham\", role = \"aut\", email = \"hadley@rstudio.com\"),\n person(\"Joe\", \"Cheng\", role = \"aut\", email = \"joe@rstudio.com\"),\n person(\"Winston\", \"Chang\", role = \"aut\", email = \"winston@rstudio.com\"),\n person(\"Richard\", \"Iannone\", role = \"aut\", email = \"rich@rstudio.com\", comment = c(ORCID = \"0000-0003-3925-190X\")),\n #\n # Contributors, ordered alphabetically by first name\n person(\"Andrew\", \"Dunning\", role = \"ctb\", comment = c(ORCID = \"0000-0003-0464-5036\")),\n person(\"Atsushi\", \"Yasumoto\", role = \"ctb\", comment = c(ORCID = \"0000-0002-8335-495X\")),\n person(\"Barret\", \"Schloerke\", role = \"ctb\"),\n person(\"Christophe\", \"Dervieux\", role = \"ctb\"),\n person(\"Frederik\", \"Aust\", role = \"ctb\", email = \"frederik.aust@uni-koeln.de\", comment = c(ORCID = \"0000-0003-4900-788X\")),\n person(\"Jeff\", \"Allen\", role = \"ctb\", email = \"jeff@rstudio.com\"),\n person(\"JooYoung\", \"Seo\", role=\"ctb\", comment = c(ORCID = \"0000-0002-4064-6012\")),\n person(\"Malcolm\", \"Barrett\", role = \"ctb\"),\n person(\"Rob\", \"Hyndman\", role = \"ctb\", email = \"Rob.Hyndman@monash.edu\"),\n person(\"Romain\", \"Lesur\", role = \"ctb\"),\n person(\"Roy\", \"Storey\", role = \"ctb\"),\n person(\"Ruben\", \"Arslan\", role = \"ctb\", email = \"ruben.arslan@uni-goettingen.de\"),\n person(\"Sergio\", \"Oller\", role = \"ctb\"),\n #\n # Copyright holders\n person(family = \"RStudio, PBC\", role = \"cph\"),\n person(family = \"jQuery Foundation\", role = \"cph\",\n comment = \"jQuery library\"),\n person(family = \"jQuery contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery library; authors listed in inst/rmd/h/jquery-AUTHORS.txt\"),\n person(family = \"jQuery UI contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery UI library; authors listed in inst/rmd/h/jqueryui-AUTHORS.txt\"),\n person(\"Mark\", \"Otto\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(\"Jacob\", \"Thornton\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Bootstrap contributors\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Twitter, Inc\", role = \"cph\",\n comment = \"Bootstrap library\"),\n person(\"Alexander\", \"Farkas\", role = c(\"ctb\", \"cph\"),\n comment = \"html5shiv library\"),\n person(\"Scott\", \"Jehl\", role = c(\"ctb\", \"cph\"),\n comment = \"Respond.js library\"),\n person(\"Ivan\", \"Sagalaev\", role = c(\"ctb\", \"cph\"),\n comment = \"highlight.js library\"),\n person(\"Greg\", \"Franko\", role = c(\"ctb\", \"cph\"),\n comment = \"tocify library\"),\n person(\"John\", \"MacFarlane\", role = c(\"ctb\", \"cph\"),\n comment = \"Pandoc templates\"),\n person(family = \"Google, Inc.\", role = c(\"ctb\", \"cph\"),\n comment = \"ioslides library\"),\n person(\"Dave\", \"Raggett\", role = \"ctb\",\n comment = \"slidy library\"),\n person(family = \"W3C\", role = \"cph\",\n comment = \"slidy library\"),\n person(\"Dave\", \"Gandy\", role = c(\"ctb\", \"cph\"),\n comment = \"Font-Awesome\"),\n person(\"Ben\", \"Sperry\", role = \"ctb\",\n comment = \"Ionicons\"),\n person(family = \"Drifty\", role = \"cph\",\n comment = \"Ionicons\"),\n person(\"Aidan\", \"Lister\", role = c(\"ctb\", \"cph\"), \n comment = \"jQuery StickyTabs\"),\n person(\"Benct Philip\", \"Jonsson\", role = c(\"ctb\", \"cph\"), \n comment = \"pagebreak lua filter\"),\n person(\"Albert\", \"Krewinkel\", role = c(\"ctb\", \"cph\"), \n comment = \"pagebreak lua filter\")\n )", "Maintainer": "Yihui Xie ", "Description": "Convert R Markdown documents into a variety of formats.", "Depends": "R (>= 3.0)", "Imports": "tools, utils, knitr (>= 1.22), yaml (>= 2.1.19), htmltools (>=\n0.3.5), evaluate (>= 0.13), base64enc, jsonlite, mime, tinytex\n(>= 0.11), xfun, methods, stringr (>= 1.2.0)", "Suggests": "shiny (>= 0.11), tufte, testthat, digest, dygraphs, tibble,\nfs, pkgdown, rsconnect", "SystemRequirements": "pandoc (>= 1.12.3) - http://pandoc.org", "URL": "https://github.com/rstudio/rmarkdown", "BugReports": "https://github.com/rstudio/rmarkdown/issues", "License": "GPL-3", "RoxygenNote": "7.1.0", "Encoding": "UTF-8", "VignetteBuilder": "knitr", "NeedsCompilation": "no", "Packaged": "2020-06-18 13:32:36 UTC; yihui", "Author": "JJ Allaire [aut],\n Yihui Xie [aut, cre] (),\n Jonathan McPherson [aut],\n Javier Luraschi [aut],\n Kevin Ushey [aut],\n Aron Atkins [aut],\n Hadley Wickham [aut],\n Joe Cheng [aut],\n Winston Chang [aut],\n Richard Iannone [aut] (),\n Andrew Dunning [ctb] (),\n Atsushi Yasumoto [ctb] (),\n Barret Schloerke [ctb],\n Christophe Dervieux [ctb],\n Frederik Aust [ctb] (),\n Jeff Allen [ctb],\n JooYoung Seo [ctb] (),\n Malcolm Barrett [ctb],\n Rob Hyndman [ctb],\n Romain Lesur [ctb],\n Roy Storey [ctb],\n Ruben Arslan [ctb],\n Sergio Oller [ctb],\n RStudio, PBC [cph],\n jQuery Foundation [cph] (jQuery library),\n jQuery contributors [ctb, cph] (jQuery library; authors listed in\n inst/rmd/h/jquery-AUTHORS.txt),\n jQuery UI contributors [ctb, cph] (jQuery UI library; authors listed in\n inst/rmd/h/jqueryui-AUTHORS.txt),\n Mark Otto [ctb] (Bootstrap library),\n Jacob Thornton [ctb] (Bootstrap library),\n Bootstrap contributors [ctb] (Bootstrap library),\n Twitter, Inc [cph] (Bootstrap library),\n Alexander Farkas [ctb, cph] (html5shiv library),\n Scott Jehl [ctb, cph] (Respond.js library),\n Ivan Sagalaev [ctb, cph] (highlight.js library),\n Greg Franko [ctb, cph] (tocify library),\n John MacFarlane [ctb, cph] (Pandoc templates),\n Google, Inc. [ctb, cph] (ioslides library),\n Dave Raggett [ctb] (slidy library),\n W3C [cph] (slidy library),\n Dave Gandy [ctb, cph] (Font-Awesome),\n Ben Sperry [ctb] (Ionicons),\n Drifty [cph] (Ionicons),\n Aidan Lister [ctb, cph] (jQuery StickyTabs),\n Benct Philip Jonsson [ctb, cph] (pagebreak lua filter),\n Albert Krewinkel [ctb, cph] (pagebreak lua filter)", "Repository": "CRAN", "Date/Publication": "2020-06-18 14:50:02 UTC", "Built": "R 4.0.1; ; \"Fri, 19 Jun 2020 13:48:46 +0000\"; unix" } }, "rprojroot": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rprojroot", "Title": "Finding Files in Project Subdirectories", "Version": "1.3-2", "Authors@R": "person(given = \"Kirill\", family = \"Müller\", role = c(\"aut\",\n \"cre\"), email = \"krlmlr+r@mailbox.org\")", "Description": "Robust, reliable and flexible paths to files below a\n project root. The 'root' of a project is defined as a directory\n that matches a certain criterion, e.g., it contains a certain\n regular file.", "Depends": "R (>= 3.0.0)", "Imports": "backports", "Suggests": "testthat, mockr, knitr, withr, rmarkdown", "VignetteBuilder": "knitr", "License": "GPL-3", "LazyData": "true", "Encoding": "UTF-8", "URL": "https://github.com/krlmlr/rprojroot,\nhttps://krlmlr.github.io/rprojroot", "BugReports": "https://github.com/krlmlr/rprojroot/issues", "RoxygenNote": "6.0.1", "Collate": "'rrmake.R' 'criterion.R' 'file.R' 'has-file.R' 'root.R'\n'rprojroot-package.R' 'shortcut.R' 'thisfile.R'", "NeedsCompilation": "no", "Packaged": "2018-01-03 13:27:15 UTC; muelleki", "Author": "Kirill Müller [aut, cre]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2018-01-03 15:36:24 UTC", "Built": "R 4.0.0; ; \"Wed, 15 Apr 2020 03:57:45 +0000\"; unix" } }, "rstudioapi": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rstudioapi", "Title": "Safely Access the RStudio API", "Description": "Access the RStudio API (if available) and provide informative error\n messages when it's not.", "Version": "0.11", "Authors@R": "c(\n person(\"Kevin\", \"Ushey\", role = c(\"aut\", \"cre\"), email = \"kevin@rstudio.com\"),\n person(\"JJ\", \"Allaire\", role = c(\"aut\"), email = \"jj@rstudio.com\"),\n person(\"Hadley\", \"Wickham\", role = c(\"aut\"), email = \"hadley@rstudio.com\"),\n person(\"Gary\", \"Ritchie\", role = c(\"aut\"), email = \"gary@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\")\n )", "Maintainer": "Kevin Ushey ", "License": "MIT + file LICENSE", "URL": "https://github.com/rstudio/rstudioapi", "BugReports": "https://github.com/rstudio/rstudioapi/issues", "RoxygenNote": "7.0.2", "Suggests": "testthat, knitr, rmarkdown, clipr", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-02-07 18:58:10 UTC; kevinushey", "Author": "Kevin Ushey [aut, cre],\n JJ Allaire [aut],\n Hadley Wickham [aut],\n Gary Ritchie [aut],\n RStudio [cph]", "Repository": "CRAN", "Date/Publication": "2020-02-07 23:20:02 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 20:57:26 +0000\"; unix" } }, "rvest": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "rvest", "Title": "Easily Harvest (Scrape) Web Pages", "Version": "0.3.6", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Wrappers around the 'xml2' and 'httr' packages to\n make it easy to download, then manipulate, HTML and XML.", "License": "GPL-3", "URL": "http://rvest.tidyverse.org/, https://github.com/tidyverse/rvest", "BugReports": "https://github.com/tidyverse/rvest/issues", "Depends": "R (>= 3.2), xml2", "Imports": "httr (>= 0.5), magrittr, selectr", "Suggests": "covr, knitr, png, rmarkdown, spelling, stringi (>= 0.3.1),\ntestthat", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "Language": "en-US", "LazyData": "true", "RoxygenNote": "7.1.1", "NeedsCompilation": "no", "Packaged": "2020-07-20 14:06:08 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-07-25 21:50:02 UTC", "Built": "R 4.0.2; ; \"Sun, 26 Jul 2020 16:08:30 +0000\"; unix" } }, "scales": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "scales", "Title": "Scale Functions for Visualization", "Version": "1.1.1", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"Dana\",\n family = \"Seidel\",\n role = \"aut\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Graphical scales map data to aesthetics, and\n provide methods for automatically determining breaks and labels for\n axes and legends.", "License": "MIT + file LICENSE", "URL": "https://scales.r-lib.org, https://github.com/r-lib/scales", "BugReports": "https://github.com/r-lib/scales/issues", "Depends": "R (>= 3.2)", "Imports": "farver (>= 2.0.3), labeling, lifecycle, munsell (>= 0.5), R6,\nRColorBrewer, viridisLite", "Suggests": "bit64, covr, dichromat, ggplot2, hms (>= 0.5.0), testthat (>=\n2.1.0)", "Encoding": "UTF-8", "LazyLoad": "yes", "RoxygenNote": "7.1.0", "NeedsCompilation": "no", "Packaged": "2020-05-11 14:46:40 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n Dana Seidel [aut],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-05-11 23:20:05 UTC", "Built": "R 4.0.0; ; \"Tue, 12 May 2020 14:45:45 +0000\"; unix" } }, "selectr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "selectr", "Type": "Package", "Title": "Translate CSS Selectors to XPath Expressions", "Version": "0.4-2", "Date": "2019-11-20", "Authors@R": "c(person(\"Simon\", \"Potter\",\n role = c(\"aut\", \"trl\", \"cre\"),\n email = \"simon@sjp.co.nz\"),\n person(\"Simon\", \"Sapin\", role = \"aut\"),\n person(\"Ian\", \"Bicking\", role = \"aut\"))", "License": "BSD_3_clause + file LICENCE", "Depends": "R (>= 3.0)", "Imports": "methods, stringr, R6", "Suggests": "testthat, XML, xml2", "URL": "https://sjp.co.nz/projects/selectr", "BugReports": "https://github.com/sjp/selectr/issues", "Description": "Translates a CSS3 selector into an equivalent XPath\n expression. This allows us to use CSS selectors when working with\n the XML package as it can only evaluate XPath expressions. Also\n provided are convenience functions useful for using CSS selectors on\n XML nodes. This package is a port of the Python package 'cssselect'\n ().", "NeedsCompilation": "no", "Packaged": "2019-11-20 06:04:49 UTC; simon", "Author": "Simon Potter [aut, trl, cre],\n Simon Sapin [aut],\n Ian Bicking [aut]", "Maintainer": "Simon Potter ", "Repository": "CRAN", "Date/Publication": "2019-11-20 07:30:03 UTC", "Built": "R 4.0.0; ; \"Sun, 19 Apr 2020 04:51:33 +0000\"; unix" } }, "sf": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "sf", "Version": "0.9-6", "Title": "Simple Features for R", "Authors@R": "\n c(person(given = \"Edzer\",\n family = \"Pebesma\",\n role = c(\"aut\", \"cre\"),\n email = \"edzer.pebesma@uni-muenster.de\",\n comment = c(ORCID = \"0000-0001-8049-7069\")),\n person(given = \"Roger\",\n family = \"Bivand\",\n role = \"ctb\",\n comment = c(ORCID = \"0000-0003-2392-6140\")),\n person(given = \"Etienne\",\n family = \"Racine\",\n role = \"ctb\"),\n person(given = \"Michael\",\n family = \"Sumner\",\n role = \"ctb\"),\n person(given = \"Ian\",\n family = \"Cook\",\n role = \"ctb\"),\n person(given = \"Tim\",\n family = \"Keitt\",\n role = \"ctb\"),\n person(given = \"Robin\",\n family = \"Lovelace\",\n role = \"ctb\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"ctb\"),\n person(given = \"Jeroen\",\n family = \"Ooms\",\n role = \"ctb\",\n comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(given = \"Kirill\",\n family = \"M\\u00fcller\",\n role = \"ctb\"),\n person(given = \"Thomas Lin\",\n family = \"Pedersen\",\n role = \"ctb\"),\n person(given = \"Dan\",\n family = \"Baston\",\n role = \"ctb\"))", "Description": "Support for simple features, a standardized way to\n encode spatial vector data. Binds to 'GDAL' for reading and writing\n data, to 'GEOS' for geometrical operations, and to 'PROJ' for\n projection conversions and datum transformations. Optionally uses the 's2'\n package for spherical geometry operations on geographic coordinates.", "License": "GPL-2 | MIT + file LICENSE", "URL": "https://r-spatial.github.io/sf/, https://github.com/r-spatial/sf/", "BugReports": "https://github.com/r-spatial/sf/issues/", "Depends": "methods, R (>= 3.3.0)", "Imports": "classInt (>= 0.4-1), DBI (>= 0.8), graphics, grDevices, grid,\nmagrittr, Rcpp (>= 0.12.18), stats, tools, units (>= 0.6-0),\nutils", "Suggests": "blob, covr, dplyr (>= 0.8-3), ggplot2, knitr, lwgeom (>=\n0.2-1), maps, mapview, microbenchmark, odbc, pillar, pool,\nraster, rgdal, rgeos, rlang, rmarkdown, RPostgres (>= 1.1.0),\nRPostgreSQL, RSQLite, s2 (>= 1.0.1), sp (>= 1.2-4), spatstat,\nspatstat.utils, stars (>= 0.2-0), testthat, tibble (>= 1.4.1),\ntidyr (>= 1.0-0), tidyselect (>= 1.0.0), tmap (>= 2.0), vctrs", "LinkingTo": "Rcpp", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "RoxygenNote": "7.1.1", "SystemRequirements": "C++11, GDAL (>= 2.0.1), GEOS (>= 3.4.0), PROJ (>=\n4.8.0)", "Collate": "'RcppExports.R' 'init.R' 'crs.R' 'bbox.R' 'read.R' 'db.R'\n'sfc.R' 'sfg.R' 'sf.R' 'bind.R' 'wkb.R' 'wkt.R' 'plot.R'\n'geom-measures.R' 'geom-predicates.R' 'geom-transformers.R'\n'transform.R' 'sp.R' 'grid.R' 'arith.R' 'tidyverse.R'\n'tidyverse-vctrs.R' 'cast_sfg.R' 'cast_sfc.R' 'graticule.R'\n'datasets.R' 'aggregate.R' 'agr.R' 'maps.R' 'join.R' 'sample.R'\n'valid.R' 'collection_extract.R' 'jitter.R' 'sgbp.R'\n'spatstat.R' 'stars.R' 'crop.R' 'gdal_utils.R' 'nearest.R'\n'normalize.R' 'defunct.R' 'z_range.R' 'm_range.R'\n'shift_longitude.R' 'make_grid.R' 's2.R'", "NeedsCompilation": "yes", "Packaged": "2020-09-11 14:23:29 UTC; edzer", "Author": "Edzer Pebesma [aut, cre] (),\n Roger Bivand [ctb] (),\n Etienne Racine [ctb],\n Michael Sumner [ctb],\n Ian Cook [ctb],\n Tim Keitt [ctb],\n Robin Lovelace [ctb],\n Hadley Wickham [ctb],\n Jeroen Ooms [ctb] (),\n Kirill Müller [ctb],\n Thomas Lin Pedersen [ctb],\n Dan Baston [ctb]", "Maintainer": "Edzer Pebesma ", "Repository": "CRAN", "Date/Publication": "2020-09-13 15:00:03 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-09-15 08:31:30 UTC; unix" } }, "shiny": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "shiny", "Type": "Package", "Title": "Web Application Framework for R", "Version": "1.5.0", "Authors@R": "c(\n person(\"Winston\", \"Chang\", role = c(\"aut\", \"cre\"), email = \"winston@rstudio.com\"),\n person(\"Joe\", \"Cheng\", role = \"aut\", email = \"joe@rstudio.com\"),\n person(\"JJ\", \"Allaire\", role = \"aut\", email = \"jj@rstudio.com\"),\n person(\"Yihui\", \"Xie\", role = \"aut\", email = \"yihui@rstudio.com\"),\n person(\"Jonathan\", \"McPherson\", role = \"aut\", email = \"jonathan@rstudio.com\"),\n person(family = \"RStudio\", role = \"cph\"),\n person(family = \"jQuery Foundation\", role = \"cph\",\n comment = \"jQuery library and jQuery UI library\"),\n person(family = \"jQuery contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt\"),\n person(family = \"jQuery UI contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt\"),\n person(\"Mark\", \"Otto\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(\"Jacob\", \"Thornton\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Bootstrap contributors\", role = \"ctb\",\n comment = \"Bootstrap library\"),\n person(family = \"Twitter, Inc\", role = \"cph\",\n comment = \"Bootstrap library\"),\n person(\"Alexander\", \"Farkas\", role = c(\"ctb\", \"cph\"),\n comment = \"html5shiv library\"),\n person(\"Scott\", \"Jehl\", role = c(\"ctb\", \"cph\"),\n comment = \"Respond.js library\"),\n person(\"Stefan\", \"Petre\", role = c(\"ctb\", \"cph\"),\n comment = \"Bootstrap-datepicker library\"),\n person(\"Andrew\", \"Rowls\", role = c(\"ctb\", \"cph\"),\n comment = \"Bootstrap-datepicker library\"),\n person(\"Dave\", \"Gandy\", role = c(\"ctb\", \"cph\"),\n comment = \"Font-Awesome font\"),\n person(\"Brian\", \"Reavis\", role = c(\"ctb\", \"cph\"),\n comment = \"selectize.js library\"),\n person(\"Kristopher Michael\", \"Kowal\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(family = \"es5-shim contributors\", role = c(\"ctb\", \"cph\"),\n comment = \"es5-shim library\"),\n person(\"Denis\", \"Ineshin\", role = c(\"ctb\", \"cph\"),\n comment = \"ion.rangeSlider library\"),\n person(\"Sami\", \"Samhuri\", role = c(\"ctb\", \"cph\"),\n comment = \"Javascript strftime library\"),\n person(family = \"SpryMedia Limited\", role = c(\"ctb\", \"cph\"),\n comment = \"DataTables library\"),\n person(\"John\", \"Fraser\", role = c(\"ctb\", \"cph\"),\n comment = \"showdown.js library\"),\n person(\"John\", \"Gruber\", role = c(\"ctb\", \"cph\"),\n comment = \"showdown.js library\"),\n person(\"Ivan\", \"Sagalaev\", role = c(\"ctb\", \"cph\"),\n comment = \"highlight.js library\"),\n person(family = \"R Core Team\", role = c(\"ctb\", \"cph\"),\n comment = \"tar implementation from R\")\n )", "Description": "Makes it incredibly easy to build interactive web\n applications with R. Automatic \"reactive\" binding between inputs and\n outputs and extensive prebuilt widgets make it possible to build\n beautiful, responsive, and powerful applications with minimal effort.", "License": "GPL-3 | file LICENSE", "Depends": "R (>= 3.0.2), methods", "Imports": "utils, grDevices, httpuv (>= 1.5.2), mime (>= 0.3), jsonlite\n(>= 0.9.16), xtable, digest, htmltools (>= 0.4.0.9003), R6 (>=\n2.0), sourcetools, later (>= 1.0.0), promises (>= 1.1.0),\ntools, crayon, rlang (>= 0.4.0), fastmap (>= 1.0.0), withr,\ncommonmark (>= 1.7), glue (>= 1.3.2)", "Suggests": "datasets, Cairo (>= 1.5-5), testthat (>= 2.1.1), knitr (>=\n1.6), markdown, rmarkdown, ggplot2, reactlog (>= 1.0.0),\nmagrittr, shinytest, yaml, future, dygraphs, ragg, showtext", "URL": "http://shiny.rstudio.com", "BugReports": "https://github.com/rstudio/shiny/issues", "Collate": "'app.R' 'app_template.R' 'bookmark-state-local.R' 'stack.R'\n'bookmark-state.R' 'bootstrap-deprecated.R'\n'bootstrap-layout.R' 'globals.R' 'conditions.R' 'map.R'\n'utils.R' 'bootstrap.R' 'cache-context.R' 'cache-disk.R'\n'cache-memory.R' 'cache-utils.R' 'diagnose.R' 'fileupload.R'\n'font-awesome.R' 'graph.R' 'reactives.R' 'reactive-domains.R'\n'history.R' 'hooks.R' 'html-deps.R' 'htmltools.R'\n'image-interact-opts.R' 'image-interact.R' 'imageutils.R'\n'input-action.R' 'input-checkbox.R' 'input-checkboxgroup.R'\n'input-date.R' 'input-daterange.R' 'input-file.R'\n'input-numeric.R' 'input-password.R' 'input-radiobuttons.R'\n'input-select.R' 'input-slider.R' 'input-submit.R'\n'input-text.R' 'input-textarea.R' 'input-utils.R'\n'insert-tab.R' 'insert-ui.R' 'jqueryui.R' 'middleware-shiny.R'\n'middleware.R' 'timer.R' 'shiny.R' 'mock-session.R' 'modal.R'\n'modules.R' 'notifications.R' 'priorityqueue.R' 'progress.R'\n'react.R' 'reexports.R' 'render-cached-plot.R' 'render-plot.R'\n'render-table.R' 'run-url.R' 'serializers.R'\n'server-input-handlers.R' 'server.R' 'shiny-options.R'\n'shinyui.R' 'shinywrappers.R' 'showcase.R' 'snapshot.R' 'tar.R'\n'test-export.R' 'test-server.R' 'test.R' 'update-input.R'", "RoxygenNote": "7.1.0.9000", "Encoding": "UTF-8", "NeedsCompilation": "no", "Packaged": "2020-06-19 17:22:05 UTC; winston", "Author": "Winston Chang [aut, cre],\n Joe Cheng [aut],\n JJ Allaire [aut],\n Yihui Xie [aut],\n Jonathan McPherson [aut],\n RStudio [cph],\n jQuery Foundation [cph] (jQuery library and jQuery UI library),\n jQuery contributors [ctb, cph] (jQuery library; authors listed in\n inst/www/shared/jquery-AUTHORS.txt),\n jQuery UI contributors [ctb, cph] (jQuery UI library; authors listed in\n inst/www/shared/jqueryui/AUTHORS.txt),\n Mark Otto [ctb] (Bootstrap library),\n Jacob Thornton [ctb] (Bootstrap library),\n Bootstrap contributors [ctb] (Bootstrap library),\n Twitter, Inc [cph] (Bootstrap library),\n Alexander Farkas [ctb, cph] (html5shiv library),\n Scott Jehl [ctb, cph] (Respond.js library),\n Stefan Petre [ctb, cph] (Bootstrap-datepicker library),\n Andrew Rowls [ctb, cph] (Bootstrap-datepicker library),\n Dave Gandy [ctb, cph] (Font-Awesome font),\n Brian Reavis [ctb, cph] (selectize.js library),\n Kristopher Michael Kowal [ctb, cph] (es5-shim library),\n es5-shim contributors [ctb, cph] (es5-shim library),\n Denis Ineshin [ctb, cph] (ion.rangeSlider library),\n Sami Samhuri [ctb, cph] (Javascript strftime library),\n SpryMedia Limited [ctb, cph] (DataTables library),\n John Fraser [ctb, cph] (showdown.js library),\n John Gruber [ctb, cph] (showdown.js library),\n Ivan Sagalaev [ctb, cph] (highlight.js library),\n R Core Team [ctb, cph] (tar implementation from R)", "Maintainer": "Winston Chang ", "Repository": "CRAN", "Date/Publication": "2020-06-23 13:30:03 UTC", "Built": "R 4.0.2; ; \"Wed, 24 Jun 2020 13:59:09 +0000\"; unix" } }, "sourcetools": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "sourcetools", "Type": "Package", "Title": "Tools for Reading, Tokenizing and Parsing R Code", "Version": "0.1.7", "Author": "Kevin Ushey", "Maintainer": "Kevin Ushey ", "Description": "Tools for the reading and tokenization of R code. The\n 'sourcetools' package provides both an R and C++ interface for the tokenization\n of R code, and helpers for interacting with the tokenized representation of R\n code.", "License": "MIT + file LICENSE", "LazyData": "TRUE", "Depends": "R (>= 3.0.2)", "Suggests": "testthat", "RoxygenNote": "5.0.1", "BugReports": "https://github.com/kevinushey/sourcetools/issues", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2018-04-25 03:19:22 UTC; kevin", "Repository": "CRAN", "Date/Publication": "2018-04-25 03:38:09 UTC", "Built": "R 3.5.0; x86_64-pc-linux-gnu; \"Wed, 16 May 2018 02:32:47 +0000\"; unix" } }, "sp": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "sp", "Version": "1.4-2", "Title": "Classes and Methods for Spatial Data", "Authors@R": "c(person(\"Edzer\", \"Pebesma\", role = c(\"aut\", \"cre\"),\n\t\t\t\temail = \"edzer.pebesma@uni-muenster.de\"),\n\t\t\tperson(\"Roger\", \"Bivand\", role = \"aut\",\n \temail = \"Roger.Bivand@nhh.no\"),\n\t\t\tperson(\"Barry\", \"Rowlingson\", role = \"ctb\"),\n\t\t\tperson(\"Virgilio\", \"Gomez-Rubio\", role = \"ctb\"),\n\t\t\tperson(\"Robert\", \"Hijmans\", role = \"ctb\"),\n\t\t\tperson(\"Michael\", \"Sumner\", role = \"ctb\"),\n\t\t\tperson(\"Don\", \"MacQueen\", role = \"ctb\"),\n\t\t\tperson(\"Jim\", \"Lemon\", role = \"ctb\"),\n\t\t\tperson(\"Josh\", \"O'Brien\", role = \"ctb\"),\n\t\t\tperson(\"Joseph\", \"O'Rourke\", role = \"ctb\"))", "Depends": "R (>= 3.0.0), methods", "Imports": "utils, stats, graphics, grDevices, lattice, grid", "Suggests": "RColorBrewer, rgdal (>= 1.2-3), rgeos (>= 0.3-13), gstat,\nmaptools, deldir", "Description": "Classes and methods for spatial\n data; the classes document where the spatial location information\n resides, for 2D or 3D data. Utility functions are provided, e.g. for\n plotting data as maps, spatial selection, as well as methods for\n retrieving coordinates, for subsetting, print, summary, etc.", "License": "GPL (>= 2)", "URL": "https://github.com/edzer/sp/ https://edzer.github.io/sp/", "BugReports": "https://github.com/edzer/sp/issues", "Collate": "bpy.colors.R AAA.R Class-CRS.R CRS-methods.R Class-Spatial.R\nSpatial-methods.R projected.R Class-SpatialPoints.R\nSpatialPoints-methods.R Class-SpatialPointsDataFrame.R\nSpatialPointsDataFrame-methods.R Class-SpatialMultiPoints.R\nSpatialMultiPoints-methods.R\nClass-SpatialMultiPointsDataFrame.R\nSpatialMultiPointsDataFrame-methods.R Class-GridTopology.R\nClass-SpatialGrid.R Class-SpatialGridDataFrame.R\nClass-SpatialLines.R SpatialLines-methods.R\nClass-SpatialLinesDataFrame.R SpatialLinesDataFrame-methods.R\nClass-SpatialPolygons.R Class-SpatialPolygonsDataFrame.R\nSpatialPolygons-methods.R SpatialPolygonsDataFrame-methods.R\nGridTopology-methods.R SpatialGrid-methods.R\nSpatialGridDataFrame-methods.R SpatialPolygons-internals.R\npoint.in.polygon.R SpatialPolygons-displayMethods.R zerodist.R\nimage.R stack.R bubble.R mapasp.R select.spatial.R gridded.R\nasciigrid.R spplot.R over.R spsample.R recenter.R dms.R\ngridlines.R spdists.R rbind.R flipSGDF.R chfids.R loadmeuse.R\ncompassRose.R surfaceArea.R spOptions.R subset.R disaggregate.R\nsp_spat1.R merge.R aggregate.R", "NeedsCompilation": "yes", "Packaged": "2020-05-18 09:24:15 UTC; edzer", "Author": "Edzer Pebesma [aut, cre],\n Roger Bivand [aut],\n Barry Rowlingson [ctb],\n Virgilio Gomez-Rubio [ctb],\n Robert Hijmans [ctb],\n Michael Sumner [ctb],\n Don MacQueen [ctb],\n Jim Lemon [ctb],\n Josh O'Brien [ctb],\n Joseph O'Rourke [ctb]", "Maintainer": "Edzer Pebesma ", "Repository": "CRAN", "Date/Publication": "2020-05-20 10:10:07 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 20 May 2020 13:51:51 -0400\"; unix" } }, "spData": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "spData", "Title": "Datasets for Spatial Analysis", "Version": "0.3.8", "Authors@R": "c(person(\"Roger\", \"Bivand\", role = \"aut\", email=\"Roger.Bivand@nhh.no\", comment = c(ORCID = \"0000-0003-2392-6140\")),\n person(\"Jakub\", \"Nowosad\", role = c(\"aut\", \"cre\"), email=\"nowosad.jakub@gmail.com\", comment = c(ORCID = \"0000-0002-1057-3721\")),\n person(\"Robin\", \"Lovelace\", role = \"aut\", comment = c(ORCID = \"0000-0001-5679-6536\")),\n person(\"Mark\", \"Monmonier\", role = \"ctb\", comment = \"author of the state.vbm dataset\"), \n person(\"Greg\", \"Snow\", role = \"ctb\", comment = \"author of the state.vbm dataset\")\n )", "Description": "Diverse spatial datasets for demonstrating, benchmarking and teaching spatial data analysis. \n It includes R data of class sf (defined by the package 'sf'), Spatial ('sp'), and nb ('spdep').\n Unlike other spatial data packages such as 'rnaturalearth' and 'maps', \n it also contains data stored in a range of file formats including GeoJSON, ESRI Shapefile and GeoPackage. \n Some of the datasets are designed to illustrate specific analysis techniques.\n cycle_hire() and cycle_hire_osm(), for example, is designed to illustrate point pattern analysis techniques.", "Depends": "R (>= 3.3.0)", "Imports": "sp, raster", "Suggests": "foreign, maptools, rgdal, sf (>= 0.9-1), spDataLarge (>=\n0.4.0), spdep, spatialreg", "License": "CC0", "RoxygenNote": "7.1.0", "LazyData": "true", "URL": "https://nowosad.github.io/spData/", "BugReports": "https://github.com/Nowosad/spData/issues", "Additional_repositories": "https://nowosad.github.io/drat", "NeedsCompilation": "no", "Packaged": "2020-07-03 09:01:25 UTC; jn", "Author": "Roger Bivand [aut] (),\n Jakub Nowosad [aut, cre] (),\n Robin Lovelace [aut] (),\n Mark Monmonier [ctb] (author of the state.vbm dataset),\n Greg Snow [ctb] (author of the state.vbm dataset)", "Maintainer": "Jakub Nowosad ", "Repository": "CRAN", "Date/Publication": "2020-07-03 17:00:03 UTC", "Built": "R 4.0.2; ; 2020-09-14 18:30:17 UTC; unix" } }, "stringi": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "stringi", "Version": "1.5.3", "Date": "2020-09-04", "Title": "Character String Processing Facilities", "Description": "A multitude of character string/text/natural language\n processing tools: pattern searching (e.g., with 'Java'-like regular\n expressions or the 'Unicode' collation algorithm), random string generation,\n case mapping, string transliteration, concatenation, sorting, padding,\n wrapping, Unicode normalisation, date-time formatting and parsing,\n and many more. They are fast, consistent, convenient, and -\n owing to the use of the 'ICU' (International Components for Unicode)\n library - portable across all locales and platforms.", "URL": "https://stringi.gagolewski.com/ http://site.icu-project.org/\nhttps://www.unicode.org/", "BugReports": "https://github.com/gagolews/stringi/issues", "SystemRequirements": "ICU4C (>= 55, optional)", "Type": "Package", "Depends": "R (>= 2.14)", "Imports": "tools, utils, stats", "Biarch": "TRUE", "License": "file LICENSE", "Author": "Marek Gagolewski [aut, cre, cph] (),\n Bartek Tartanus [ctb],\n and other contributors (stringi source code);\n IBM, Unicode, Inc. and other contributors (ICU4C source code);\n Unicode, Inc. (Unicode Character Database)", "Maintainer": "Marek Gagolewski ", "RoxygenNote": "7.1.1", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-09-04 06:13:18 UTC; gagolews", "Repository": "CRAN", "Date/Publication": "2020-09-09 06:40:03 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-09-09 16:25:13 UTC; unix" } }, "stringr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "stringr", "Title": "Simple, Consistent Wrappers for Common String Operations", "Version": "1.4.0", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\", \"cph\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "A consistent, simple and easy to use set of\n wrappers around the fantastic 'stringi' package. All function and\n argument names (and positions) are consistent, all functions deal with\n \"NA\"'s and zero length vectors in the same way, and the output from\n one function is easy to feed into the input of another.", "License": "GPL-2 | file LICENSE", "URL": "http://stringr.tidyverse.org, https://github.com/tidyverse/stringr", "BugReports": "https://github.com/tidyverse/stringr/issues", "Depends": "R (>= 3.1)", "Imports": "glue (>= 1.2.0), magrittr, stringi (>= 1.1.7)", "Suggests": "covr, htmltools, htmlwidgets, knitr, rmarkdown, testthat", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-02-09 16:03:19 UTC; hadley", "Author": "Hadley Wickham [aut, cre, cph],\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2019-02-10 03:40:03 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 23:48:31 +0000\"; unix" } }, "sys": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "sys", "Type": "Package", "Title": "Powerful and Reliable Tools for Running System Commands in R", "Version": "3.4", "Authors@R": "c(person(\"Jeroen\", \"Ooms\", role = c(\"aut\", \"cre\"), \n email = \"jeroen@berkeley.edu\", comment = c(ORCID = \"0000-0002-4035-0289\")),\n person(\"Gábor\", \"Csárdi\", , \"csardi.gabor@gmail.com\", role = \"ctb\"))", "Description": "Drop-in replacements for the base system2() function with fine control\n and consistent behavior across platforms. Supports clean interruption, timeout, \n background tasks, and streaming STDIN / STDOUT / STDERR over binary or text \n connections. Arguments on Windows automatically get encoded and quoted to work \n on different locales.", "License": "MIT + file LICENSE", "URL": "https://github.com/jeroen/sys", "BugReports": "https://github.com/jeroen/sys/issues", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "Suggests": "unix (>= 1.4), spelling, testthat", "Language": "en-US", "NeedsCompilation": "yes", "Packaged": "2020-07-22 19:23:54 UTC; jeroen", "Author": "Jeroen Ooms [aut, cre] (),\n Gábor Csárdi [ctb]", "Maintainer": "Jeroen Ooms ", "Repository": "CRAN", "Date/Publication": "2020-07-23 04:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Thu, 23 Jul 2020 20:37:50 +0000\"; unix" } }, "testthat": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "testthat", "Title": "Unit Testing for R", "Version": "2.3.2", "Authors@R": "c(\n person(\"Hadley\", \"Wickham\", , \"hadley@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"RStudio\", role = c(\"cph\", \"fnd\")),\n person(\"R Core team\", role = \"ctb\",\n comment = \"Implementation of utils::recover()\")\n )", "Description": "Software testing is important, but, in part because it is \n frustrating and boring, many of us avoid it. 'testthat' is a testing framework \n for R that is easy to learn and use, and integrates with your existing 'workflow'.", "License": "MIT + file LICENSE", "URL": "http://testthat.r-lib.org, https://github.com/r-lib/testthat", "BugReports": "https://github.com/r-lib/testthat/issues", "Depends": "R (>= 3.1)", "Imports": "cli, crayon (>= 1.3.4), digest, ellipsis, evaluate, magrittr,\nmethods, pkgload, praise, R6 (>= 2.2.0), rlang (>= 0.4.1),\nwithr (>= 2.0.0)", "Suggests": "covr, curl (>= 0.9.5), devtools, knitr, rmarkdown, usethis,\nvctrs (>= 0.1.0), xml2", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "RoxygenNote": "7.0.2", "Collate": "'auto-test.R' 'capture-condition.R' 'capture-output.R'\n'colour-text.R' 'compare.R' 'compare-character.R'\n'compare-numeric.R' 'compare-time.R' 'context.R' 'describe.R'\n'evaluate-promise.R' 'example.R' 'expect-comparison.R'\n'expect-condition.R' 'expect-equality.R' 'expect-inheritance.R'\n'expect-invisible.R' 'expect-known.R' 'expect-length.R'\n'expect-logical.R' 'expect-messages.R' 'expect-named.R'\n'expect-null.R' 'expect-output.R' 'reporter.R'\n'expect-self-test.R' 'expect-setequal.R' 'expect-silent.R'\n'expect-that.R' 'expect-vector.R' 'expectation.R'\n'expectations-matches.R' 'make-expectation.R' 'mock.R'\n'old-school.R' 'praise.R' 'quasi-label.R' 'recover.R'\n'reporter-check.R' 'reporter-debug.R' 'reporter-fail.R'\n'reporter-junit.R' 'reporter-list.R' 'reporter-location.R'\n'reporter-minimal.R' 'reporter-multi.R' 'stack.R'\n'reporter-progress.R' 'reporter-rstudio.R' 'reporter-silent.R'\n'reporter-stop.R' 'reporter-summary.R' 'reporter-tap.R'\n'reporter-teamcity.R' 'reporter-zzz.R' 'skip.R' 'source.R'\n'teardown.R' 'test-compiled-code.R' 'test-directory.R'\n'test-example.R' 'test-files.R' 'test-path.R' 'test-that.R'\n'try-again.R' 'utils-io.R' 'utils.R' 'verify-output.R'\n'watcher.R'", "NeedsCompilation": "yes", "Packaged": "2020-03-02 14:59:34 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph, fnd],\n R Core team [ctb] (Implementation of utils::recover())", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-03-02 15:40:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 15 Apr 2020 05:02:32 +0000\"; unix" } }, "tibble": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "tibble", "Title": "Simple Data Frames", "Version": "3.0.3", "Authors@R": "\n c(person(given = \"Kirill\",\n family = \"M\\u00fcller\",\n role = c(\"aut\", \"cre\"),\n email = \"krlmlr+r@mailbox.org\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Romain\",\n family = \"Francois\",\n role = \"ctb\",\n email = \"romain@r-enthusiasts.com\"),\n person(given = \"Jennifer\",\n family = \"Bryan\",\n role = \"ctb\",\n email = \"jenny@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Provides a 'tbl_df' class (the 'tibble') that\n provides stricter checking and better formatting than the traditional\n data frame.", "License": "MIT + file LICENSE", "URL": "https://tibble.tidyverse.org/, https://github.com/tidyverse/tibble", "BugReports": "https://github.com/tidyverse/tibble/issues", "Depends": "R (>= 3.1.0)", "Imports": "cli, crayon (>= 1.3.4), ellipsis (>= 0.2.0), fansi (>= 0.4.0),\nlifecycle (>= 0.2.0), magrittr, methods, pillar (>= 1.4.3),\npkgconfig, rlang (>= 0.4.3), utils, vctrs (>= 0.2.4)", "Suggests": "bench, bit64, blob, covr, dplyr, evaluate, formattable, hms,\nhtmltools, import, knitr, lubridate, mockr, nycflights13,\npurrr, rmarkdown, testthat (>= 2.1.0), tidyr, withr", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "yes", "RoxygenNote": "7.1.1.9000", "NeedsCompilation": "yes", "Packaged": "2020-07-10 09:44:37 UTC; kirill", "Author": "Kirill Müller [aut, cre],\n Hadley Wickham [aut],\n Romain Francois [ctb],\n Jennifer Bryan [ctb],\n RStudio [cph]", "Maintainer": "Kirill Müller ", "Repository": "CRAN", "Date/Publication": "2020-07-10 20:40:03 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sun, 12 Jul 2020 02:21:36 +0000\"; unix" } }, "tidyr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "tidyr", "Title": "Tidy Messy Data", "Version": "1.1.2", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Tools to help to create tidy data, where each column is a \n variable, each row is an observation, and each cell contains a single value. \n 'tidyr' contains tools for changing the shape (pivoting) and hierarchy\n (nesting and 'unnesting') of a dataset, turning deeply nested lists\n into rectangular data frames ('rectangling'), and extracting values out \n of string columns. It also includes tools for working with missing values \n (both implicit and explicit).", "License": "MIT + file LICENSE", "URL": "https://tidyr.tidyverse.org, https://github.com/tidyverse/tidyr", "BugReports": "https://github.com/tidyverse/tidyr/issues", "Depends": "R (>= 3.1)", "Imports": "dplyr (>= 0.8.2), ellipsis (>= 0.1.0), glue, magrittr, purrr,\nrlang, tibble (>= 2.1.1), tidyselect (>= 1.1.0), utils, vctrs\n(>= 0.3.0), lifecycle", "Suggests": "covr, jsonlite, knitr, repurrrsive (>= 1.0.0), rmarkdown,\nreadr, testthat (>= 2.1.0)", "LinkingTo": "cpp11 (>= 0.2.1)", "VignetteBuilder": "knitr", "SystemRequirements": "C++11", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "NeedsCompilation": "yes", "Packaged": "2020-08-26 15:43:09 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2020-08-27 12:20:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sat, 29 Aug 2020 02:23:35 +0000\"; unix" } }, "tidyselect": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "tidyselect", "Title": "Select from a Set of Strings", "Version": "1.1.0", "Authors@R": "c(\n person(\"Lionel\", \"Henry\", ,\"lionel@rstudio.com\", c(\"aut\", \"cre\")),\n person(\"Hadley\", \"Wickham\", ,\"hadley@rstudio.com\", \"aut\"),\n person(\"RStudio\", role = \"cph\")\n )", "Description": "A backend for the selecting functions of the 'tidyverse'.\n It makes it easy to implement select-like functions in your own\n packages in a way that is consistent with other 'tidyverse'\n interfaces for selection.", "Depends": "R (>= 3.2)", "Imports": "ellipsis, glue (>= 1.3.0), purrr (>= 0.3.2), rlang (>= 0.4.6),\nvctrs (>= 0.2.2)", "Suggests": "covr, crayon, dplyr, knitr, magrittr, rmarkdown, testthat (>=\n2.3.0), tibble (>= 2.1.3), withr", "License": "GPL-3", "Encoding": "UTF-8", "LazyData": "true", "ByteCompile": "true", "RoxygenNote": "7.1.0", "URL": "https://tidyselect.r-lib.org, https://github.com/r-lib/tidyselect", "BugReports": "https://github.com/r-lib/tidyselect/issues", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-05-11 17:13:55 UTC; lionel", "Author": "Lionel Henry [aut, cre],\n Hadley Wickham [aut],\n RStudio [cph]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-05-11 23:10:07 UTC", "Built": "R 4.0.0; ; \"Tue, 12 May 2020 15:21:24 +0000\"; unix" } }, "tidyverse": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "tidyverse", "Title": "Easily Install and Load the 'Tidyverse'", "Version": "1.3.0", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = c(\"aut\", \"cre\"),\n email = \"hadley@rstudio.com\"),\n person(given = \"RStudio\",\n role = c(\"cph\", \"fnd\")))", "Description": "The 'tidyverse' is a set of packages that work in\n harmony because they share common data representations and 'API'\n design. This package is designed to make it easy to install and load\n multiple 'tidyverse' packages in a single step. Learn more about the\n 'tidyverse' at .", "License": "GPL-3 | file LICENSE", "URL": "http://tidyverse.tidyverse.org,\nhttps://github.com/tidyverse/tidyverse", "BugReports": "https://github.com/tidyverse/tidyverse/issues", "Depends": "R (>= 3.2)", "Imports": "broom (>= 0.5.2), cli (>= 1.1.0), crayon (>= 1.3.4), dbplyr\n(>= 1.4.2), dplyr (>= 0.8.3), forcats (>= 0.4.0), ggplot2 (>=\n3.2.1), haven (>= 2.2.0), hms (>= 0.5.2), httr (>= 1.4.1),\njsonlite (>= 1.6), lubridate (>= 1.7.4), magrittr (>= 1.5),\nmodelr (>= 0.1.5), pillar (>= 1.4.2), purrr (>= 0.3.3), readr\n(>= 1.3.1), readxl (>= 1.3.1), reprex (>= 0.3.0), rlang (>=\n0.4.1), rstudioapi (>= 0.10), rvest (>= 0.3.5), stringr (>=\n1.4.0), tibble (>= 2.1.3), tidyr (>= 1.0.0), xml2 (>= 1.2.2)", "Suggests": "covr, feather, glue, knitr, rmarkdown, testthat", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.0.0", "NeedsCompilation": "no", "Packaged": "2019-11-20 00:01:12 UTC; hadley", "Author": "Hadley Wickham [aut, cre],\n RStudio [cph, fnd]", "Maintainer": "Hadley Wickham ", "Repository": "CRAN", "Date/Publication": "2019-11-21 05:30:02 UTC", "Built": "R 4.0.0; ; \"Wed, 22 Apr 2020 16:48:17 +0000\"; unix" } }, "tinytex": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "tinytex", "Type": "Package", "Title": "Helper Functions to Install and Maintain TeX Live, and Compile\nLaTeX Documents", "Version": "0.25", "Authors@R": "c(\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\", \"cph\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(family = \"RStudio, PBC\", role = \"cph\"),\n person(\"Fernando\", \"Cagua\", role = \"ctb\"),\n person(\"Ethan\", \"Heinzen\", role = \"ctb\"),\n person()\n )", "Description": "Helper functions to install and maintain the 'LaTeX' distribution\n named 'TinyTeX' (), a lightweight, cross-platform,\n portable, and easy-to-maintain version of 'TeX Live'. This package also\n contains helper functions to compile 'LaTeX' documents, and install missing\n 'LaTeX' packages automatically.", "Imports": "xfun (>= 0.5)", "Suggests": "testit, rstudioapi", "License": "MIT + file LICENSE", "URL": "https://github.com/yihui/tinytex", "BugReports": "https://github.com/yihui/tinytex/issues", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "NeedsCompilation": "no", "Packaged": "2020-07-23 23:35:49 UTC; yihui", "Author": "Yihui Xie [aut, cre, cph] (),\n RStudio, PBC [cph],\n Fernando Cagua [ctb],\n Ethan Heinzen [ctb]", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2020-07-24 04:30:02 UTC", "Built": "R 4.0.2; ; \"Fri, 24 Jul 2020 13:30:12 +0000\"; unix" } }, "units": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "units", "Version": "0.6-7", "Title": "Measurement Units for R Vectors", "Authors@R": "c(person(\"Edzer\", \"Pebesma\", role = c(\"aut\", \"cre\"), email = \"edzer.pebesma@uni-muenster.de\", comment = c(ORCID = \"0000-0001-8049-7069\")),\n\t\tperson(\"Thomas\", \"Mailund\", role = \"aut\", email = \"mailund@birc.au.dk\"),\n\t\tperson(\"Tomasz\", \"Kalinowski\", role = \"aut\"),\n\t\tperson(\"James\", \"Hiebert\", role = \"ctb\"),\n\t\tperson(\"Iñaki\", \"Ucar\", role = \"ctb\", email = \"iucar@fedoraproject.org\", comment = c(ORCID = \"0000-0001-6403-5550\"))\n\t\t)", "Depends": "R (>= 3.0.2)", "Imports": "Rcpp", "LinkingTo": "Rcpp (>= 0.12.10)", "Suggests": "udunits2, NISTunits, measurements, xml2, magrittr, pillar (>=\n1.3.0), dplyr (>= 1.0.0), vctrs (>= 0.3.1), knitr, testthat,\nggforce, rmarkdown", "VignetteBuilder": "knitr", "Description": "Support for measurement units in R vectors, matrices\n\tand arrays: automatic propagation, conversion, derivation\n\tand simplification of units; raising errors in case of unit\n\tincompatibility. Compatible with the POSIXct, Date and difftime \n\tclasses. Uses the UNIDATA udunits library and unit database for \n\tunit compatibility checking and conversion.\n\tDocumentation about 'units' is provided in the paper by Pebesma, Mailund &\n Hiebert (2016, ), included in this package as a\n vignette; see 'citation(\"units\")' for details.", "SystemRequirements": "udunits-2", "License": "GPL-2", "URL": "https://github.com/r-quantities/units/", "BugReports": "https://github.com/r-quantities/units/issues/", "RoxygenNote": "7.1.0", "Encoding": "UTF-8", "NeedsCompilation": "yes", "Packaged": "2020-06-11 11:45:34 UTC; edzer", "Author": "Edzer Pebesma [aut, cre] (),\n Thomas Mailund [aut],\n Tomasz Kalinowski [aut],\n James Hiebert [ctb],\n Iñaki Ucar [ctb] ()", "Maintainer": "Edzer Pebesma ", "Repository": "CRAN", "Date/Publication": "2020-06-13 15:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 16:36:04 UTC; unix" } }, "utf8": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "utf8", "Version": "1.1.4", "Title": "Unicode Text Processing", "Authors@R": "c(\n person(c(\"Patrick\", \"O.\"), \"Perry\",\n role = c(\"aut\", \"cph\", \"cre\"),\n email = \"patperry@gmail.com\"),\n person(\"Unicode, Inc.\",\n role = c(\"cph\", \"dtc\"),\n comment = \"Unicode Character Database\"))", "Depends": "R (>= 2.10)", "Suggests": "knitr, rmarkdown, testthat", "Description": "Process and print 'UTF-8' encoded international text (Unicode). Input, validate, normalize, encode, format, and display.", "License": "Apache License (== 2.0) | file LICENSE", "URL": "https://github.com/patperry/r-utf8", "BugReports": "https://github.com/patperry/r-utf8/issues", "LazyData": "Yes", "Encoding": "UTF-8", "VignetteBuilder": "knitr, rmarkdown", "NeedsCompilation": "yes", "Packaged": "2018-05-24 15:00:57 UTC; ptrck", "Author": "Patrick O. Perry [aut, cph, cre],\n Unicode, Inc. [cph, dtc] (Unicode Character Database)", "Maintainer": "Patrick O. Perry ", "Repository": "CRAN", "Date/Publication": "2018-05-24 17:09:15 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-08-25 21:14:59 UTC; unix" } }, "vctrs": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "vctrs", "Title": "Vector Helpers", "Version": "0.3.4", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Lionel\",\n family = \"Henry\",\n role = c(\"aut\", \"cre\"),\n email = \"lionel@rstudio.com\"),\n person(given = \"Davis\",\n family = \"Vaughan\",\n role = \"aut\",\n email = \"davis@rstudio.com\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "Defines new notions of prototype and size that are\n used to provide tools for consistent and well-founded type-coercion\n and size-recycling, and are in turn connected to ideas of type- and\n size-stability useful for analysing function interfaces.", "License": "GPL-3", "URL": "https://vctrs.r-lib.org/", "BugReports": "https://github.com/r-lib/vctrs/issues", "Depends": "R (>= 3.2)", "Imports": "ellipsis (>= 0.2.0), digest, glue, rlang (>= 0.4.7)", "Suggests": "bit64, covr, crayon, dplyr (>= 0.8.5), generics, knitr,\npillar (>= 1.4.4), pkgdown, rmarkdown, testthat (>= 2.3.0),\ntibble, withr, xml2, waldo (>= 0.2.0), zeallot", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "Language": "en-GB", "LazyData": "true", "RoxygenNote": "7.1.1", "NeedsCompilation": "yes", "Packaged": "2020-08-28 15:24:30 UTC; lionel", "Author": "Hadley Wickham [aut],\n Lionel Henry [aut, cre],\n Davis Vaughan [aut],\n RStudio [cph]", "Maintainer": "Lionel Henry ", "Repository": "CRAN", "Date/Publication": "2020-08-29 07:40:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; \"Sun, 30 Aug 2020 03:21:55 +0000\"; unix" } }, "viridis": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "viridis", "Type": "Package", "Title": "Default Color Maps from 'matplotlib'", "Version": "0.5.1", "Authors@R": "c(\n person(\"Simon\", \"Garnier\", email = \"garnier@njit.edu\", role = c(\"aut\", \"cre\")),\n person(\"Noam\", \"Ross\", email = \"noam.ross@gmail.com\", role = c(\"ctb\", \"cph\")),\n person(\"Bob\", \"Rudis\", email = \"bob@rud.is\", role = c(\"ctb\", \"cph\")),\n person(\"Marco\", \"Sciaini\", email = \"sciaini.marco@gmail.com\", role = c(\"ctb\", \"cph\")),\n person(\"Cédric\", \"Scherer\", email = \"scherer@izw-berlin.de\", role = c(\"ctb\", \"cph\"))\n )", "Maintainer": "Simon Garnier ", "Description": "Implementation of the 'viridis' - the default -, 'magma', 'plasma', \n 'inferno', and 'cividis' color maps for 'R'. 'viridis', 'magma', 'plasma', \n and 'inferno' are ported from 'matplotlib' , a \n popular plotting library for 'python'. 'cividis', was developed by Jamie R. \n Nuñez and Sean M. Colby. These color maps are designed in such a way that \n they will analytically be perfectly perceptually-uniform, both in regular \n form and also when converted to black-and-white. They are also designed to \n be perceived by readers with the most common form of color blindness (all \n color maps in this package) and color vision deficiency ('cividis' only). ", "License": "MIT + file LICENSE", "LazyData": "TRUE", "Encoding": "UTF-8", "Depends": "R (>= 2.10), viridisLite (>= 0.3.0)", "Imports": "stats, ggplot2 (>= 1.0.1), gridExtra", "Suggests": "hexbin (>= 1.27.0), scales, MASS, knitr, dichromat,\ncolorspace, rasterVis, httr, mapproj, vdiffr, svglite (>=\n1.2.0), testthat, covr, rmarkdown, rgdal", "VignetteBuilder": "knitr", "URL": "https://github.com/sjmgarnier/viridis", "BugReports": "https://github.com/sjmgarnier/viridis/issues", "RoxygenNote": "6.0.1", "NeedsCompilation": "no", "Packaged": "2018-03-29 14:51:56 UTC; simon", "Author": "Simon Garnier [aut, cre],\n Noam Ross [ctb, cph],\n Bob Rudis [ctb, cph],\n Marco Sciaini [ctb, cph],\n Cédric Scherer [ctb, cph]", "Repository": "CRAN", "Date/Publication": "2018-03-29 15:48:56 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:41:01 UTC; unix" } }, "viridisLite": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "viridisLite", "Type": "Package", "Title": "Default Color Maps from 'matplotlib' (Lite Version)", "Version": "0.3.0", "Authors@R": "c(\n person(\"Simon\", \"Garnier\", email = \"garnier@njit.edu\", role = c(\"aut\", \"cre\")),\n person(\"Noam\", \"Ross\", email = \"noam.ross@gmail.com\", role = c(\"ctb\", \"cph\")),\n person(\"Bob\", \"Rudis\", email = \"bob@rud.is\", role = c(\"ctb\", \"cph\")),\n person(\"Marco\", \"Sciaini\", email = \"sciaini.marco@gmail.com\", role = c(\"ctb\", \"cph\")),\n person(\"Cédric\", \"Scherer\", email = \"scherer@izw-berlin.de\", role = c(\"ctb\", \"cph\"))\n )", "Maintainer": "Simon Garnier ", "Description": "Implementation of the 'viridis' - the default -, 'magma', 'plasma', \n 'inferno', and 'cividis' color maps for 'R'. 'viridis', 'magma', 'plasma', \n and 'inferno' are ported from 'matplotlib' , a \n popular plotting library for 'python'. 'cividis', was developed by Jamie R. \n Nuñez and Sean M. Colby. These color maps are designed in such a way that \n they will analytically be perfectly perceptually-uniform, both in regular \n form and also when converted to black-and-white. They are also designed to \n be perceived by readers with the most common form of color blindness (all \n color maps in this package) and color vision deficiency ('cividis' only). \n This is the 'lite' version of the more complete 'viridis' package that can \n be found at .", "License": "MIT + file LICENSE", "LazyData": "TRUE", "Encoding": "UTF-8", "Depends": "R (>= 2.10)", "Suggests": "hexbin (>= 1.27.0), ggplot2 (>= 1.0.1), testthat, covr", "URL": "https://github.com/sjmgarnier/viridisLite", "BugReports": "https://github.com/sjmgarnier/viridisLite/issues", "RoxygenNote": "6.0.1", "NeedsCompilation": "no", "Packaged": "2018-02-01 17:33:56 UTC; simon", "Author": "Simon Garnier [aut, cre],\n Noam Ross [ctb, cph],\n Bob Rudis [ctb, cph],\n Marco Sciaini [ctb, cph],\n Cédric Scherer [ctb, cph]", "Repository": "CRAN", "Date/Publication": "2018-02-01 22:45:56 UTC", "Built": "R 4.0.2; ; 2020-08-25 16:41:39 UTC; unix" } }, "whisker": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "whisker", "Maintainer": "Edwin de Jonge ", "License": "GPL-3", "Title": "{{mustache}} for R, Logicless Templating", "Type": "Package", "LazyLoad": "yes", "Author": "Edwin de Jonge", "Description": "Implements 'Mustache' logicless templating.", "Version": "0.4", "URL": "http://github.com/edwindj/whisker", "Suggests": "markdown", "RoxygenNote": "6.1.1", "NeedsCompilation": "no", "Packaged": "2019-08-28 07:26:51 UTC; edwin", "Repository": "CRAN", "Date/Publication": "2019-08-28 08:50:02 UTC", "Built": "R 4.0.0; ; \"Mon, 13 Apr 2020 21:06:58 +0000\"; unix" } }, "withr": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "withr", "Title": "Run Code 'With' Temporarily Modified Global State", "Version": "2.2.0", "Authors@R": "\n c(person(given = \"Jim\",\n family = \"Hester\",\n role = c(\"aut\", \"cre\"),\n email = \"james.f.hester@gmail.com\"),\n person(given = \"Kirill\",\n family = \"Müller\",\n role = \"aut\",\n email = \"krlmlr+r@mailbox.org\"),\n person(given = \"Kevin\",\n family = \"Ushey\",\n role = \"aut\",\n email = \"kevinushey@gmail.com\"),\n person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Winston\",\n family = \"Chang\",\n role = \"aut\"),\n person(given = \"Richard\",\n family = \"Cotton\",\n role = \"ctb\"),\n person(given = \"RStudio\",\n role = \"cph\"))", "Description": "A set of functions to run code 'with' safely and\n temporarily modified global state. Many of these functions were\n originally a part of the 'devtools' package, this provides a simple\n package with limited dependencies to provide access to these\n functions.", "License": "GPL (>= 2)", "URL": "http://withr.r-lib.org, http://github.com/r-lib/withr#readme", "BugReports": "http://github.com/r-lib/withr/issues", "Depends": "R (>= 3.2.0)", "Imports": "graphics, grDevices, stats", "Suggests": "covr, DBI, knitr, lattice, methods, rmarkdown, RSQLite,\ntestthat (>= 2.1.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.0", "Collate": "'local_.R' 'with_.R' 'collate.R' 'connection.R' 'db.R'\n'defer.R' 'wrap.R' 'devices.R' 'dir.R' 'env.R' 'file.R'\n'libpaths.R' 'locale.R' 'makevars.R' 'namespace.R' 'options.R'\n'par.R' 'path.R' 'rng.R' 'seed.R' 'sink.R' 'tempfile.R'\n'timezone.R' 'torture.R' 'utils.R' 'with.R'", "NeedsCompilation": "no", "Packaged": "2020-04-20 21:18:04 UTC; jhester", "Author": "Jim Hester [aut, cre],\n Kirill Müller [aut],\n Kevin Ushey [aut],\n Hadley Wickham [aut],\n Winston Chang [aut],\n Richard Cotton [ctb],\n RStudio [cph]", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-04-20 22:10:02 UTC", "Built": "R 4.0.0; ; \"Wed, 29 Apr 2020 13:34:30 +0000\"; unix" } }, "xfun": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "xfun", "Type": "Package", "Title": "Miscellaneous Functions by 'Yihui Xie'", "Version": "0.17", "Authors@R": "c(\n person(\"Yihui\", \"Xie\", role = c(\"aut\", \"cre\", \"cph\"), email = \"xie@yihui.name\", comment = c(ORCID = \"0000-0003-0645-5666\")),\n person(\"Wush\", \"Wu\", role = \"ctb\"),\n person(\"Daijiang\", \"Li\", role = \"ctb\"),\n person(\"Xianying\", \"Tan\", role = \"ctb\"),\n person(\"Salim\", \"Brüggemann\", role = \"ctb\", email = \"salim-b@pm.me\", comment = c(ORCID = \"0000-0002-5329-5987\")),\n person()\n )", "Description": "Miscellaneous functions commonly used in other packages maintained by 'Yihui Xie'.", "Imports": "stats, tools", "Suggests": "testit, parallel, codetools, rstudioapi, tinytex, mime,\nmarkdown, knitr, htmltools, remotes, pak, rmarkdown", "License": "MIT + file LICENSE", "URL": "https://github.com/yihui/xfun", "BugReports": "https://github.com/yihui/xfun/issues", "Encoding": "UTF-8", "LazyData": "true", "RoxygenNote": "7.1.1", "VignetteBuilder": "knitr", "NeedsCompilation": "yes", "Packaged": "2020-09-08 22:01:26 UTC; yihui", "Author": "Yihui Xie [aut, cre, cph] (),\n Wush Wu [ctb],\n Daijiang Li [ctb],\n Xianying Tan [ctb],\n Salim Brüggemann [ctb] ()", "Maintainer": "Yihui Xie ", "Repository": "CRAN", "Date/Publication": "2020-09-09 05:30:02 UTC", "Built": "R 4.0.2; x86_64-pc-linux-gnu; 2020-09-09 16:25:08 UTC; unix" } }, "xml2": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "xml2", "Title": "Parse XML", "Version": "1.3.2", "Authors@R": "\n c(person(given = \"Hadley\",\n family = \"Wickham\",\n role = \"aut\",\n email = \"hadley@rstudio.com\"),\n person(given = \"Jim\",\n family = \"Hester\",\n role = c(\"aut\", \"cre\"),\n email = \"jim.hester@rstudio.com\"),\n person(given = \"Jeroen\",\n family = \"Ooms\",\n role = \"aut\"),\n person(given = \"RStudio\",\n role = \"cph\"),\n person(given = \"R Foundation\",\n role = \"ctb\",\n comment = \"Copy of R-project homepage cached as example\"))", "Description": "Work with XML files using a simple, consistent\n interface. Built on top of the 'libxml2' C library.", "License": "GPL (>= 2)", "URL": "https://xml2.r-lib.org/, https://github.com/r-lib/xml2", "BugReports": "https://github.com/r-lib/xml2/issues", "Depends": "R (>= 3.1.0)", "Imports": "methods", "Suggests": "covr, curl, httr, knitr, magrittr, mockery, rmarkdown,\ntestthat (>= 2.1.0)", "VignetteBuilder": "knitr", "Encoding": "UTF-8", "RoxygenNote": "7.1.0", "SystemRequirements": "libxml2: libxml2-dev (deb), libxml2-devel (rpm)", "Collate": "'S4.R' 'as_list.R' 'xml_parse.R' 'as_xml_document.R'\n'classes.R' 'init.R' 'paths.R' 'utils.R' 'xml_attr.R'\n'xml_children.R' 'xml_find.R' 'xml_modify.R' 'xml_name.R'\n'xml_namespaces.R' 'xml_path.R' 'xml_schema.R'\n'xml_serialize.R' 'xml_structure.R' 'xml_text.R' 'xml_type.R'\n'xml_url.R' 'xml_write.R' 'zzz.R'", "NeedsCompilation": "yes", "Packaged": "2020-04-23 15:45:47 UTC; jhester", "Author": "Hadley Wickham [aut],\n Jim Hester [aut, cre],\n Jeroen Ooms [aut],\n RStudio [cph],\n R Foundation [ctb] (Copy of R-project homepage cached as example)", "Maintainer": "Jim Hester ", "Repository": "CRAN", "Date/Publication": "2020-04-23 22:00:03 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Wed, 29 Apr 2020 13:35:52 +0000\"; unix" } }, "xtable": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "xtable", "Version": "1.8-4", "Date": "2019-04-08", "Title": "Export Tables to LaTeX or HTML", "Authors@R": "c(person(\"David B.\", \"Dahl\", role=\"aut\"),\n person(\"David\", \"Scott\", role=c(\"aut\",\"cre\"),\n email=\"d.scott@auckland.ac.nz\"),\n person(\"Charles\", \"Roosen\", role=\"aut\"),\n person(\"Arni\", \"Magnusson\", role=\"aut\"),\n person(\"Jonathan\", \"Swinton\", role=\"aut\"),\n person(\"Ajay\", \"Shah\", role=\"ctb\"),\n person(\"Arne\", \"Henningsen\", role=\"ctb\"),\n person(\"Benno\", \"Puetz\", role=\"ctb\"),\n person(\"Bernhard\", \"Pfaff\", role=\"ctb\"),\n person(\"Claudio\", \"Agostinelli\", role=\"ctb\"),\n person(\"Claudius\", \"Loehnert\", role=\"ctb\"),\n person(\"David\", \"Mitchell\", role=\"ctb\"),\n person(\"David\", \"Whiting\", role=\"ctb\"),\n person(\"Fernando da\", \"Rosa\", role=\"ctb\"),\n person(\"Guido\", \"Gay\", role=\"ctb\"),\n person(\"Guido\", \"Schulz\", role=\"ctb\"),\n person(\"Ian\", \"Fellows\", role=\"ctb\"),\n person(\"Jeff\", \"Laake\", role=\"ctb\"),\n person(\"John\", \"Walker\", role=\"ctb\"),\n person(\"Jun\", \"Yan\", role=\"ctb\"),\n person(\"Liviu\", \"Andronic\", role=\"ctb\"),\n person(\"Markus\", \"Loecher\", role=\"ctb\"),\n person(\"Martin\", \"Gubri\", role=\"ctb\"),\n person(\"Matthieu\", \"Stigler\", role=\"ctb\"),\n person(\"Robert\", \"Castelo\", role=\"ctb\"),\n person(\"Seth\", \"Falcon\", role=\"ctb\"),\n person(\"Stefan\", \"Edwards\", role=\"ctb\"),\n person(\"Sven\", \"Garbade\", role=\"ctb\"),\n person(\"Uwe\", \"Ligges\", role=\"ctb\"))", "Maintainer": "David Scott ", "Imports": "stats, utils", "Suggests": "knitr, plm, zoo, survival", "VignetteBuilder": "knitr", "Description": "Coerce data to LaTeX and HTML tables.", "URL": "http://xtable.r-forge.r-project.org/", "Depends": "R (>= 2.10.0)", "License": "GPL (>= 2)", "Repository": "CRAN", "NeedsCompilation": "no", "Packaged": "2019-04-21 10:56:51 UTC; dsco036", "Author": "David B. Dahl [aut],\n David Scott [aut, cre],\n Charles Roosen [aut],\n Arni Magnusson [aut],\n Jonathan Swinton [aut],\n Ajay Shah [ctb],\n Arne Henningsen [ctb],\n Benno Puetz [ctb],\n Bernhard Pfaff [ctb],\n Claudio Agostinelli [ctb],\n Claudius Loehnert [ctb],\n David Mitchell [ctb],\n David Whiting [ctb],\n Fernando da Rosa [ctb],\n Guido Gay [ctb],\n Guido Schulz [ctb],\n Ian Fellows [ctb],\n Jeff Laake [ctb],\n John Walker [ctb],\n Jun Yan [ctb],\n Liviu Andronic [ctb],\n Markus Loecher [ctb],\n Martin Gubri [ctb],\n Matthieu Stigler [ctb],\n Robert Castelo [ctb],\n Seth Falcon [ctb],\n Stefan Edwards [ctb],\n Sven Garbade [ctb],\n Uwe Ligges [ctb]", "Date/Publication": "2019-04-21 12:20:03 UTC", "Built": "R 4.0.2; ; 2020-09-03 13:53:57 UTC; unix" } }, "yaml": { "Source": "CRAN", "Repository": "https://cloud.r-project.org", "description": { "Package": "yaml", "Type": "Package", "Title": "Methods to Convert R Data to YAML and Back", "Date": "2020-01-23", "Version": "2.2.1", "Suggests": "RUnit", "Author": "Jeremy Stephens [aut, cre], Kirill Simonov [aut], Yihui Xie [ctb],\n Zhuoer Dong [ctb], Hadley Wickham [ctb], Jeffrey Horner [ctb], reikoch [ctb],\n Will Beasley [ctb], Brendan O'Connor [ctb], Gregory R. Warnes [ctb]", "Maintainer": "Jeremy Stephens ", "License": "BSD_3_clause + file LICENSE", "Description": "Implements the 'libyaml' 'YAML' 1.1 parser and emitter\n () for R.", "URL": "https://github.com/viking/r-yaml/", "BugReports": "https://github.com/viking/r-yaml/issues", "NeedsCompilation": "yes", "Packaged": "2020-01-30 20:29:56 UTC; stephej1", "Repository": "CRAN", "Date/Publication": "2020-02-01 05:50:02 UTC", "Built": "R 4.0.0; x86_64-pc-linux-gnu; \"Mon, 13 Apr 2020 20:55:38 +0000\"; unix" } } }, "files": { "app.R": { "checksum": "eeaaae3ac149c50c4f0ce7564e739abf" }, "packrat/desc/askpass": { "checksum": "435c84ecbd5005c6e42abccb6759df60" }, "packrat/desc/assertthat": { "checksum": "5b9f3728de66ad3216ed8b6e4fc7cc72" }, "packrat/desc/backports": { "checksum": "b83ddb956cdcc71ea66b7b1ae6caa0d5" }, "packrat/desc/base64enc": { "checksum": "1c630d351c344414750fe377379e49e1" }, "packrat/desc/BH": { "checksum": "8f299723177dee9ccff8bc3fea6e43cc" }, "packrat/desc/blob": { "checksum": "def19f8b861d9e54d4547c3be0105bc2" }, "packrat/desc/broom": { "checksum": "b8446d58b6111d09a2b63e2fe73ec58a" }, "packrat/desc/callr": { "checksum": "7bf838914af4ac8fa74aef35b85d1f7b" }, "packrat/desc/cellranger": { "checksum": "10ae013060987bd8a7aba3912941e47a" }, "packrat/desc/class": { "checksum": "21f15310f6b61825ec7c24b76b260a6f" }, "packrat/desc/classInt": { "checksum": "9e0cf3fadcc8600eb39aed3178f184ea" }, "packrat/desc/cli": { "checksum": "8ef556e3af4257c99b77294ec355294f" }, "packrat/desc/clipr": { "checksum": "63f0892bc1cfba44d6373aef656a6398" }, "packrat/desc/colorspace": { "checksum": "120a3f6ce468e0a1b50365df71d90cac" }, "packrat/desc/commonmark": { "checksum": "8fec981683b0ef7da3d729df1e445385" }, "packrat/desc/cpp11": { "checksum": "0ff2317a1a73efd3a64f88131ca436fc" }, "packrat/desc/crayon": { "checksum": "f19aa51ef16b15099fd8cf3b642e587a" }, "packrat/desc/crosstalk": { "checksum": "1b77d1ea6938a9642a5600ccb5b20146" }, "packrat/desc/curl": { "checksum": "053a79b0873a3faa8053ecce93859c4f" }, "packrat/desc/DBI": { "checksum": "04f18d626cd0f3b09640dacdc64f7629" }, "packrat/desc/dbplyr": { "checksum": "1848de986e1225e4dbc480f534c3405b" }, "packrat/desc/desc": { "checksum": "6f1c2594fea290687b9404051c90eaaf" }, "packrat/desc/digest": { "checksum": "ce166ca3c563349f779f3a8a92068efb" }, "packrat/desc/dplyr": { "checksum": "fe7838fe52705f97fa42bd8beb1433d7" }, "packrat/desc/e1071": { "checksum": "894d17765f8fe9b2707c7d568cca043c" }, "packrat/desc/ellipsis": { "checksum": "b6f76892193a9d2ffa1c258dc7ed0db5" }, "packrat/desc/evaluate": { "checksum": "765884904255950b94e7bdb11041a1c4" }, "packrat/desc/fansi": { "checksum": "84e2ce3335b0837a61ad2a068108faab" }, "packrat/desc/farver": { "checksum": "a1e5610a2f03251c79dccd4897562a42" }, "packrat/desc/fastmap": { "checksum": "271a41736452cb3d8775756ae1b1d14a" }, "packrat/desc/forcats": { "checksum": "def14b422961019d5af77170d0d6a236" }, "packrat/desc/fs": { "checksum": "07ecaf3f34c2645392506cfce929fb70" }, "packrat/desc/generics": { "checksum": "857b2f7765fc381aa10ce54f2daf9bd7" }, "packrat/desc/ggplot2": { "checksum": "c6c9625744f3f2fdd5801ffe6e452de8" }, "packrat/desc/glue": { "checksum": "0be6d7959a94fa7a658dc025c747a30c" }, "packrat/desc/gridExtra": { "checksum": "52022f2a18675f84afc443600321d98d" }, "packrat/desc/gtable": { "checksum": "c74e55317add2c8ec17d919df82f95ad" }, "packrat/desc/haven": { "checksum": "24b8388598ec1c028899269df5abb971" }, "packrat/desc/highr": { "checksum": "b2bf9f57afc6cc6e9461cb3397502a9f" }, "packrat/desc/hms": { "checksum": "3625a42a110ef2206709ec35e954d90f" }, "packrat/desc/htmltools": { "checksum": "8a827ea72e3b7fe879c92833bd10097b" }, "packrat/desc/htmlwidgets": { "checksum": "664a637eb9a30c771447192f85fe4244" }, "packrat/desc/httpuv": { "checksum": "df619accc123b904b4365f3461fc0ba8" }, "packrat/desc/httr": { "checksum": "a3c66945b0420726c6546cb4efa082ab" }, "packrat/desc/isoband": { "checksum": "e05a297467cd37e67d98d73febe6ef47" }, "packrat/desc/jsonlite": { "checksum": "43773415d7939eda1875e1075ac172a1" }, "packrat/desc/KernSmooth": { "checksum": "fc35cd43e70502672949f9ff0bcc92f5" }, "packrat/desc/knitr": { "checksum": "50c713782d5c9910b464b6483f2745f5" }, "packrat/desc/labeling": { "checksum": "43585aa12b1d2251986a09cff1633003" }, "packrat/desc/later": { "checksum": "847ec0b6b87ac782cb0f79dd222964ae" }, "packrat/desc/lattice": { "checksum": "907db321375c24126e2e816d8ddb7d8e" }, "packrat/desc/lazyeval": { "checksum": "b4fc49b539c03e3936d42e149680b6eb" }, "packrat/desc/leaflet": { "checksum": "05b6ab6999db2a08e06040a89044bd7b" }, "packrat/desc/leaflet.providers": { "checksum": "ddc3ad419ad3d53ed118132254d54fe2" }, "packrat/desc/lifecycle": { "checksum": "2d333c6bef8158aea0fac27d7bd6ee7d" }, "packrat/desc/lubridate": { "checksum": "e01623f56daa8e33921c883350b2c146" }, "packrat/desc/magrittr": { "checksum": "c34ba69b5f5000a1a59b525b2d8c80bc" }, "packrat/desc/markdown": { "checksum": "b8b149063720205468e227287bf31c68" }, "packrat/desc/MASS": { "checksum": "5073489634bbfb2a54551360da5b3882" }, "packrat/desc/Matrix": { "checksum": "33a8d6ac1332d1924b0e70c3d96ac31f" }, "packrat/desc/mgcv": { "checksum": "286f3639fb021167bf5fb07e49d967dc" }, "packrat/desc/mime": { "checksum": "0a7c2d97d203b20dea367c6024c3f10f" }, "packrat/desc/modelr": { "checksum": "d13c11b438394f2bdbb6d4613c978f7d" }, "packrat/desc/munsell": { "checksum": "a8e48b09ee8b89e3dc086518fe80c446" }, "packrat/desc/nlme": { "checksum": "26ab45f32f55da63981bdb14dcff517b" }, "packrat/desc/openssl": { "checksum": "9ee8514bfba8d26ba4b2a6e486c75166" }, "packrat/desc/pillar": { "checksum": "887e58de7f9006be6b8f9dc767cf7b6a" }, "packrat/desc/pkgbuild": { "checksum": "34b1296f294e89201507d6c438d496dd" }, "packrat/desc/pkgconfig": { "checksum": "49baa4035daf4dce7354bea49d40c39f" }, "packrat/desc/pkgload": { "checksum": "bb28df23b5f1bc9045914ea752b99e37" }, "packrat/desc/png": { "checksum": "70344b54a17228b7e16e44adbe5f7483" }, "packrat/desc/praise": { "checksum": "aa90c81d6e6d4df18ee22a6e4861bd56" }, "packrat/desc/prettyunits": { "checksum": "8d8e2b0f2372c91ffb7d99a09cfc1e91" }, "packrat/desc/processx": { "checksum": "81c6ab0adca7520db0b397908776ec3d" }, "packrat/desc/progress": { "checksum": "253c764d955887ddaeb1e05ec3bbcc5f" }, "packrat/desc/promises": { "checksum": "f5a3fdc54100d6f37a528c6fc16ac327" }, "packrat/desc/ps": { "checksum": "01e1fc56d9eb4ba4f323fa96d70087a3" }, "packrat/desc/purrr": { "checksum": "dc7b76467dac0b72eb26fb7ef087f72d" }, "packrat/desc/R6": { "checksum": "4a15232c3891b4cf09fb0a70c7d397ea" }, "packrat/desc/raster": { "checksum": "83b22d2f6dd934223e42af5f92367e6e" }, "packrat/desc/RColorBrewer": { "checksum": "b9f97c0919b8108a4908d5805327be80" }, "packrat/desc/Rcpp": { "checksum": "8830ff847a9409ebc33ecddc2a8b81b7" }, "packrat/desc/readr": { "checksum": "8ca27126bd55ccd5971a324ddd7d75ae" }, "packrat/desc/readxl": { "checksum": "d743665ea9b4f84942a65b1ea240392e" }, "packrat/desc/rematch": { "checksum": "9cfde0654cc144e6497e754c00a7bd65" }, "packrat/desc/reprex": { "checksum": "fa97887a2a889fa0cad61a2fba765ac2" }, "packrat/desc/rlang": { "checksum": "c783d2354a07907eda43d553170b3982" }, "packrat/desc/rmarkdown": { "checksum": "abc1793fb96ca84c743ab10aee5c5614" }, "packrat/desc/rprojroot": { "checksum": "4a84dfdec5a01077079f1d25333b618c" }, "packrat/desc/rstudioapi": { "checksum": "05414389b109accb441c856c2320a027" }, "packrat/desc/rvest": { "checksum": "a3124cffaba2de353354121195118007" }, "packrat/desc/scales": { "checksum": "07c82555a718ce584c16aa26ad637ec0" }, "packrat/desc/selectr": { "checksum": "b8df16901353eec3a0978622ac6c47c5" }, "packrat/desc/sf": { "checksum": "75f9c5956a204a13970e7e6a791b28fa" }, "packrat/desc/shiny": { "checksum": "093144412b3ce4c58484ac5ee525e76d" }, "packrat/desc/sourcetools": { "checksum": "f7340112890e5adf31191886311ce407" }, "packrat/desc/sp": { "checksum": "53bfbf1b903dde86e8d7a2a98f934cbd" }, "packrat/desc/spData": { "checksum": "20d8c40f836feb6884c749cd7d8450e5" }, "packrat/desc/stringi": { "checksum": "32fc8da6259c5d874d0e90ee6fd96fa5" }, "packrat/desc/stringr": { "checksum": "17370b5837f7d80dd997c10a507dd594" }, "packrat/desc/sys": { "checksum": "3e0db7fb9ceaff27bdab039f50326a86" }, "packrat/desc/testthat": { "checksum": "4747a980249e59efb7a8053a08a4b30b" }, "packrat/desc/tibble": { "checksum": "4b3e39ef9d489b2495a2523eaeaa664f" }, "packrat/desc/tidyr": { "checksum": "3277cc19bc1098d258080e8c26d0651c" }, "packrat/desc/tidyselect": { "checksum": "729ae81741a5b4602bd316e48ea8e56f" }, "packrat/desc/tidyverse": { "checksum": "d445975a37fbaf2bafe69513296d862b" }, "packrat/desc/tinytex": { "checksum": "d69791564726c3750d048753c135d7ee" }, "packrat/desc/units": { "checksum": "077373d1b791d88909e5e3478ac27f00" }, "packrat/desc/utf8": { "checksum": "8ab05fd6ff4eb44e2935892fb738343e" }, "packrat/desc/vctrs": { "checksum": "262aa45440edbd0276facfa463ce7410" }, "packrat/desc/viridis": { "checksum": "c02c2e7772127b96bb167dc99ad0f4cd" }, "packrat/desc/viridisLite": { "checksum": "16e31d1ad63d0da5620afd322b9bfbff" }, "packrat/desc/whisker": { "checksum": "a721279133357560db87f8721a62fe20" }, "packrat/desc/withr": { "checksum": "e68721d461de47b96dec143dd5ba7168" }, "packrat/desc/xfun": { "checksum": "bfc81af9183960bb5291fffabb10c403" }, "packrat/desc/xml2": { "checksum": "55deabe75b7bcf222e3afe395c2c4b18" }, "packrat/desc/xtable": { "checksum": "7b24e68901661ca611999f1af93b52bd" }, "packrat/desc/yaml": { "checksum": "ed0e10a2df0ee4a8aad9d96494b4d565" }, "packrat/packrat.lock": { "checksum": "ee1750f1ca2fc960b9a05009ebce1577" } }, "users": null } ================================================ FILE: benchmarks.csv ================================================ command,date_benchmarked,build_time,platform,cpu_model,ram,commit,commit_date,laptop_or_desktop,comments bookdown::render_book(),2023-04-08T16:18:23Z,529.794,x86_64-pc-linux-gnu,12th Gen Intel(R) Core(TM) i5-1240P,67126108160,9651afe2076ce690fc241bf4ca5d353c067f6caf,2023-04-06T09:02:01Z,NA,devcontainer ================================================ FILE: code/01-cranlogs.R ================================================ # Code to download logs of various packages # By Robin Lovelace and Colin Gillespie: # https://github.com/csgillespie/efficientR # Edited by Jakub Nowosad # attach the packages ----------------------------------------------------- library(cranlogs) library(tidyverse) library(ggplot2) options(scipen = 99) # which packages to track: pkgs = c("sp", "raster", "sf", "terra", "stars") # read packages downloads ------------------------------------------------- # and calculate rolling median dd_top = cran_downloads(packages = pkgs, from = "2013-01-01", to = Sys.Date()) |> group_by(package) |> mutate(Downloads = zoo::rollmedian(count, k = 91, na.pad = TRUE)) # plot and save ----------------------------------------------------------- ggfig = ggplot(data = dd_top, mapping = aes(date, Downloads, color = package)) + geom_line() + labs(x = "Date", color = "Package: ") + scale_color_brewer(type = "qual", palette = "Set1") + theme_bw() + scale_y_log10(limits = c(10, NA)) # ggfig ggsave("images/01-cranlogs.png", ggfig, width = 6, height = 3, dpi = 150) # magick::image_read("images/spatial-package-growth.png") ================================================ FILE: code/01-sf-revdep.R ================================================ library(tidyverse) sf_revdeps = devtools::revdep("sf", dependencies = c("Depends", "Imports")) sf_revdeps_dls = cranlogs::cran_downloads(packages = sf_revdeps, when = "last-month") top_dls = sf_revdeps_dls |> group_by(package) |> summarise(Downloads = round(mean(count)), date = max(date)) |> arrange(desc(Downloads)) write_csv(top_dls, "extdata/top_dls.csv") ================================================ FILE: code/02-contpop.R ================================================ library(sf) library(spData) world_proj = st_transform(world, "+proj=eck4") world_cents = st_centroid(world_proj, of_largest_polygon = TRUE) par(mar = c(0, 0, 0, 0)) plot(world_proj["continent"], reset = FALSE, main = "", key.pos = NULL) g = st_graticule() g = st_transform(g, crs = "+proj=eck4") plot(g$geometry, add = TRUE, col = "lightgray") cex = sqrt(world$pop) / 10000 plot(st_geometry(world_cents), add = TRUE, cex = cex, lwd = 2, graticule = TRUE) ================================================ FILE: code/02-datum-fig.R ================================================ library(grid) library(gridExtra) library(jpeg) library(PlaneGeometry) library(ggplot2) geo_dat = Ellipse$new(center = c(0.5, 0.5), rmajor = 1.02, rminor = 1, alpha = 0, degrees = TRUE) geo_dat = geo_dat$path() geo_dat = as.data.frame(rbind(geo_dat, geo_dat[1, ])) geoid = readJPEG("images/geoid.jpg") geoid = abind::abind(geoid, geoid[,,1]) # add an alpha channel geoid[,,4] = 0.5 pals = palette.colors(8) gg_geo = ggplot() + geom_path(data = geo_dat, aes(x = x, y = y), color = pals[1], size = 1.2) + geom_vline(xintercept = 0.5, color = pals[1]) + geom_hline(yintercept = 0.5, color = pals[1]) + annotate(geom = "curve", xend = -0.11, yend = 1.3, x = -0.31, y = 1.4, curvature = -0.3, arrow = arrow(length = unit(2, "mm")), color = pals[1]) + annotate(geom = "text", x = -0.31, y = 1.4, label = "Geocentric\ndatum", hjust = "right", color = pals[1]) + coord_equal(clip = "off") + theme_void() gg_loc = ggplot() + geom_path(data = geo_dat, aes(x = x, y = y), color = pals[8], linetype = 2, size = 1.2) + annotate(geom = "curve", xend = 1.53, yend = 0.5, x = 1.7, y = 0, curvature = 0.3, arrow = arrow(length = unit(2, "mm")), color = pals[8]) + annotate(geom = "text", x = 1.7, y = 0, label = "Local\ndatum", hjust = "right", color = pals[8]) + coord_equal(clip = "off") + theme_void() vp_geo = viewport(0.5, 0.5, width = 0.9 * 1.06, height = 0.9 * 1.06) vp_loc = viewport(0.53, 0.525, width = 0.9 * 1.06, height = 0.9 * 1.06) png("images/02_datum_fig.png", width = 831*3, height = 425*3, res = 300) grid.newpage() grid.raster(geoid, interpolate = TRUE, height = 0.9) print(gg_geo, vp = vp_geo) print(gg_loc, vp = vp_loc) dev.off() # system("optipng images/02_datum_fig.png") ================================================ FILE: code/02-raster-crs.R ================================================ library(terra) library(rcartocolor) library(tmap) raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") new_raster = rast(raster_filepath) new_raster2 = project(new_raster, "EPSG:26912") tm1 = tm_shape(new_raster) + tm_graticules(n.x = 3, n.y = 4) + tm_raster(palette = carto_pal(7, "Geyser"), style = "cont", legend.show = FALSE) + tm_layout(inner.margins = 0) + tm_ylab("Latitude", space = 0.5) + tm_xlab("Longitude") tm2 = tm_shape(new_raster2) + tm_grid(n.x = 3, n.y = 4) + tm_raster(palette = carto_pal(7, "Geyser"), style = "cont", legend.show = FALSE) + tm_layout(inner.margins = 0) + tm_ylab("y") + tm_xlab("x") tm = tmap_arrange(tm1, tm2) tmap_save(tm, "images/02_raster_crs.png", width = 950*1.5, height = 532*1.5, dpi = 150, scale = 1.5) ================================================ FILE: code/02-raster-intro-plot.R ================================================ # first intro plot ----------------------------------------------------------- library(terra) library(sf) library(tmap) library(spData) set.seed(2021-09-09) small_ras = rast(matrix(1:16, 4, 4, byrow = TRUE)) crs(small_ras) = "EPSG:4326" polys = st_as_sf(as.polygons(small_ras, na.rm = FALSE)) polys$lyr.1 = as.character(polys$lyr.1) polys$vals = sample.int(100, 16) polys$vals[c(7, 9)] = "NA" suppressWarnings({polys$valsn = as.numeric(polys$vals)}) tm1 = tm_shape(polys) + tm_borders(col = "black") + tm_text(text = "lyr.1") + tm_title("A. Cell IDs") + tm_layout(frame = FALSE) tm2 = tm_shape(polys) + tm_borders(col = "black") + tm_text(text = "vals") + tm_title("B. Cell values") + tm_layout(frame = FALSE) tm3 = tm_shape(polys) + tm_fill(fill = "valsn", fill.scale = tm_scale(values = "RdBu", value.na = "white"), fill.legend = tm_legend(show = FALSE)) + tm_title("C. Colored values") + tm_layout(frame = FALSE) tmap_arrange(tm1, tm2, tm3, nrow = 1) ================================================ FILE: code/02-raster-intro-plot2.R ================================================ # second intro plot ----------------------------------------------------------- library(tmap) library(rcartocolor) library(spDataLarge) library(terra) # data read --------------------------------------------------------------- cla_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) cat_raster = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) # plots create ------------------------------------------------------------ rast_srtm = tm_shape(cla_raster) + tm_raster(col.scale = tm_scale_continuous(values = carto_pal(7, "Geyser")), col.legend = tm_legend("Elevation (m)")) + tm_title("A. Continuous data") + tm_layout(legend.frame = TRUE, legend.bg.color = "white", legend.position = c("RIGHT", "BOTTOM")) rast_nlcd = tm_shape(cat_raster) + tm_raster(col.scale = tm_scale_categorical(levels.drop = TRUE), col.legend = tm_legend("Land cover")) + tm_title("B. Categorical data") + tm_layout(legend.frame = TRUE, legend.bg.color = "white", legend.position = c("RIGHT", "BOTTOM")) tmap_arrange(rast_srtm, rast_nlcd, nrow = 1) ================================================ FILE: code/02-sfdiagram.R ================================================ library(DiagrammeR) library(DiagrammeRsvg) save_png = function(plot, path){ par(bg = NA) DiagrammeRsvg::export_svg(plot) %>% charToRaw() %>% rsvg::rsvg() %>% png::writePNG(path) } sf_diagram = grViz("digraph { graph [layout = dot, rankdir = LR] node [shape = rectangle] rec2 [label = 'sfg'] rec4 [label = 'sfc'] rec6 [label = 'sf'] rec7 [label = 'data.frame'] node [shape = diamond] rec1 [label = 'st_point()\nst_linestring()\n...'] rec3 [label = 'st_sfc()'] rec5 [label = 'st_sf()'] # edge definitions with the node IDs rec1 -> rec2 -> rec3 -> rec4 -> rec5 -> rec6 rec7 -> rec5 }", height = 100) save_png(sf_diagram, "images/02-sfdiagram.png") knitr::plot_crop("images/02-sfdiagram.png") ================================================ FILE: code/02-sfheaders.R ================================================ # Aim: compare sf vs. sfheaders in terms of speed library(spData) library(sf) # proof of concept world_df = sf::st_coordinates(world) head(world_df) summary(world_df) # what does each mean? # world_df_split = split(world_df, world_df[, "L1"]) # how to reassemble? world_df_sfh = sfheaders::sf_to_df(world) head(world_df_sfh) world_sfh = sfheaders::sf_multipolygon(world_df_sfh) world_sfh = sfheaders::sf_multipolygon(world_df_sfh, x = "x", y = "y", polygon_id = "", multipolygon_id = "multipolygon_id") length(world$geom) length(world_sfh$geometry) waldo::compare(world$geom[1], world_sfh$geometry[1]) # how to get the same object back? world$geom[1][[1]][[2]] world_sfh$geom[1][[1]][[2]] plot(world$geom[1]) plot(world_sfh$geometry[1]) bench::mark( check = FALSE, sf = sf::st_coordinates(world), sfheaders = sfheaders::sf_to_df(world) ) # # A tibble: 2 × 13 # expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc total_time result memory time gc # # 1 sf 4.74ms 4.91ms 198. 1.86MB 11.6 85 5 430ms # 2 sfheaders 1.01ms 1.08ms 894. 2.1MB 56.6 142 9 159ms library(sf) nc = sf::st_read(system.file("./shape/nc.shp", package = "sf")) # sf::st_cast(nc, "LINESTRING") ## Error sfheaders::sf_cast(nc, "LINESTRING") # And where it is comparible between sf and sfheaders, the latter is faster bench::mark(check = FALSE, sf = { sf::st_cast(nc, "POINT") }, sfheaders = { sfheaders::sf_cast(nc, "POINT") } ) ================================================ FILE: code/02-vector-crs.R ================================================ library(tmap) library(sf) vector_filepath = system.file("vector/zion.gpkg", package = "spDataLarge") new_vector = read_sf(vector_filepath) new_vector2 = st_transform(new_vector, "EPSG:4326") tm1 = tm_shape(new_vector2) + tm_graticules(n.x = 3, n.y = 4) + tm_polygons() + tm_ylab("Latitude", space = 0.5) + tm_xlab("Longitude") tm2 = tm_shape(new_vector) + tm_grid(n.x = 3, n.y = 4) + tm_polygons() + tm_ylab("y") + tm_xlab("x") tm = tmap_arrange(tm1, tm2) tmap_save(tm, "images/02_vector_crs.png", width = 950*1.5, height = 532*1.5, dpi = 150, scale = 1.5) if (packageVersion("tmap") >= "4.0"){ #toDo -- tm_ylab and tm_xlab does not exist tm1 = tm_shape(new_vector2) + tm_graticules(n.x = 3, n.y = 4) + tm_polygons() + tm_ylab("Latitude", space = 0.5) + tm_xlab("Longitude") tm2 = tm_shape(new_vector) + tm_grid(n.x = 3, n.y = 4) + tm_polygons() + tm_ylab("y") + tm_xlab("x") tm = tmap_arrange(tm1, tm2) tmap_save(tm, "images/02_vector_crs.png", width = 950*1.5, height = 532*1.5, dpi = 150, scale = 1.5) } ================================================ FILE: code/02-vectorplots.R ================================================ library(globe) library(dplyr) library(sf) london_lonlat = st_point(c(-0.1, 51.5)) %>% st_sfc() %>% st_sf(crs = 4326, geometry = .) london_osgb = st_transform(london_lonlat, 27700) origin_osgb = st_point(c(0, 0)) %>% st_sfc() %>% st_sf(crs = 27700, geometry = .) london_orign = rbind(london_osgb, origin_osgb) png("images/vector_lonlat.png") globe::globeearth(eye = c(0, 0)) gratmat = st_coordinates(st_graticule())[, 1:2] globe::globelines(loc = gratmat, col = "gray", lty = 3) globe::globelines(loc = matrix(c(-90, 90, 0, 0), ncol = 2)) globe::globelines(loc = matrix(c(0, 0, -90, 90), ncol = 2)) globe::globepoints(loc = c(-0.1, 51.5), pch = 4, cex = 2, lwd = 3, col = "red") globe::globepoints(loc = c(0, 0), pch = 1, cex = 2, lwd = 3, col = "blue") dev.off() png("images/vector_projected.png") uk = rnaturalearth::ne_countries(scale = 50) %>% st_as_sf() %>% filter(grepl(pattern = "United Kingdom|Ire", x = name_long)) %>% st_transform(27700) plot(uk$geometry) plot(london_orign$geometry[1], add = TRUE, pch = 4, cex = 2, lwd = 3, col = "red") plot(london_orign$geometry[2], add = TRUE, pch = 1, cex = 2, lwd = 3, col = "blue") abline(h = seq(0, 9e5, length.out = 10), col = "gray", lty = 3) abline(v = seq(0, 9e5, length.out = 10), col = "gray", lty = 3) dev.off() ================================================ FILE: code/03-cont-raster-plot.R ================================================ library(tmap) library(terra) elev = rast(system.file("raster/elev.tif", package = "spData")) grain = rast(system.file("raster/grain.tif", package = "spData")) colfunc2 = c("clay" = "brown", "silt" = "sandybrown", "sand" = "rosybrown") p1 = tm_shape(elev) + tm_raster(col.scale = tm_scale_continuous(), col.legend = tm_legend(title = "", position = tm_pos_in(), bg.color = "white")) p2 = tm_shape(grain) + tm_raster(col.scale = tm_scale_categorical(values = colfunc2), col.legend = tm_legend(title = "", position = tm_pos_in(), bg.color = "white")) tmap_arrange(p1, p2, nrow = 1) ================================================ FILE: code/04-areal-example.R ================================================ library(sf) library(tmap) library(spData) rx = rbind(congruent, incongruent) # tmap_mode("plot") m1 = tm_shape(rx) + tm_fill("value", fill.scale = tm_scale(breaks = seq(3.5, 7, by = 0.5))) + tm_borders(lwd = 1, col = "black", lty = 1) + tm_facets(by = "level", drop.units = TRUE, ncol = 2) + tm_shape(aggregating_zones) + tm_borders(col_alpha = 0.2, lwd = 8, col = "red") + tm_layout(legend.show = FALSE, scale = 1) m1 ================================================ FILE: code/04-focal-example.R ================================================ library(tmap) library(sf) library(terra) library(grid) elev = rast(system.file("raster/elev.tif", package = "spData")) elev[1, 1] = 0 poly_window = rbind(c(-1.5, 0), c(0,0), c(0, 1.5), c(-1.5, 1.5), c(-1.5, 0)) |> list() |> st_polygon() |> st_sfc() |> st_sf(data.frame(id = 1), geometry = _, crs = "EPSG:4326") poly_target = rbind(c(-1, 0.5), c(-0.5, 0.5), c(-0.5, 1), c(-1, 1), c(-1, 0.5)) |> list() |> st_polygon() |> st_sfc() |> st_sf(data.frame(id = 1), geometry = _, crs = "EPSG:4326") polys = st_as_sf(terra::as.polygons(elev, na.rm = FALSE, dissolve = FALSE)) r_focal = focal(elev, w = matrix(1, nrow = 3, ncol = 3), fun = min) poly_focal = st_as_sf(terra::as.polygons(r_focal, na.rm = FALSE, dissolve = FALSE)) poly_focal$focal_min[is.nan(poly_focal$focal_min)] = NA poly_focal$focal_min2 = as.character(poly_focal$focal_min) poly_focal$focal_min2[is.na(poly_focal$focal_min2)] = "NA" tm1 = tm_shape(polys) + tm_polygons(fill = "elev", fill.scale = tm_scale_continuous(), lwd = 0.5) + tm_text(text = "elev") + tm_shape(poly_target) + tm_borders(lwd = 3, col = "orange") + tm_shape(poly_window) + tm_borders(lwd = 6, col = "orange") + tm_layout(frame = FALSE, legend.show = FALSE) tm2 = tm_shape(poly_focal) + tm_polygons(fill = "focal_min", fill.scale = tm_scale_continuous(), lwd = 0.5) + tm_text(text = "focal_min2") + tm_shape(poly_target) + tm_borders(lwd = 3, col = "orange") + tm_layout(frame = FALSE, legend.show = FALSE) png(filename = "images/04_focal_example.png", width = 950, height = 555) tmap_arrange(tm1, tm2) grid.polyline(x = c(0.255, 0.59), y = c(0.685, 0.685), arrow = arrow(length = unit(0.2, "inches")), gp = gpar(lwd = 2)) dev.off() ================================================ FILE: code/04-local-operations.R ================================================ library(tmap) library(terra) elev = rast(system.file("raster/elev.tif", package = "spData")) elev_poly = st_as_sf(as.polygons(elev)) elev2 = elev + elev; elev_poly2 = st_as_sf(as.polygons(elev2, dissolve = FALSE)) elev3 = elev^2; elev_poly3 = st_as_sf(as.polygons(elev3, dissolve = FALSE)) elev4 = log(elev); elev_poly4 = st_as_sf(as.polygons(elev4, dissolve = FALSE)) elev5 = elev > 5; elev_poly5 = st_as_sf(as.polygons(elev5, dissolve = FALSE)) tm1 = tm_shape(elev_poly) + tm_polygons(fill = "elev", lwd = 0.5, fill.scale = tm_scale_continuous(), fill.legend = tm_legend(title = "", orientation = "landscape", width = 10, text.size = 0.8, position = tm_pos_out(cell.h = "center", cell.v = "bottom"))) + tm_title("elev") + tm_layout(frame = FALSE) tm2 = tm_shape(elev_poly2) + tm_polygons(fill = "elev", lwd = 0.5, fill.scale = tm_scale_continuous(), fill.legend = tm_legend(title = "", orientation = "landscape", width = 10, text.size = 0.8, position = tm_pos_out(cell.h = "center", cell.v = "bottom"))) + tm_title("elev + elev") + tm_layout(frame = FALSE) tm3 = tm_shape(elev_poly3) + tm_polygons(fill = "elev", lwd = 0.5, fill.scale = tm_scale_continuous(), fill.legend = tm_legend(title = "", orientation = "landscape", width = 10, text.size = 0.8, position = tm_pos_out(cell.h = "center", cell.v = "bottom"))) + tm_title("elev^2") + tm_layout(frame = FALSE) tm4 = tm_shape(elev_poly4) + tm_polygons(fill = "elev", lwd = 0.5, fill.scale = tm_scale_continuous(), fill.legend = tm_legend(title = "", orientation = "landscape", width = 10, text.size = 0.8, position = tm_pos_out(cell.h = "center", cell.v = "bottom"))) + tm_title("log(elev)") + tm_layout(frame = FALSE) tm5 = tm_shape(elev_poly5) + tm_polygons(fill = "elev", lwd = 0.5, fill.scale = tm_scale_categorical(), fill.legend = tm_legend(title = "", orientation = "landscape", width = 10, text.size = 0.8, position = tm_pos_out(cell.h = "center", cell.v = "bottom"))) + tm_title("elev > 5") + tm_layout(frame = FALSE) tma = tmap_arrange(tm2, tm3, tm4, tm5, nrow = 1) tmap_save(tma, "images/04-local-operations.png", height = 700, width = 2000) ================================================ FILE: code/04-ndvi.R ================================================ library(tmap) library(terra) multi_raster_file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(multi_raster_file) multi_rast = (multi_rast * 0.0000275) - 0.2 multi_rast[multi_rast < 0] = 0 ndvi_fun = function(nir, red){ (nir - red) / (nir + red) } ndvi_rast = lapp(multi_rast[[c(4, 3)]], fun = ndvi_fun) multi_rast2 = stretch(multi_rast, maxq = 0.98) tm1 = tm_shape(multi_rast2[[3:1]]) + tm_rgb(tm_mv("landsat_3", "landsat_2", "landsat_1"), col.scale = tm_scale_rgb(maxValue = 255)) + tm_title("RGB image") + tm_layout(frame = FALSE) tm2 = tm_shape(ndvi_rast) + tm_raster(col.scale = tm_scale_continuous(), col.legend = tm_legend(title = "", reverse = TRUE, text.size = 0.5)) + tm_title("NDVI") + tm_layout(frame = FALSE, legend.frame = TRUE, legend.position = c("left", "bottom"), legend.bg.color = "white") tma = tmap_arrange(tm1, tm2, nrow = 1) tmap_save(tma, "images/04-ndvi.png", height = 800*2, width = 1100*2) ================================================ FILE: code/04-raster-subset.R ================================================ library(tmap) library(sf) library(terra) set.seed(2023-03-10) elev = rast(system.file("raster/elev.tif", package = "spData")) # subsetting -------------------------------------------------------------- clip = rast(nrow = 3, ncol = 3, resolution = 0.3, xmin = 0.9, xmax = 1.8, ymin = -0.45, ymax = 0.45, vals = rep(1, 9)) elev_poly = st_as_sf(as.polygons(elev)) clip_poly = st_as_sf(as.polygons(clip, dissolve = FALSE)) # masking ----------------------------------------------------------------- r_mask = rast(nrow = 6, ncol = 6, resolution = 0.5, xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, vals = sample(c(FALSE, TRUE), 36, replace = TRUE)) masked = elev[r_mask, drop = FALSE] r_mask2 = r_mask r_mask2[!r_mask] = NA r_mask_poly = st_as_sf(as.polygons(r_mask2, dissolve = FALSE)) masked_poly = st_as_sf(as.polygons(masked)) tm1 = tm_shape(elev_poly) + tm_polygons(fill = "elev", lwd = 0.5) + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = c(0, 0, 0, 0.1)) tm2 = tm_shape(r_mask_poly) + tm_polygons(lwd = 0.5) + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = c(0, 0, 0, 0.1)) tm3 = tm_shape(masked_poly) + tm_polygons(fill = "elev", lwd = 0.5) + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = c(0, 0, 0, 0.1)) tma = tmap_arrange(tm1, tm2, tm3, nrow = 1) tmap_save(tma, "images/04_raster_subset.png", width = 7.5, height = 3, dpi = 300) ================================================ FILE: code/04-spatial-join.R ================================================ # Aim: demonstrate spatial joins ------------------------------------------ library(sf) library(spData) library(tmap) # names(world) # names(urban_agglomerations) # Question arising from the data: # what % of country populations lived in their largest agglomerations? # explanation: we're joining the point data onto world if (!exists("random_joined")) { set.seed(2018) bb = st_bbox(world) random_df = tibble::tibble( x = runif(n = 10, min = bb[1], max = bb[3]), y = runif(n = 10, min = bb[2], max = bb[4]) ) random_points = st_as_sf(random_df, coords = c("x", "y")) |> st_set_crs("EPSG:4326") world_random = world[random_points, ] random_joined = st_join(random_points, world["name_long"]) } # summary(random_joined$name_long) # factors still there random_joined$name_long = as.character(random_joined$name_long) jm0 = tm_shape(world) + tm_borders(lwd = 0.2) + tm_format("World") jm1 = jm0 + tm_shape(shp = random_points, bbox = bb) + tm_symbols(col = "black", shape = 4, lwd = 3) jm2 = jm0 + tm_shape(world_random, bbox = bb) + tm_fill(fill = "name_long", fill.scale = tm_scale(values = "Dark2")) + tm_layout(legend.show = FALSE) jm3 = jm0 + tm_shape(shp = random_joined, bbox = bb) + tm_symbols(col = "name_long", shape = 4, lwd = 3, col.scale = tm_scale(values = "Dark2")) + tm_layout(legend.show = FALSE) jm4 = jm0 + tm_shape(shp = random_joined, bbox = bb) + tm_symbols(col = "name_long", shape = 4, lwd = 3, col.scale = tm_scale(values = "Dark2")) + tm_layout(legend.only = TRUE) tmap_arrange(jm1, jm2, jm3, jm4, nrow = 2, ncol = 2) ================================================ FILE: code/05-bilinear.R ================================================ library(tmap) library(sf) library(terra) elev = rast(system.file("raster/elev.tif", package = "spData")) elev_agg = aggregate(elev, fact = 2, fun = mean) elev_poly1 = st_as_sf(as.polygons(elev, dissolve = FALSE)[1]) elev_poly1_cen = st_centroid(elev_poly1) elev_points4 = st_as_sf(as.points(elev_agg)[c(1, 2, 4, 5)]) xy_1 = xyFromCell(elev, 1) xy_2 = xyFromCell(elev_agg, c(1, 2, 4, 5)) elev_lines1 = st_linestring(rbind(xy_1, xy_2[1, ])) elev_lines2 = st_linestring(rbind(xy_1, xy_2[2, ])) elev_lines3 = st_linestring(rbind(xy_1, xy_2[3, ])) elev_lines4 = st_linestring(rbind(xy_1, xy_2[4, ])) elev_lines_all = st_sfc(elev_lines1, elev_lines2, elev_lines3, elev_lines4) st_crs(elev_lines_all) = st_crs(elev) tm_shape(elev_agg) + tm_raster(col.scale = tm_scale_continuous(), col.legend = tm_legend("")) + tm_shape(elev_lines_all) + tm_lines() + tm_shape(elev_poly1) + tm_borders(lwd = 2) + tm_shape(elev_points4) + tm_symbols(size = 1.1, shape = 21, fill = "orange") + tm_shape(elev_poly1_cen) + tm_symbols(size = 1.2, shape = 4) + tm_layout(frame = FALSE) ================================================ FILE: code/05-extend-example.R ================================================ library(terra) library(sf) library(tmap) elev = rast(system.file("raster/elev.tif", package = "spData")) elev2 = extend(elev, c(1, 2)) elev_poly = st_as_sf(as.polygons(elev, dissolve = FALSE)) elev2_poly = st_as_sf(as.polygons(elev2, na.rm = FALSE, dissolve = FALSE)) tm1 = tm_shape(elev_poly, bbox = elev2_poly) + tm_polygons(fill = "elev") + tm_layout(frame = FALSE, legend.show = FALSE) tm2 = tm_shape(elev2_poly) + tm_polygons(fill = "elev") + tm_layout(frame = FALSE, legend.show = FALSE) tmap_arrange(tm1, tm2, nrow = 1) ================================================ FILE: code/05-us-regions.R ================================================ library(tmap) library(spData) library(dplyr) library(sf) regions = aggregate(x = us_states[, "total_pop_15"], by = list(us_states$REGION), FUN = sum, na.rm = TRUE) us_states_facet = select(us_states, REGION, total_pop_15) |> mutate(Level = "State") regions_facet = dplyr::rename(regions, REGION = Group.1) |> mutate(Level = "Region") us_facet = rbind(us_states_facet, regions_facet) |> mutate(Level = factor(Level, levels = c("State", "Region"))) |> st_cast("MULTIPOLYGON") tm_shape(us_facet) + tm_polygons("total_pop_15", fill.legend = tm_legend("Total population:")) + tm_facets(by = "Level", ncol = 2, drop.units = TRUE) ================================================ FILE: code/05-venn-clip.R ================================================ if (!exists("b")) { library(sf) b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles l = c("x", "y") x = b[1] y = b[2] x_and_y = st_intersection(x, y) } old_par = par(mfrow = c(2, 3), mai = c(0.1, 0.1, 0.1, 0.1)) plot(b, border = "gray") plot(x, add = TRUE, col = "lightgray", border = "gray") text(cex = 1.8, x = 0.5, y = 1, "x") plot(b, add = TRUE, border = "gray") x_not_y = st_difference(x, y) plot(b, border = "gray") plot(x_not_y, col = "lightgray", add = TRUE, border = "gray") text(cex = 1.8, x = 0.5, y = 1, "st_difference(x, y)") y_not_x = st_difference(y, x) plot(b, border = "gray") plot(y_not_x, col = "lightgray", add = TRUE, border = "gray") text(cex = 1.8, x = 0.5, y = 1, "st_difference(y, x)") x_or_y = st_union(x, y) plot(x_or_y, col = "lightgray", border = "gray") text(cex = 1.8, x = 0.5, y = 1, "st_union(x, y)") x_and_y = st_intersection(x, y) plot(b, border = "gray") plot(x_and_y, col = "lightgray", add = TRUE, border = "gray") text(cex = 1.8, x = 0.5, y = 1, "st_intersection(x, y)") # x_xor_y = st_difference(x_xor_y, x_and_y) # failing x_xor_y = st_sym_difference(x, y) plot(x_xor_y, col = "lightgray", border = "gray") text(cex = 1.8, x = 0.5, y = 1, "st_sym_difference(x, y)") # plot.new() # plot(b, border = "gray") # plot(y, col = "lightgray", add = TRUE, border = "gray") # plot(b, add = TRUE, border = "gray") # text(cex = 1.2, x = 0.5, y = 1, "y") par(old_par) ================================================ FILE: code/06-contour-tmap.R ================================================ library(tmap) library(sf) library(terra) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) # create hillshade hs = shade(slope = terrain(dem, "slope", unit = "radians"), aspect = terrain(dem, "aspect", unit = "radians")) # https://github.com/rspatial/terra/issues/948#issuecomment-1356226265 # h = shade(slope = terrain(dem, "slope", unit = "radians"), # aspect = terrain(dem, "aspect", unit = "radians"), # angle = c(45, 45, 45), direction = c(0, 45, 315)) # h = Reduce(mean, h) # create contour cn = st_as_sf(as.contour(dem)) # toDo: jn # tm_iso does not exist tm1 = tm_shape(hs) + tm_grid(col = "black", n.x = 2, n.y = 2, labels.rot = c(0, 90)) + tm_raster(col.scale = tm_scale(values = gray(0:100 / 100), n = 100)) + tm_shape(dem) + tm_raster(col_alpha = 0.6, col.scale = tm_scale(values = hcl.colors(25, "Geyser"))) + tm_shape(cn) + tm_lines(col = "white") + tm_text("level") + #tm_shape(cn) + #tm_iso("level", col = "white") + tm_layout(outer.margins = c(0.04, 0.04, 0.02, 0.02), frame = FALSE, legend.show = FALSE) tmap_save(tm1, "images/05-contour-tmap.png", height = 1000, width = 1000) ================================================ FILE: code/06-pointextr.R ================================================ library(tmap) library(terra) library(sf) terrain_colors = rcartocolor::carto_pal(7, "Geyser") srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) zion = st_transform(zion, crs(srtm)) data("zion_points", package = "spDataLarge") tm1 = tm_shape(srtm) + tm_raster(col.scale = tm_scale_continuous(values = terrain_colors), col.legend = tm_legend("Elevation (m asl)")) + tm_shape(zion) + tm_borders(lwd = 2) + tm_shape(zion_points) + tm_symbols(fill = "black", size = 0.5) + tm_add_legend(type = "symbols", fill = "black", size = 0.7, labels = "zion_points", shape = 21) tm1 ================================================ FILE: code/06-raster-vectorization1.R ================================================ library(tmap) library(spData) library(terra) elev = rast(system.file("raster/elev.tif", package = "spData")) elev_point = as.points(elev) |> st_as_sf() p1 = tm_shape(elev) + tm_raster(col.scale = tm_scale(n = 36)) + tm_title("A. Raster") + tm_layout(outer.margins = rep(0.01, 4), inner.margins = rep(0, 4), legend.show = FALSE) p2 = tm_shape(elev_point) + tm_symbols(fill = "elev", fill.scale = tm_scale(n = 36), size = 2) + tm_title("B. Points") + tm_layout(outer.margins = rep(0.01, 4), inner.margins = rep(0.09, 4), legend.show = FALSE) tmap_arrange(p1, p2, ncol = 2) ================================================ FILE: code/06-raster-vectorization2.R ================================================ library(tmap) library(spData) library(terra) grain = rast(system.file("raster/grain.tif", package = "spData")) grain_poly = as.polygons(grain, dissolve = FALSE) %>% st_as_sf() grain_poly2 = as.polygons(grain) %>% st_as_sf() cols = c("clay" = "brown", "sand" = "rosybrown", "silt" = "sandybrown") p1p = tm_shape(grain) + tm_raster("grain", col.scale = tm_scale(values = cols)) + tm_title("A. Raster") + tm_layout(frame = FALSE, legend.show = FALSE) p2p = tm_shape(grain_poly) + tm_polygons("grain", fill.scale = tm_scale(values = cols), lwd = 3) + tm_title("B. Polygons") + tm_layout(frame = FALSE, legend.show = FALSE) p3p = tm_shape(grain_poly2) + tm_polygons("grain", fill.scale = tm_scale(values = cols), lwd = 3) + tm_title("C. Aggregated polygons") + tm_layout(frame = FALSE, legend.show = FALSE) tmap_arrange(p1p, p2p, p3p, ncol = 3) ================================================ FILE: code/06-vector-rasterization1.R ================================================ library(sf) library(tmap) library(spData) library(terra) if (!exists("cycle_hire_osm_projected")) { cycle_hire_osm_projected = st_transform(cycle_hire_osm, "EPSG:27700") raster_template = rast(ext(cycle_hire_osm_projected), resolution = 1000, crs = "EPSG:27700") ch_raster1 = rasterize(vect(cycle_hire_osm_projected), raster_template, field = 1) ch_raster2 = rasterize(vect(cycle_hire_osm_projected), raster_template, field = 1, fun = "length") ch_raster3 = rasterize(vect(cycle_hire_osm_projected), raster_template, field = "capacity", fun = sum, na.rm = TRUE) } r0p = tm_shape(cycle_hire_osm_projected) + tm_symbols(fill = "capacity", size = 0.3, fill.legend = tm_legend("Capacity: ")) + tm_title("A. Points") + tm_layout(legend.position = c("RIGHT", "BOTTOM"), legend.frame = TRUE, inner.margins = c(0.02, 0.02, 0.02, 0.35)) r1p = tm_shape(ch_raster1) + tm_raster(col.scale = tm_scale_categorical(values = cols4all::c4a(n = 1, "hcl.blues3", reverse = TRUE)), col.legend = tm_legend("Values: ")) + tm_title("B. Presence/absence") + tm_layout(legend.position = c("RIGHT", "BOTTOM"), legend.frame = TRUE, inner.margins = c(0.02, 0.02, 0.02, 0.35)) r2p = tm_shape(ch_raster2) + tm_raster(col.legend = tm_legend("Values: ")) + tm_title("C. Count") + tm_layout(legend.position = c("RIGHT", "BOTTOM"), legend.frame = TRUE, inner.margins = c(0.02, 0.02, 0.02, 0.35)) r3p = tm_shape(ch_raster3) + tm_raster(col.legend = tm_legend("Values: ")) + tm_title("D. Aggregated capacity") + tm_layout(legend.position = c("RIGHT", "BOTTOM"), legend.frame = TRUE, inner.margins = c(0.02, 0.02, 0.02, 0.35)) tmap_arrange(r0p, r1p, r2p, r3p, ncol = 2) ================================================ FILE: code/06-vector-rasterization2.R ================================================ library(tmap) if (!exists("raster_template2")) { library(sf) library(terra) library(spData) library(spDataLarge) california = dplyr::filter(us_states, NAME == "California") california_borders = st_cast(california, "MULTILINESTRING") raster_template2 = rast(ext(california), resolution = 0.5, crs = crs(california)) california_raster1 = rasterize(vect(california_borders), raster_template2, touches = TRUE) california_raster2 = rasterize(vect(california), raster_template2) } california_raster_centr = st_as_sf(as.polygons(raster_template2)) california_raster_centr = st_centroid(california_raster_centr) r1po = tm_shape(california_raster1) + tm_raster(col.legend = tm_legend("Values: "), col.scale = tm_scale(values = "#b6d8fc")) + tm_shape(california_raster_centr) + tm_symbols(shape = 20, col = "black", size = 0.2) + tm_shape(california) + tm_borders() + tm_title("A. Line rasterization") + tm_layout(legend.show = FALSE, frame = FALSE) r2po = tm_shape(california_raster2) + tm_raster(col.legend = tm_legend("Values: "), col.scale = tm_scale(values = "#b6d8fc")) + tm_shape(california_raster_centr) + tm_symbols(shape = 20, col = "black", size = 0.2) + tm_shape(california) + tm_borders() + tm_title("B. Polygon rasterization") + tm_layout(legend.show = FALSE, frame = FALSE) tmap_arrange(r1po, r2po, ncol = 2) ================================================ FILE: code/09-break-styles.R ================================================ library(tmap) library(spData) library(spDataLarge) # ?tmap_style_save m_equal = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale_intervals(style = "equal")) + tm_title('style = "equal"') + tm_layout(legend.position = tm_pos_auto_in(), title.position = tm_pos_in("right", "bottom"), scale = 0.8, inner.margins = c(0.15, 0.35, 0.02, 0.02)) m_pretty = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale_intervals(style = "pretty")) + tm_title('style = "pretty"')+ tm_layout(legend.position = tm_pos_auto_in(), title.position = tm_pos_in("right", "bottom"), scale = 0.8, inner.margins = c(0.15, 0.35, 0.02, 0.02)) m_quantile = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale_intervals(style = "quantile")) + tm_title('style = "quantile"')+ tm_layout(legend.position = tm_pos_auto_in(), title.position = tm_pos_in("right", "bottom"), scale = 0.8, inner.margins = c(0.15, 0.35, 0.02, 0.02)) m_jenks = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale_intervals(style = "jenks")) + tm_title('style = "jenks"')+ tm_layout(legend.position = tm_pos_auto_in(), title.position = tm_pos_in("right", "bottom"), scale = 0.8, inner.margins = c(0.15, 0.35, 0.02, 0.02)) m_log10 = tm_shape(nz) + tm_polygons(fill = "Population", fill.scale = tm_scale_intervals(style = "log10_pretty", values = "bu_pu")) + tm_title('style = "log10_pretty"')+ tm_layout(legend.position = tm_pos_auto_in(), title.position = tm_pos_in("right", "bottom"), scale = 0.8, inner.margins = c(0.15, 0.35, 0.02, 0.02)) tmap_arrange(m_pretty, m_equal, m_quantile, m_jenks, m_log10, nrow = 2) ================================================ FILE: code/09-layout1.R ================================================ library(spData) library(tmap) map_nz = tm_shape(nz) + tm_fill() + tm_borders() l2 = map_nz + tm_layout(scale = 4) l3 = map_nz + tm_layout(bg.color = "lightblue") l4 = map_nz + tm_layout(frame = FALSE) tmap_arrange(l2, l3, l4, nrow = 1) ================================================ FILE: code/09-layout2.R ================================================ library(spData) library(tmap) legend_title = expression("Area (km"^2*")") map_nza = tm_shape(nz) + tm_fill(fill = "Land_area", fill.legend = tm_legend(position = c("left", "top"), title = legend_title)) + tm_borders() c1 = map_nza + tm_title('frame.lwd = 5', position = c("right", "bottom")) + tm_layout(frame.lwd = 5, scale = 0.8) c2 = map_nza + tm_title('inner.margins = rep(0.2, 4)', position = c("right", "bottom")) + tm_layout(inner.margins = rep(0.2, 4), scale = 0.8) c3 = map_nza + tm_title('legend.show = FALSE', position = c("right", "bottom")) + tm_layout(legend.show = FALSE, scale = 0.8) c4 = tm_shape(nz) + tm_fill(fill = "Land_area") + tm_borders() + tm_title('legend.position =\n c("right", "bottom")', position = c("left", "top")) + tm_layout(legend.position = c("right", "bottom"), scale = 0.8) tmap_arrange(c1, c2, c3, c4, nrow = 2) ================================================ FILE: code/09-map-pkgs.R ================================================ # Aim: generate package metrics on common mapping packages remotes::install_github("ropenscilabs/packagemetrics") # generic mapping packages ------------------------------------------------ generic_map_pkgs = c( "ggplot2", "googleway", "ggspatial", "leaflet", "mapview", "plotly", "rasterVis", "tmap" ) generic_map_pkgs = packagemetrics::package_list_metrics(generic_map_pkgs) # pkg_table = packagemetrics::metrics_table(pkg_df) readr::write_csv(generic_map_pkgs, "extdata/generic_map_pkgs.csv") # specific purpose mapping packages --------------------------------------- specific_map_pkgs = c( "cartogram", "geogrid", "geofacet", "linemap", "tanaka", "rayshader" ) specific_map_pkgs = packagemetrics::package_list_metrics(specific_map_pkgs) # pkg_table = packagemetrics::metrics_table(pkg_df) readr::write_csv(specific_map_pkgs, "extdata/specific_map_pkgs.csv") ================================================ FILE: code/09-tmpal.R ================================================ library(tmap) library(spData) mc1 = tm_shape(nz) + tm_polygons(fill = "Median_income") mc2 = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(breaks = c(0, 30000, 40000, 50000))) mc3 = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(n = 10)) mc4 = tm_shape(nz) + tm_polygons(fill = "Median_income", fill.scale = tm_scale(values = "BuGn")) tmap_arrange(mc1, mc2, mc3, mc4, nrow = 1) ================================================ FILE: code/09-tmshape.R ================================================ library(tmap) library(spData) # Add fill layer to nz shape m1 = tm_shape(nz) + tm_fill() + tm_title_in("tm_shape(nz) +\n tm_fill()", size = 0.7) # Add border layer to nz shape m2 = tm_shape(nz) + tm_borders() + tm_title_in("tm_shape(nz) +\n tm_borders()", size = 0.7) # Add fill and border layers to nz shape m3 = tm_shape(nz) + tm_fill() + tm_borders() + tm_title_in("tm_shape(nz) +\n tm_fill() +\n tm_borders()", size = 0.7) tmap_arrange(m1, m2, m3, nrow = 1) ================================================ FILE: code/09-tmstyles.R ================================================ library(spData) library(tmap) legend_title = expression("Area (km"^2*")") map_nza = tm_shape(nz) + tm_fill(col = "Land_area", title = legend_title) + tm_borders() s1 = map_nza + tm_style("bw") + tm_layout(title = "style: bw") s2 = map_nza + tm_style("classic") + tm_layout(title = "style: classic") s3 = map_nza + tm_style("cobalt") + tm_layout(title = "style: cobalt") s4 = map_nza + tm_style("col_blind") + tm_layout(title = "style: col_blind") tmap_arrange(s1, s2, s3, s4, nrow = 1) ================================================ FILE: code/09-urban-animation.R ================================================ library(sf) library(dplyr) library(spData) library(tmap) world2 = filter(world, continent != "Antarctica") m_save = tm_shape(world2) + tm_polygons() + tm_shape(urban_agglomerations) + tm_symbols(size = "population_millions", size.legend = tm_legend(title = "Population (m)"), fill = "red", fill_alpha = 0.5) + tm_facets(by = "year", nrow = 1, ncol = 1, free.coords = FALSE) tmap::tmap_animation(tm = m_save, filename = "/tmp/urban-animated.gif", width = 1200, height = 800) # magick::image_read("/tmp/urban-animated.gif") ================================================ FILE: code/09-usboundaries.R ================================================ # Aim: create animation showing shifting US boundaries # depends on 17 MB USAboundariesData package # link to script file that shows changing state boundaries # install.packages("USAboundaries", repos = "https://ropensci.r-universe.dev") # install.packages("USAboundariesData", repos = "https://ropensci.r-universe.dev", type = "source") library(USAboundaries) library(tidyverse) library(tmap) library(sf) dates = paste(historydata::us_state_populations$year, "01", "01", sep = "-") dates_unique = unique(dates) # select all dates earlier than 2000 after this error: # Error in us_states(map_date, resolution, states) : # map_date <= as.Date("2000-12-31") is not TRUE dates_unique = dates_unique[dates_unique <= "2000-12-31"] usb1 = USAboundaries::us_states(map_date = dates_unique[1]) usb1$year = lubridate::year(dates_unique[1]) plot(usb1$geometry) usbl = map(dates_unique, ~USAboundaries::us_states(map_date = .)) # usb = do.call(rbind, usbl) statepop = historydata::us_state_populations |> select(-GISJOIN) |> rename(name = state) sel = usb1$name %in% statepop$name summary(sel) usb1$name[!sel] usbj = left_join(usb1, statepop) plot(usbj["population"]) i = 2 dates_unique[dates_unique > "2000-12-31"] = "2000-12-31" for(i in 2:length(dates_unique)) { usbi = USAboundaries::us_states(map_date = dates_unique[i]) #print(st_crs(usbi)) usbi$year = lubridate::year(dates_unique[i]) if (dates_unique[i] == "2000-12-31") usbi$year = 2010 # plot(usbi$geometry) usbji = left_join(usbi, statepop) # plot(usbji["population"]) usbj = bind_rows(usbj, usbji) } summary(usbj) usa_contig = usbji[!grepl(pattern = "Alaska|Haw", usbji$name), ] usbj_contig9311 = st_intersection(usbj, usa_contig[0]) |> st_transform("EPSG:9311") |> st_collection_extract("POLYGON") pal = viridis::viridis(n = 7, direction = -1) pb = c(0, 1, 2, 5, 10, 20, 30, 40) * 1e6 facet_anim = tm_shape(usbj_contig9311) + tm_polygons(fill = "population", fill.scale = tm_scale(values = pal, breaks = pb, label.na = FALSE)) + tm_facets(by = "year", nrow = 1, ncol = 1, free.coords = FALSE) + tm_shape(usa_union) + tm_borders(lwd = 2) + tm_layout(legend.position = c("left", "bottom"), legend.frame = FALSE) tmap_animation(tm = facet_anim, filename = "09-us_pop.gif", width = 900, height = 600) browseURL("09-us_pop.gif") ================================================ FILE: code/10-qgis-raster.R ================================================ library(qgisprocess) library(terra) library(tmap) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) dem_slope = terrain(dem, unit = "radians") dem_aspect = terrain(dem, v = "aspect", unit = "radians") dem_TPI = terrain(dem, v = "TPI") qgis_algo = qgis_algorithms() grep("wetness", qgis_algo$algorithm, value = TRUE) qgis_show_help("sagang:sagawetnessindex") dem_wetness = qgis_run_algorithm("sagang:sagawetnessindex", DEM = dem) dem_wetness_1 = qgis_as_terra(dem_wetness$AREA) dem_wetness_2 = qgis_as_terra(dem_wetness$AREA_MOD) dem_wetness_3 = qgis_as_terra(dem_wetness$SLOPE) dem_wetness_4 = qgis_as_terra(dem_wetness$TWI) # plot(dem_wetness_1) # plot(dem_wetness_2) # plot(dem_wetness_3) # plot(dem_wetness_4) grep("geomorphon", qgis_algo$algorithm, value = TRUE) qgis_show_help("grass7:r.geomorphon") dem_geomorph = qgis_run_algorithm("grass7:r.geomorphon", elevation = dem, `-m` = TRUE, search = 120) dem_geomorph_terra = qgis_as_terra(dem_geomorph$forms) # plot(dem_geomorphon) # set.values(dem_geomorphon) # set.cats(dem_geomorphon, value = cats(dem_geomorphon)[[1]][-11, ]) dem_hillshade = shade(dem_slope, dem_aspect, 10, 200) tm1 = tm_shape(dem_hillshade) + tm_raster(col.scale = tm_scale_continuous(values = rev(hcl.colors(99, "Grays"))), col.legend = tm_legend_hide()) + tm_shape(dem_wetness_4) + tm_raster(col_alpha = 0.5, col.scale = tm_scale_continuous(values = "Blues"), col.legend = tm_legend(title = "")) + tm_title("TWI", position = tm_pos_out()) + tm_layout(inner.margins = c(0, 0.22, 0, 0), legend.position = c("LEFT", "top"), frame = FALSE) tm2 = tm_shape(dem_hillshade) + tm_raster(col.scale = tm_scale_continuous(values = rev(hcl.colors(99, "Grays"))), col.legend = tm_legend_hide()) + tm_shape(dem_geomorph_terra) + tm_raster(col_alpha = 0.5, col.legend = tm_legend(title = ""), col.scale = tm_scale_categorical(levels.drop = TRUE)) + tm_title("Geomorphons", position = tm_pos_out(pos.h = "right")) + tm_layout(inner.margins = c(0, 0, 0, 0.22), legend.position = c("RIGHT", "top"), frame = FALSE) qgis_raster_map = tmap_arrange(tm1, tm2, nrow = 1) tmap_save(qgis_raster_map, "images/10-qgis-raster-map.png", width = 20, height = 9, units = "cm") ================================================ FILE: code/10-saga-segments.R ================================================ library(terra) library(sf) library(Rsagacmd) library(tmap) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) sg = saga$imagery_segmentation$seed_generation ndvi_seeds = sg(ndvi, band_width = 2) plot(ndvi_seeds$seed_grid) srg = saga$imagery_segmentation$seeded_region_growing ndvi_srg = srg(ndvi_seeds$seed_grid, ndvi, method = 1) plot(ndvi_srg$segments) ndvi_segments = as.polygons(ndvi_srg$segments) |> st_as_sf() tms1 = tm_shape(ndvi) + tm_raster(col.scale = tm_scale_continuous(n = 7, values = "PRGn"), col.legend = tm_legend(title = "NDVI")) + tm_layout(frame = FALSE, legend.frame = TRUE, legend.position = c("LEFT", "BOTTOM"), legend.bg.color = "white") tms2 = tms1 + tm_shape(ndvi_segments) + tm_borders(col = "red", lwd = 0.5) + tm_layout(legend.show = FALSE) tms = tmap_arrange(tms1, tms2) tmap_save(tms, filename = "images/10-saga-segments.png", dpi = 150, width = 9, height = 4.75, units = "cm") ================================================ FILE: code/10-saga-wetness.R ================================================ # Filename: 09-saga-wetness.R (2018-06-19) # # TO DO: Compute and visualize SAGA wetness index # # Author(s): Jannes Muenchow # #********************************************************** # CONTENTS------------------------------------------------- #********************************************************** # # 1. ATTACH PACKAGES AND DATA # 2. SAGA WETNESS INDEX # #********************************************************** # 1 ATTACH PACKAGES AND DATA------------------------------- #********************************************************** # attach packages library(tmap) library(raster) library(sf) library(RSAGA) # attach data data("landslides", package = "RSAGA") lsl_sf = st_as_sf(landslides, coords = c("x", "y"), crs = 32717) write.sgrd(data = dem, file = file.path(tempdir(), "dem"), header = dem$header) dem = file.path(tempdir(), "dem.sdat") |> raster(crs = st_crs(lsl_sf)$proj4string) #********************************************************** # 2 SAGA WETNESS INDEX------------------------------------- #********************************************************** # compute SAGA wetness index rsaga.wetness.index(in.dem = file.path(tempdir(), "dem"), out.wetness.index = file.path(tempdir(), "twi")) twi = raster(file.path(tempdir(), "twi.sdat"), crs = st_crs(lsl_sf)$proj4string) # compute hillshade hs = hillShade(terrain(dem, opt = "slope"), terrain(dem, opt = "aspect")) rect = tmaptools::bb_poly(hs) bbx = tmaptools::bb(hs, xlim = c(-0.02, 1), ylim = c(-0.02, 1), relative = TRUE) # create figure fig = tm_shape(hs, bbox = bbx) + tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, labels.rot = c(0, 90)) + tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + tm_shape(twi) + tm_raster(alpha = 0.9, palette = RColorBrewer::brewer.pal(n = 9, name = "Blues"), n = 9) + qtm(rect, fill = NULL) + tm_layout(outer.margins = c(0.04, 0.04, 0.02, 0.02), frame = FALSE) + tm_legend(bg.color = "white") # save the figure tmap_save(fig, filename = "images/09_twi.png", dpi = 300, width = 9, height = 9.75, units = "cm") ================================================ FILE: code/10-sliver.R ================================================ library(qgisprocess) library(sf) library(tmap) data("incongruent", "aggregating_zones", package = "spData") incongr_wgs = st_transform(incongruent, "EPSG:4326") aggzone_wgs = st_transform(aggregating_zones, "EPSG:4326") alg = "native:union" union = qgis_run_algorithm(alg, INPUT = incongr_wgs, OVERLAY = aggzone_wgs) union_sf = st_as_sf(union) single = st_cast(union_sf, "MULTIPOLYGON") |> st_cast("POLYGON") single$area = st_area(single) x = 25000 units(x) = "m^2" sub = dplyr::filter(single, area < x) clean = qgis_run_algorithm("grass7:v.clean", input = union_sf, type = 4, tool = 10, threshold = 25000, output = file.path(tempdir(), "clean.gpkg")) clean_sf = st_as_sf(clean) tm1 = tm_shape(union_sf) + tm_polygons(fill_alpha = 0.2, lwd = 0.2) + tm_shape(sub) + tm_fill(fill = "#C51111") + tm_title("Sliver polygons included") tm2 = tm_shape(clean_sf) + tm_polygons(fill_alpha = 0.2, lwd = 0.2) + tm_title("Sliver polygons cleaned") sc_maps = tmap_arrange(tm1, tm2, nrow = 1) # save the output tmap_save(sc_maps, "images/10-sliver.png", width = 12, height = 5, units = "cm") ================================================ FILE: code/10-tsp.R ================================================ # Filename: 09-tsp.R (2018-06-19) # # TO DO: Traveling salesman figure # # Author(s): Jannes Muenchow # #********************************************************** # CONTENTS------------------------------------------------- #********************************************************** # # 1. ATTACH PACKAGES AND DATA # 2. TSP # #********************************************************** # 1 ATTACH PACKAGES AND DATA------------------------------- #********************************************************** # attach packages library(rgrass7) library(link2GI) library(sf) library(mapview) # attach data data("cycle_hire", package = "spData") # just keep the first 25 points points = cycle_hire[1:25, ] data("london_streets", package = "spDataLarge") #********************************************************** # 2 TSP---------------------------------------------------- #********************************************************** # initialize spatial GRASS database link2GI::linkGRASS7(london_streets, ver_select = TRUE) # add data to the db writeVECT(SDF = as(london_streets, "Spatial"), vname = "london_streets") writeVECT(SDF = as(points[, 1], "Spatial"), vname = "points") # clean topology execGRASS(cmd = "v.clean", input = "london_streets", output = "streets_clean", tool = "break", flags = "overwrite") # add points to the street network execGRASS(cmd = "v.net", input = "streets_clean", output = "streets_points_con", points = "points", operation = "connect", threshold = 0.001, flags = c("overwrite", "c")) # run tsp execGRASS(cmd = "v.net.salesman", input = "streets_points_con", output = "shortest_route", center_cats = paste0("1-", nrow(points)), flags = c("overwrite")) # load output into R route = readVECT("shortest_route") |> st_as_sf() |> st_geometry() # make a plot fig = mapview(route, map.types = "OpenStreetMap.BlackAndWhite", lwd = 7) + mapview(points) # save it as a png mapshot(fig, file = "images/09_shortest_route.png", remove_controls = c("homeButton", "layersControl")) ================================================ FILE: code/11-centroid-alg.R ================================================ # Aim: take a matrix representing a convex polygon, return its centroid, # demonstrate how algorithms work # Pre-requisite: an input object named poly_mat with 2 columns representing # vertices of a polygon, with 1st and last rows identical: if (!exists("poly_mat")) { message("No poly_mat object provided, creating object representing a 9 by 9 square") poly_mat = cbind( x = c(0, 0, 9, 9, 0), y = c(0, 9, 9, 0, 0) ) } # Step 1: create sub-triangles, set up ------------------------------------ Origin = poly_mat[1, ] # create a point representing the origin i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) { rbind(Origin, poly_mat[x:(x + 1), ], Origin) }) # Step 2: calculate triangle centroids ------------------------------------ C_list = lapply(T_all, function(x) (x[1, ] + x[2, ] + x[3, ]) / 3) C = do.call(rbind, C_list) # Step 3: calculate triangle areas ---------------------------------------- A = vapply(T_all, function(x) { abs(x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 }, FUN.VALUE = double(1)) # Step 4: calculate area-weighted centroid average ------------------------ poly_area = sum(A) print(paste0("The area is: ", poly_area)) poly_centroid = c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) # Step 5: output results -------------------------------------------------- print(paste0( "The coordinates of the centroid are: ", round(poly_centroid[1], 2), ", ", round(poly_centroid[2], 2) )) ================================================ FILE: code/11-centroid-setup.R ================================================ library(sf) # centroid calculation with Python: dir.create("py") if (!file.exists("py/__init__.py")) { download.file("https://github.com/gisalgs/geom/archive/master.zip", "py/gisalgs.zip") unzip("py/gisalgs.zip", exdir = "py/") file.rename(from = "py/geom-master", to = "py/geom") file.copy("py/geom/__init__.py", "py/__init__.py") } library(reticulate) reticulate::py_config() # Test Python works res = py_run_string("x = 1 + 3") res$x py_run_string("import os") py_run_string("import sys") current_directory <- getwd() py_run_string(paste0("sys.path.append('", current_directory, "/py')")) py_run_string("from geom.point import *") res = py_run_string("p, p1, p2 = Point(10,0), Point(0,100), Point(0,1)") res$p1 py_run_string("print(p.distance(p1))") res = py_run_file("py/geom/centroid.py") points_py = res$points mat_points = matrix(unlist(points_py), ncol = 2, byrow = TRUE) plot(mat_points) poly_csv = "0,5,10,15,20,25,30,40,45,50,40,30,25,20,15,10,8,4,0 10,0,10,0,10,0,20,20,0,50,40,50,20,50,10,50,8,50,10" poly_df = read.csv(text = poly_csv, header = FALSE) poly_mat = t(poly_df) poly_sf = st_polygon(x = list(mat_points)) plot(poly_sf) # access modules g = import_from_path(module = 'geom', paste0(getwd(), '/py')) g$point unlink("py", recursive = TRUE) ================================================ FILE: code/11-hello.R ================================================ # Aim: provide a minimal R script print("Hello geocompr") ================================================ FILE: code/11-polycent.R ================================================ # Aim: create visualization showing steps in centroid algorithm if (!exists("poly_mat")) { # source("code/chapters/10-algorithms.R") x_coords = c(10, 0, 0, 2, 20, 10) y_coords = c(0, 0, 10, 12, 15, 0) poly_mat = cbind(x_coords, y_coords) O = poly_mat[1, ] # create a point representing the origin t_area = function(x) { abs( x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 } t_centroid = function(x) { (x[1, ] + x[2, ] + x[3, ]) / 3 } i = 2:(nrow(poly_mat) - 2) T_all = purrr::map(i, ~rbind(O, poly_mat[.:(. + 1), ], O)) A = purrr::map_dbl(T_all, ~t_area(.)) C_list = lapply(T_all, t_centroid) C = do.call(rbind, C_list) } # plot(poly_mat) # lines(poly_mat) # # manual solution: # lines(T_all[[1]], col = "blue", lwd = 2) # text(x = C[1, 1], y = C[1, 2], "C1", col = "blue") # lines(T_all[[2]], col = "red", lwd = 2) # text(x = C[2, 1], y = C[2, 2], "C2", col = "red") # iterating solution: par(mfrow = c(1, 3), mar = c(0, 0, 0, 0), pty = "s") # optional wide plot (alternative = animation) i = 2 cols = c("#fa8072", "#000080", "#93e9be") for(i in rep(1:length(T_all), 2)) { if (i == 1 | sum(par()$mfrow) > 2) { plot(poly_mat, xlab = "", ylab = "", axes = FALSE, cex = 3) lines(poly_mat, lwd = 7) } lines(T_all[[i]], col = cols[i], lwd = 2) # lines(do.call(rbind, T_all[1:i]), col = cols[1:i], lwd = 2) text(x = C[i, 1], y = C[i, 2], paste0("C", i), col = cols[i]) if (i != 1) { points(x = mean(C[1:i, 1]), y = mean(C[1:i, 2]), pch = 4, cex = 3) } Sys.sleep(time = 0.5) } par(mfrow = c(1, 1)) ================================================ FILE: code/12-cv.R ================================================ # Filename: 12-cv.R (2022-02-16) # # TO DO: Introduce spatial cross-validation with the help of mlr3 # # Author(s): Jannes Muenchow # #********************************************************** # contents---- #********************************************************** # # 1 attach packages and data # 2 modeling # 3 spatial prediction # #********************************************************** # 1 attach packages and data---- #********************************************************** # attach packages library(dplyr) library(ggplot2) library(mlr3) library(mlr3extralearners) library(mlr3learners) library(mlr3spatiotempcv) library(mlr3tuning) library(raster) library(sf) library(tmap) #********************************************************** # 2 modeling---- #********************************************************** # attach data data("lsl", "study_mask", package = "spDataLarge") ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # 2.1 create a mlr3 task==== #********************************************************** # id = name of the task # target = response variable # spatial: setting up the scene for spatial cv (hence x and y will not be used # as predictors but as coordinates for kmeans clustering) # all variables in data will be used in the model task = TaskClassifST$new( id = "lsl_glm_sp", backend = mlr3::as_data_backend(lsl), target = "lslpts", positive = "TRUE", extra_args = list( coordinate_names = c("x", "y"), coords_as_features = FALSE, crs = 32717) ) # pls note that you can use a task created by TaskClassifST for both the spatial # and the conventional CV approach # 2.2 construct a learner and run the model==== #********************************************************** # glm learner lrn_glm = lrn("classif.log_reg", predict_type = "prob") # define a fallback learner in case one model fails lrn_glm$fallback = lrn("classif.featureless", predict_type = "prob") # construct SVM learner (using ksvm function from the kernlab package) lrn_ksvm = lrn("classif.ksvm", predict_type = "prob", kernel = "rbfdot", type = "C-svc") lrn_ksvm$fallback = lrn("classif.featureless", predict_type = "prob") # rbfdot Radial Basis kernel "Gaussian" # the list of hyper-parameters (kernel parameters). This is a list which # contains the parameters to be used with the kernel function. For valid # parameters for existing kernels are : # sigma inverse kernel width for the Radial Basis kernel function "rbfdot" and # the Laplacian kernel "laplacedot". # C cost of constraints violation (default: 1) this is the ‘C’-constant of the # regularization term in the Lagrange formulation. # specify nested resampling and adjust learner accordingly # five spatially disjoint partitions tune_level = rsmp("spcv_coords", folds = 5) # use 50 randomly selected hyperparameters terminator = trm("evals", n_evals = 50) tuner = tnr("random_search") # define the outer limits of the randomly selected hyperparameters search_space = ps( C = p_dbl(lower = -12, upper = 15, trafo = function(x) 2^x), sigma = p_dbl(lower = -15, upper = 6, trafo = function(x) 2^x) ) at_ksvm = AutoTuner$new( learner = lrn_ksvm, resampling = tune_level, measure = msr("classif.auc"), search_space = search_space, terminator = terminator, tuner = tuner ) # 2.3 cross-validation==== #********************************************************** # specify the reampling method, i.e. spatial CV with 100 repetitions and 5 folds # -> in each repetition dataset will be splitted into five folds # method: repeated_spcv_coords -> spatial partioning # method: repeated_cv -> non-spatial partitioning rsmp_sp = rsmp("repeated_spcv_coords", folds = 5, repeats = 100) rsmp_nsp = rsmp("repeated_cv", folds = 5, repeats = 100) # create your design design_grid = benchmark_grid( tasks = task, learners = list(lrn_glm, at_ksvm), resamplings = list(rsmp_sp, rsmp_nsp)) print(design_grid) # run the cross-validations (spatial and conventional) # execute the outer loop sequentially and parallelize the inner loop future::plan(list("sequential", "multisession"), # use half of all available cores # workers = floor(future::availableCores()) / 2 workers = 8) # why do we need the seed? # the seed is needed when to make sure # that always the same spatial partitions are used when re-running the code set.seed(012348) # reduce verbosity lgr::get_logger("mlr3")$set_threshold("warn") lgr::get_logger("bbotk")$set_threshold("info") tictoc::tic() progressr::with_progress(expr = { # New argument `encapsulate` for `resample()` and `benchmark()` to # conveniently enable encapsulation and also set the fallback learner to the # respective featureless learner. This is simply for convenience, configuring # each learner individually is still possible and allows a more fine-grained # control bmr = benchmark(design_grid, encapsulate = "evaluate", store_backends = FALSE, store_models = FALSE) }) tictoc::toc() # stop the parallelization plan future:::ClusterRegistry("stop") # save your result # saveRDS(bmr, file = "extdata/12-bmr_glm_svm_spcv_convcv.rds") # plot your result # library(mlr3viz) # p1 = autoplot(bmr, measure = msr("classif.auc")) # p1$labels$y = "AUROC" # p1$layers[[1]]$aes_params$fill = c("lightblue2", "mistyrose2") # p1 + # scale_x_discrete(labels=c("spatial CV", "conventional CV")) + # ggplot2::theme_bw() # instead of using autoplot, it might be easier to create the figure yourself score = bmr$score(measure = mlr3::msr("classif.auc")) %>% # keep only the columns you need .[, .(task_id, learner_id, resampling_id, classif.auc)] # or read in the score # score = readRDS("extdata/12-bmr_score.rds") # rename the levels of resampling_id score[, resampling_id := as.factor(resampling_id) |> forcats::fct_recode("conventional CV" = "repeated_cv", "spatial CV" = "repeated_spcv_coords") |> forcats::fct_rev()] # create the boxplot which shows the overfitting in the nsp-case ggplot2::ggplot( data = score, mapping = ggplot2::aes(x = interaction(resampling_id, learner_id), y = classif.auc, fill = resampling_id )) + ggplot2::geom_boxplot() + ggplot2::theme_bw() + ggplot2::labs(y = "AUROC", x = "") #********************************************************** # 3 spatial prediction---- #********************************************************** #********************************************************** # 3.1 make the prediction using the glm==== #********************************************************** lrn_glm$train(task) fit = lrn_glm$model # according to lrn_glm$help() the default for predictions was adjusted to FALSE, # since we would like to have TRUE predictions, we have to change back # fit$coefficients = fit$coefficients * -1 pred = terra::predict(object = ta, model = fit, fun = predict, type = "response") # make the prediction "manually" ta_2 = ta newdata = as.data.frame(as.matrix(ta_2)) colSums(is.na(newdata)) # ok, there are NAs ind = rowSums(is.na(newdata)) == 0 tmp = lrn_glm$predict_newdata(newdata = newdata[ind, ], task = task) newdata[ind, "pred"] = as.data.table(tmp)[["prob.TRUE"]] # check all.equal(as.numeric(values(pred)), newdata$pred) # TRUE pred_2 = ta$slope pred_2[] = newdata$pred #********************************************************** # 3.2 plot the prediction==== #********************************************************** hs = terra::shade(ta$slope * pi / 180, terra::terrain(ta$elev, v = "aspect", unit = "radians"), 40, 270) plot(hs, col = gray(seq(0, 1, length.out = 100)), legend = FALSE) plot(pred, col = RColorBrewer::brewer.pal(name = "Reds", 9), add = TRUE) # or using tmap # white raster to only plot the axis ticks, otherwise gridlines would be visible study_mask = terra::vect(study_mask) lsl_sf = st_as_sf(lsl, coords = c("x", "y"), crs = 32717) rect = tmaptools::bb_poly(raster::raster(hs)) bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.02, 1), ylim = c(-0.02, 1), relative = TRUE) tm_shape(hs, bbox = bbx) + tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, labels.rot = c(0, 90)) + tm_raster(palette = "white", legend.show = FALSE) + # hillshade tm_shape(terra::mask(hs, study_mask), bbox = bbx) + tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + # prediction raster tm_shape(terra::mask(pred, study_mask)) + tm_raster(alpha = 0.5, palette = "Reds", n = 6, legend.show = TRUE, title = "Susceptibility") + # rectangle and outer margins qtm(rect, fill = NULL) + tm_layout(outer.margins = c(0.04, 0.04, 0.02, 0.02), frame = FALSE, legend.position = c("left", "bottom"), legend.title.size = 0.9) ================================================ FILE: code/12-partitioning.R ================================================ library(mlr3) library(mlr3spatiotempcv) library(sf) library(purrr) task_ecuador = tsk("ecuador") set.seed(2024-01-14) part_spcv_coords = rsmp("spcv_coords", folds = 5) part_spcv_coords$instantiate(task_ecuador) part_cv = rsmp("cv", folds = 5) part_cv$instantiate(task_ecuador) recreate_sf = function(type, fold){ if (type == "spcv_coords"){ part = part_spcv_coords } else { part = part_cv } data_sf = st_as_sf(task_ecuador$coordinates(), coords = c("x", "y")) st_crs(data_sf) = task_ecuador$crs data_sf$fold = fold data_sf$split = NA data_sf$split[part$train_set(fold)] = "training data" data_sf$split[part$test_set(fold)] = "test data" data_sf$type = type data_sf } param_df = data.frame(type = rep(c("spcv_coords", "cv"), each = 5), fold = rep(1:5, 2)) all_sf = pmap(param_df, recreate_sf) all_sf = do.call(rbind, all_sf) library(tmap) tm_folds = tm_shape(all_sf) + tm_dots(fill = "split", size = 0.4, fill.legend = tm_legend(title = "")) + tm_facets_grid("type", "fold") + tm_layout( legend.position = tm_pos_out("center", "bottom"), legend.frame = FALSE, legend.resize.as.group = TRUE, legend.text.size = 1, panel.labels = list(c("random partitioning", "spatial partitioning"), c("fold 1", "fold 2", "fold 3", "fold 4", "fold 5"))) tmap_save(tm_folds, "images/12_partitioning.png", width = 1417, height = 726, dpi = 144) ================================================ FILE: code/13-cycleways.R ================================================ if (!exists("route_cycleway")) { source("code/chapters/13-transport.R") } tmap_mode("plot") bristol_stations_top = bristol_stations[desire_rail, , op = st_is_within_distance, dist = 500] m_leaflet = tm_shape(bristol_ttwa) + tm_borders(col = "darkblue") + tm_shape(bristol_ways) + tm_lines(col = "highway", lwd = 3, palette = c("lightgreen", "gray", "pink")) + tm_scalebar() + tm_shape(route_cycleway) + tm_lines(col = "blue", lwd = "all", scale = 20, alpha = 0.6) + tm_shape(bristol_stations_top) + tm_dots(size = 0.3, col = "red") + tm_layout(legend.position = c("LEFT", "TOP")) # m_leaflet ================================================ FILE: code/13-desire.R ================================================ # Aim: generate tmap figure representing desire lines # load data if not already loaded: if (!exists("desire_lines")) { library(sf) library(dplyr) library(spDataLarge) library(stplanr) library(tmap) zones_attr = bristol_od |> group_by(o) |> summarize_if(is.numeric, sum) |> dplyr::rename(geo_code = o) zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") zones_od = bristol_od |> group_by(d) |> summarize_if(is.numeric, sum) |> select(geo_code = d, all_dest = all) |> inner_join(zones_joined, ., by = "geo_code") |> st_as_sf() od_top5 = bristol_od |> arrange(desc(all)) |> top_n(5, wt = all) bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) desire_lines = od2line(od_inter, zones_od) } # u_od = "https://user-images.githubusercontent.com/1825120/34081176-74fd39c8-e341-11e7-9f3e-b98807cb113b.png" # knitr::include_graphics(u_od) tmap_mode("plot") desire_lines_top5 = od2line(od_top5, zones_od) # tmaptools::palette_explorer() tm_shape(desire_lines) + tm_lines(col = "Active", col.scale = tm_scale(values = viridis::plasma(5), breaks = c(0, 5, 10, 20, 40, 100)), col.legend = tm_legend(title = "Active travel (%)"), col_alpha = 0.6, lwd = "all", #lwd.scale = tm_scale(values.scale = 2), lwd.legend = tm_legend(title = "Number of trips")) + tm_shape(desire_lines_top5) + tm_lines(lwd = 5, col = "black", col_alpha = 0.7) + tm_scalebar() ================================================ FILE: code/13-transport-data-gen.R ================================================ # See 'bristol.R' code at https://github.com/Nowosad/spDataLarge/tree/master/data-raw source("https://raw.githubusercontent.com/Nowosad/spDataLarge/master/data-raw/07_bristol.R") ================================================ FILE: code/13-zones.R ================================================ library(tmap) tmap_mode("plot") tm_shape(zones_od) + tm_fill(c("all", "all_dest"), fill.scale = tm_scale(values = viridis::plasma(4), breaks = c(0, 2000, 4000, 10000, 50000)), fill.legend = tm_legend(title = "Trips", position = tm_pos_out("right", "center")), fill.free = FALSE) + tm_facets() + tm_borders(col = "black", lwd = 0.5) + tm_layout(panel.labels = c("Zone of origin", "Zone of destination")) ================================================ FILE: code/14-location-figures.R ================================================ # Filename: 14-location_figures.R (2022-11-30, last update: 2023-08-09) # # TO DO: Build figures for location chapter # # Author(s): Jannes Muenchow, Jakub Nowosad # #********************************************************** # CONTENTS------------------------------------------------- #********************************************************** # # 1. ATTACH PACKAGES AND DATA # 2. OVERVIEW RASTER FIGURE # 3. METRO RASTER FIGURE # 4. POTENTIAL LOCATIONS # #********************************************************** # 1 ATTACH PACKAGES AND DATA------------------------------- #********************************************************** # attach packages library(terra) library(sf) library(geodata) library(tmap) library(classInt) library(mapview) library(dplyr) library(purrr) library(htmlwidgets) library(leaflet) library(z22) # attach data data("metro_names", "shops", package = "spDataLarge") # download German border polygon ger = geodata::gadm(country = "DEU", level = 0, path = tempdir()) #********************************************************** # 2 CENSUS STACK FIGURE------------------------------------ #********************************************************** # 2.1 Data preparation===================================== #********************************************************** # Load Census 2022 data using z22 package pop = z22_data("population", res = "1km", year = 2022, as = "df") |> rename(pop = cat_0) # Women data only available from Census 2011 women = z22_data("women", year = 2011, res = "1km", as = "df") |> rename(women = cat_0) mean_age = z22_data("age_avg", res = "1km", year = 2022, as = "df") |> rename(mean_age = cat_0) hh_size = z22_data("household_size_avg", res = "1km", year = 2022, as = "df") |> rename(hh_size = cat_0) # Join all data frames input_tidy = pop |> left_join(women, by = c("x", "y")) |> left_join(mean_age, by = c("x", "y")) |> left_join(hh_size, by = c("x", "y")) |> relocate(pop, .after = y) |> mutate(across(c(pop, women, mean_age, hh_size), ~ifelse(.x < 0, NA, .x))) input_ras = terra::rast(input_tidy, type = "xyz", crs = "EPSG:3035") # reproject German outline ger = st_as_sf(terra::project(ger, crs(input_ras))) # 2.2 Create figure======================================== #********************************************************** # Reclassification matrices for the figure rcl_pop = matrix(c(0, 250, 1, 250, 500, 2, 500, 2000, 3, 2000, 4000, 4, 4000, 8000, 5, 8000, Inf, 6), ncol = 3, byrow = TRUE) rcl_women = matrix(c(0, 40, 1, 40, 47, 2, 47, 53, 3, 53, 60, 4, 60, 100, 5), ncol = 3, byrow = TRUE) rcl_age = matrix(c(0, 40, 1, 40, 42, 2, 42, 44, 3, 44, 47, 4, 47, 120, 5), ncol = 3, byrow = TRUE) rcl_hh = matrix(c(0, 1.5, 1, 1.5, 2.0, 2, 2.0, 2.5, 3, 2.5, 3.0, 4, 3.0, 100, 5), ncol = 3, byrow = TRUE) # Reclassify variables pop_class = terra::classify(input_ras$pop, rcl = rcl_pop, right = NA) women_class = terra::classify(input_ras$women, rcl = rcl_women, right = NA) age_class = terra::classify(input_ras$mean_age, rcl = rcl_age, right = NA) hh_class = terra::classify(input_ras$hh_size, rcl = rcl_hh, right = NA) # Set categories for proper legend display cls = data.frame(id = 1:6, class = as.character(1:6)) set.cats(pop_class, layer = 1, value = cls) set.cats(women_class, layer = 1, value = cls[1:5,]) set.cats(age_class, layer = 1, value = cls[1:5,]) set.cats(hh_class, layer = 1, value = cls[1:5,]) reclass_fig = c(pop_class, women_class, age_class, hh_class) names(reclass_fig) = c("pop", "women", "mean_age", "hh_size") tm_1 = tm_shape(reclass_fig) + tm_raster(col.scale = tm_scale_categorical(values = "brewer.gn_bu"), col.legend = tm_legend(title = "Class", position = tm_pos_out("right", "center")), col.free = FALSE) + tm_facets(nrow = 1) + tm_shape(ger) + tm_borders() + tm_layout(panel.labels = c("population", "women", "mean age", "household size"), panel.label.size = 0.8) tmap_save(tm_1, "images/14_census_stack.png", width = 7, height = 2.2) #********************************************************** # 3 METROPOLITAN AREA FIGURE------------------------------- #********************************************************** # Reclassification matrices for continuous values (from, to, weight) rcl_women = matrix(c( 0, 40, 3, # 0-40% female -> weight 3 40, 47, 2, # 40-47% -> weight 2 47, 53, 1, # 47-53% -> weight 1 53, 60, 0, # 53-60% -> weight 0 60, 100, 0 # >60% -> weight 0 ), ncol = 3, byrow = TRUE) rcl_age = matrix(c( 0, 40, 3, # Mean age <40 -> weight 3 40, 42, 2, # 40-42 -> weight 2 42, 44, 1, # 42-44 -> weight 1 44, 47, 0, # 44-47 -> weight 0 47, 120, 0 # >47 -> weight 0 ), ncol = 3, byrow = TRUE) rcl_hh = matrix(c( 0, 1.5, 3, # 1-1.5 persons -> weight 3 1.5, 2.0, 2, # 1.5-2 -> weight 2 2.0, 2.5, 1, # 2-2.5 -> weight 1 2.5, 3.0, 0, # 2.5-3 -> weight 0 3.0, 100, 0 # >3 -> weight 0 ), ncol = 3, byrow = TRUE) rcl = list(rcl_women, rcl_age, rcl_hh) # Separate population (used as counts for metro detection) from variables to reclassify pop_ras = input_ras$pop demo_vars = c("women", "mean_age", "hh_size") reclass = map2(as.list(input_ras[[demo_vars]]), rcl, function(x, y) { terra::classify(x = x, rcl = y, right = NA) }) |> rast() names(reclass) = demo_vars # aggregate by a factor of 20 using actual population counts pop_agg = terra::aggregate(pop_ras, fact = 20, fun = sum, na.rm = TRUE) # just keep raster cells with more than 500,000 inhabitants polys = pop_agg[pop_agg > 500000, drop = FALSE] # convert all cells belonging to one region into polygons metros = polys |> terra::patches(directions = 8) |> terra::as.polygons() |> st_as_sf() # Hardcoded metro names based on centroid coordinates (avoids API dependency) # Census 2022 detects 10 metro areas (vs 8 in Census 2011) metro_names_vec = c("Hamburg", "Berlin", "Hannover", "Düsseldorf", "Leipzig", "Dresden", "Frankfurt", "Nürnberg", "Stuttgart", "München") metros$names = metro_names_vec metros_points = st_centroid(metros) tm_2 = tm_shape(pop_agg/1000) + tm_raster(col.scale = tm_scale_intervals(values = "brewer.gn_bu", style = "fixed", breaks = c(0, 200, 400, 600, 800, 1000, 1200, 1400)), col.legend = tm_legend(title = "Number of people\n(in 1,000)", position = tm_pos_out("right", "center"))) + tm_shape(ger) + tm_borders() + tm_shape(metros) + tm_borders(col = "gold", lwd = 2) + tm_shape(metros_points) + tm_text("names", size = 0.55, fontface = "italic", col = "white", xmod = 0.08, ymod = 0) + tm_shape(metros_points) + tm_text("names", size = 0.55, fontface = "italic", col = "white", xmod = -0.08, ymod = 0) + tm_shape(metros_points) + tm_text("names", size = 0.55, fontface = "italic", col = "white", xmod = 0, ymod = 0.08) + tm_shape(metros_points) + tm_text("names", size = 0.55, fontface = "italic", col = "white", xmod = 0, ymod = -0.08) + tm_shape(metros_points) + tm_text("names", size = 0.55, fontface = "italic") tmap_save(tm_2, "images/14_metro_areas.png", width = 5, height = 4) #********************************************************** # 4 POTENTIAL LOCATIONS------------------------------------ #********************************************************** # 4.1 Data preparation===================================== #********************************************************** shops = st_transform(shops, st_crs(reclass)) # create poi raster poi = terra::rasterize(x = terra::vect(shops), y = reclass, field = "osm_id", fun = "length") int = classInt::classIntervals(values(poi), n = 4, style = "fisher") int = round(int$brks) rcl_poi = matrix(c(int[1], rep(int[-c(1, length(int))], each = 2), int[length(int)] + 1), ncol = 2, byrow = TRUE) rcl_poi = cbind(rcl_poi, 0:3) # reclassify poi = terra::classify(poi, rcl = rcl_poi, right = NA) names(poi) = "poi" # add poi raster to demographic weights reclass = c(reclass, poi) # calculate the total score result = sum(reclass) # have a look at suitable bike shop locations in Berlin berlin = metros[metro_names == "Berlin", ] berlin_raster = terra::crop(result, berlin) # 4.2 Figure=============================================== #********************************************************** m = mapview(raster::raster(berlin_raster), col.regions = c(NA, "darkgreen"), na.color = "transparent", legend = TRUE, map.type = "OpenStreetMap") mapshot(m, url = file.path(getwd(), "images/08_bikeshops_berlin.html")) # using leaflet (instead of mapview) berlin_raster = berlin_raster >= 9 berlin_raster[berlin_raster == 0] = NA leaflet() |> addTiles() |> addRasterImage(raster::raster(berlin_raster), colors = "darkgreen", opacity = 0.8) |> addLegend("bottomright", colors = c("darkgreen"), labels = c("potential locations"), title = "Legend") ================================================ FILE: code/15-rf_mlr3.R ================================================ # Filename: 15-rf_mlr3.R (2022-04-14) # TO DO: use spatially cross-validated tuned hyperparameters to make a spatial prediction of the floristic composition of the Mount Mongón # Author(s): jannes.muenchow #********************************************************** # CONTENTS---- #********************************************************** # 1 attach packages and data # 2 preprocessing # 3 modeling #********************************************************** # 1 attach packages and data---- #********************************************************** # attach packages library(dplyr) library(mlr3) library(mlr3extralearners) library(mlr3learners) library(mlr3tuning) library(mlr3spatiotempcv) library(qgisprocess) library(raster) library(terra) library(sf) library(vegan) # attach data data("study_area", "random_points", "comm", package = "spDataLarge") dem = terra::rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = terra::rast(system.file("raster/ndvi.tif", package = "spDataLarge")) #********************************************************** # 2 preprocessing---- #********************************************************** alg = "saga:sagawetnessindex" args = qgis_arguments(alg) qgis_show_help(alg) ep = qgis_run_algorithm(alg = "saga:sagawetnessindex", DEM = dem, SLOPE_TYPE = 1, SLOPE = tempfile(fileext = ".sdat"), AREA = tempfile(fileext = ".sdat"), .quiet = TRUE) # read in catchment area and catchment slope ep = ep[c("AREA", "SLOPE")] |> unlist() |> terra::rast() names(ep) = c("carea", "cslope") # make sure all rasters share the same origin origin(ep) = origin(dem) ep = c(dem, ndvi, ep) ep$carea = log10(ep$carea) random_points[, names(ep)] = # first column is an ID column we don't need terra::extract(ep, terra::vect(random_points))[, -1] # presence-absence matrix pa = decostand(comm, "pa") # 100 rows (sites), 69 columns (species) # keep only sites in which at least one species was found pa = pa[rowSums(pa) != 0, ] # 84 rows, 69 columns nmds = readRDS("extdata/15-nmds.rds") elev = dplyr::filter(random_points, id %in% rownames(pa)) %>% dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = MDSrotate(nmds, elev) # extracting the first axes sc = scores(rotnmds, choices = 1:2) # construct response-predictor matrix # id- and response variable rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) # join the predictors (dem, ndvi and terrain attributes) rp = inner_join(random_points, rp, by = "id") #********************************************************** # 3 modeling---- #********************************************************** # create task task = TaskRegrST$new(id = "mongon", backend = select(rp, -id, -spri), target = "sc") rp = select(rp, -id, -spri) rp[, c("x", "y")] = st_coordinates(rp) rp = st_drop_geometry(rp) task = TaskRegrST$new(id = "mongon", backend = rp, target = "sc", extra_args = list(coordinate_names = c("x", "y"))) lrn_rf = lrn("regr.ranger", predict_type = "response") search_space = ps( mtry = p_int(lower = 1, upper = ncol(task$data()) - 1), sample.fraction = p_dbl(lower = 0.2, upper = 0.9), min.node.size = p_int(lower = 1, upper = 10) ) at = AutoTuner$new( learner = lrn_rf, # spatial partitioning resampling = rsmp("spcv_coords", folds = 5), # performance measure measure = msr("regr.rmse"), search_space = search_space, # random search with 50 iterations terminator = trm("evals", n_evals = 50), tuner = tnr("random_search") ) at$train(task) at$tuning_result at$predict(task) # predict to new data pred_terra = terra::predict(ep, model = at) names(pred_terra) = "pred" # doing it "manually" newdata = as.data.frame(as.matrix(ep)) colSums(is.na(newdata)) # 0 NAs # but assuming there were results in a more generic approach ind = rowSums(is.na(newdata)) == 0 tmp = at$predict_newdata(newdata = newdata[ind, ], task = task) newdata[ind, "pred"] = as.data.table(tmp)[["response"]] pred_2 = pred_terra pred_2[] = newdata$pred # same as # values(pred_2) = newdata$pred # all.equal(pred_terra, pred_2) # does not work, don't know why identical(values(pred_terra), values(pred_2)) # TRUE plot(pred_terra - pred_2) # just 0s, perfect plot(c(pred, pred_2)) ================================================ FILE: code/add-impact.R ================================================ # Aim: add new impact to our-impact.csv ----------------------------------- # Get impact details ------------------------------------------------------ url_issue = "https://github.com/mtennekes/tmap/issues/228" new_impact = geocompkg:::add_impact(url_issue = url_issue) new_impact new_impact = c(url = url_issue, new_impact) # Add to our-impact.csv --------------------------------------------------- u = "https://github.com/geocompx/geocompr/raw/main/our-impact.csv" geocompkg:::add_impact(url_issue, url_old_impact = u) ================================================ FILE: code/before_script.R ================================================ library(methods) library(knitr) opts_chunk$set( background = "#FCFCFC", # code chunk color in latex comment = "#>", collapse = TRUE, # The following line speeds-up the build. # Uncomment it to avoid cached data (which can cause issues): cache = TRUE, fig.pos = "t", fig.path = "figures/", fig.align = "center", fig.width = 6, fig.asp = 0.618, # 1 / phi fig.show = "hold", out.width = "100%", dpi = 105 # this creates 2*105 dpi at 6in, which is 300 dpi at 4.2in, see the EmilHvitfeldt/smltar repo ) set.seed(2017) options(digits = 3) options(dplyr.print_min = 4, dplyr.print_max = 4) # https://github.com/rstudio/rmarkdown-cookbook/commit/876bca3facedd30b8cc48cd9c1c86020d1e2adf9 # save the build-in output hook hook_output = knitr::knit_hooks$get("output") # set a new output hook to truncate text output knitr::knit_hooks$set(output = function(x, options) { if (!is.null(n <- options$out.lines)) { x = knitr:::split_lines(x) if (length(x) > n) { # truncate the output x = c(head(x, n), '....\n') } x = paste(x, collapse = '\n') } # https://github.com/EmilHvitfeldt/smltar/issues/114 # this hook is used only when the linewidth option is not NULL if (!is.null(n <- options$linewidth)) { x = knitr:::split_lines(x) # any lines wider than n should be wrapped if (any(nchar(x) > n)) x = strwrap(x, width = n) x = paste(x, collapse = '\n') } hook_output(x, options) }, crop = knitr::hook_pdfcrop) ================================================ FILE: code/benchmark.R ================================================ # Aim: benchmark and record how long it takes to build the book on different setups remotes::install_cran("benchmarkme") sys_details = benchmarkme::get_sys_details() names(sys_details) sys_details$platform_info sys_details$r_version benchmark_df = tibble::tibble( command = "bookdown::render_book()", date_benchmarked = Sys.time(), build_time = NA, platform = sys_details$r_version$platform, cpu_model = sys_details$cpu$model_name, ram = sys_details$ram, commit = gert::git_commit_info()$id, commit_date = gert::git_commit_info()$time ) # Remove cache: unlink("_bookdown_files", recursive = TRUE) benchmark_build_time = system.time({ bookdown::render_book() }) benchmark_df$build_time = benchmark_build_time[3] benchmark_df$laptop_or_desktop = NA benchmark_df$comments = NA benchmarks_previous = readr::read_csv("benchmarks.csv") benchmarks_updated = rbind(benchmark_df, benchmarks_previous) readr::write_csv(benchmarks_updated, "benchmarks.csv") ================================================ FILE: code/chapters/01-introduction.R ================================================ ## \mainmatter ## ----gdsl, echo=FALSE, message=FALSE---------------------------------------------------------------- d = readr::read_csv("extdata/gis-vs-gds-table.csv") knitr::kable(x = d, caption = paste("Differences in emphasis between software", "packages (Graphical User Interface (GUI) of", "Geographic Information Systems (GIS) and R)."), caption.short = "Differences between GUI and CLI", booktabs = TRUE) ## Reproducibility is a major advantage of command line interfaces, but what does it mean in practice? ## We define it as follows: "A process in which the same results can be generated by others using publicly accessible code." ## ## This may sound simple and easy to achieve (which it is if you carefully maintain your R code in script files), but has profound implications for teaching and the scientific process [@pebesma_r_2012]. ## ----01-introduction-2, eval=FALSE, echo=FALSE------------------------------------------------------ ## a = osmdata::getbb("Hereford") ## b = osmdata::getbb("Bialystok") ## rowMeans(a) ## rowMeans(b) ## ----interactive-demo, eval=FALSE------------------------------------------------------------------- ## library(leaflet) ## popup = c("Robin", "Jakub", "Jannes") ## leaflet() |> ## addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") |> ## addMarkers(lng = c(-3, 23, 11), ## lat = c(52, 53, 49), ## popup = popup) ## ----interactive, fig.cap="The blue markers indicate where the authors are from. The basemap is a tiled image of the Earth at night provided by NASA. Interact with the online version at geocompr.robinlovelace.net, for example by zooming in and clicking on the pop-ups.", out.width="100%", fig.scap="Where the authors are from.", echo=FALSE---- if (knitr::is_latex_output()){ knitr::include_graphics("images/interactive.png") } else if (knitr::is_html_output()){ # library(leaflet) # popup = c("Robin", "Jakub", "Jannes") # interactive = leaflet() |> # addProviderTiles("NASAGIBS.ViirsEarthAtNight2012") |> # addMarkers(lng = c(-3, 23, 11), # lat = c(52, 53, 49), # popup = popup) # library(htmlwidgets) # saveWidget(interactive, file = "interactive.html") # file.copy("interactive.html", "~/geocompr/geocompr.github.io/static/img/interactive.html") knitr::include_url("https://geocompr.github.io/img/interactive.html") } ## ----cranlogs, fig.cap="Downloads of selected R packages for working with geographic data from early 2013 to present. The y axis shows the average number of dailly downloads from the popular cloud.r-project.org CRAN mirror with a 91-day rolling window (log scale).", echo=FALSE, fig.scap="The popularity of spatial packages in R."---- knitr::include_graphics("images/01-cranlogs.png") ## ----revdep, echo=FALSE, message=FALSE-------------------------------------------------------------- top_dls = readr::read_csv("extdata/top_dls.csv") knitr::kable(top_dls[1:5, 1:2], digits = 0, caption = paste("The top 5 most downloaded packages that depend", "on sf, in terms of average number of downloads", "per day over the previous month. As of", min(top_dls$date), " there are ", nrow(top_dls), " packages which import sf."), caption.short = "Top 5 most downloaded packages depending on sf.", booktabs = TRUE, col.names = c("Package", "Downloads")) # cranlogs::cran_top_downloads(when = "last-month") # most downloaded pkgs ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## # Aim: show n. pkgs that depend on sf and sp ## revdep_sp = devtools::revdep(pkg = "sp") ## length(revdep_sp) # 622 # 2022-05-29 ## revdep_sf = devtools::revdep(pkg = "sf") ## length(revdep_sf) # 479 # 2022-05-29 ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_01-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/02-spatial-data.R ================================================ ## ----02-spatial-data-1, eval=FALSE------------------------------------------------------------------ ## install.packages("sf") ## install.packages("terra") ## install.packages("spData") ## install.packages("spDataLarge", repos = "https://nowosad.r-universe.dev") ## We recommend following R instructions on [CRAN](https://cran.r-project.org/). ## If you're running Mac or Linux, the previous command to install **sf** may not work first time. ## These operating systems (OSs) have 'systems requirements' that are described in the package's [README](https://github.com/r-spatial/sf). ## Other OS-specific instructions can be found online, including the article *Installation of R 4.2 on Ubuntu 22.04.1 LTS and tips for spatial packages* on the [rtask.thinkr.fr](https://rtask.thinkr.fr/installation-of-r-4-2-on-ubuntu-22-04-lts-and-tips-for-spatial-packages/) website. ## ----02-spatial-data-3-1, message=TRUE-------------------------------------------------------------- library(sf) # classes and functions for vector data ## ----02-spatial-data-3-2, message=FALSE------------------------------------------------------------- library(terra) # classes and functions for raster data ## ----02-spatial-data-4, results='hide'-------------------------------------------------------------- library(spData) # load geographic data library(spDataLarge) # load larger geographic data ## Take care when using the word 'vector' as it can have two meanings in this book: ## geographic vector data and the `vector` class (note the `monospace` font) in R. ## The former is a data model, the latter is an R class just like `data.frame` and `matrix`. ## Still, there is a link between the two: the spatial coordinates which are at the heart of the geographic vector data model can be represented in R using `vector` objects. ## ----vectorplots-source, include=FALSE, eval=FALSE-------------------------------------------------- ## source("https://github.com/Robinlovelace/geocompr/raw/main/code/02-vectorplots.R") # generate subsequent figure ## ----vectorplots, fig.cap="Vector (point) data in which location of London (red X) is represented with reference to an origin (blue circle). The left plot represents a geographic CRS with an origin at 0° longitude and latitude. The right plot represents a projected CRS with an origin located in the sea west of the South West Peninsula.", out.width="49%", fig.show='hold', echo=FALSE, fig.scap="Vector (point) data."---- knitr::include_graphics(c("images/vector_lonlat.png", "images/vector_projected.png")) ## ----sf-ogc, fig.cap="Simple feature types fully supported by sf.", out.width="60%", echo=FALSE----- knitr::include_graphics("images/sf-classes.png") ## ----02-spatial-data-6, eval=FALSE------------------------------------------------------------------ ## vignette(package = "sf") # see which vignettes are available ## vignette("sf1") # an introduction to the package ## ----02-spatial-data-7, eval=FALSE, echo=FALSE------------------------------------------------------ ## vignette("sf1") # an introduction to the package ## vignette("sf2") # reading, writing and converting simple features ## vignette("sf3") # manipulating simple feature geometries ## vignette("sf4") # manipulating simple features ## vignette("sf5") # plotting simple features ## vignette("sf6") # miscellaneous long-form documentation ## vignette("sf7") # spherical geometry operations ## ----02-spatial-data-8------------------------------------------------------------------------------ class(world) names(world) ## ----world-all, fig.cap="Map of the world using the sf package, with a facet for each attribute.", warning=FALSE, fig.scap="Map of the world using the sf package."---- plot(world) ## ----02-spatial-data-9------------------------------------------------------------------------------ summary(world["lifeExp"]) ## The word `MULTIPOLYGON` in the summary output above refers to the geometry type of features (countries) in the `world` object. ## This representation is necessary for countries with islands such as Indonesia and Greece. ## Other geometry types are described in Section \@ref(geometry). ## ----02-spatial-data-11----------------------------------------------------------------------------- world_mini = world[1:2, 1:3] world_mini ## The preceding code chunk uses `=` to create a new object called `world_mini` in the command `world_mini = world[1:2, 1:3]`. ## This is called assignment. ## An equivalent command to achieve the same result is `world_mini <- world[1:2, 1:3]`. ## Although 'arrow assigment' is more commonly used, we use 'equals assignment' because it's slightly faster to type and easier to teach due to compatibility with commonly used languages such as Python and JavaScript. ## Which to use is largely a matter of preference as long as you're consistent (packages such as **styler** can be used to change style). ## --------------------------------------------------------------------------------------------------- world_dfr = st_read(system.file("shapes/world.gpkg", package = "spData")) world_tbl = read_sf(system.file("shapes/world.gpkg", package = "spData")) class(world_dfr) class(world_tbl) ## ----02-spatial-data-12, eval=FALSE----------------------------------------------------------------- ## library(sp) ## world_sp = as(world, "Spatial") # from an sf object to sp ## # sp functions ... ## world_sf = st_as_sf(world_sp) # from sp to sf ## ----sfplot, fig.cap="Plotting with sf, with multiple variables (left) and a single variable (right).", out.width="49%", fig.show='hold', warning=FALSE, fig.scap="Plotting with sf."---- plot(world[3:6]) plot(world["pop"]) ## ----02-spatial-data-14, warning=FALSE-------------------------------------------------------------- world_asia = world[world$continent == "Asia", ] asia = st_union(world_asia) ## ----asia, out.width='50%', fig.cap="Plot of Asia added as a layer on top of countries worldwide.", eval=FALSE---- ## plot(world["pop"], reset = FALSE) ## plot(asia, add = TRUE, col = "red") ## ----02-spatial-data-16, eval=FALSE----------------------------------------------------------------- ## plot(world["continent"], reset = FALSE) ## cex = sqrt(world$pop) / 10000 ## world_cents = st_centroid(world, of_largest = TRUE) ## plot(st_geometry(world_cents), add = TRUE, cex = cex) ## ----contpop, fig.cap="Country continents (represented by fill color) and 2015 populations (represented by circles, with area proportional to population).", echo=FALSE, warning=FALSE, fig.scap="Country continents and 2015 populations."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/02-contpop.R") ## ----02-spatial-data-17, eval=FALSE----------------------------------------------------------------- ## india = world[world$name_long == "India", ] ## plot(st_geometry(india), expandBB = c(0, 0.2, 0.1, 1), col = "gray", lwd = 3) ## plot(st_geometry(world_asia), add = TRUE) ## ----china, fig.cap="India in context, demonstrating the expandBB argument.", warning=FALSE, echo=FALSE, out.width="50%"---- old_par = par(mar = rep(0, 4)) india = world[world$name_long == "India", ] indchi = world_asia[grepl("Indi|Chi", world_asia$name_long), ] indchi_points = st_centroid(indchi) indchi_coords = st_coordinates(indchi_points) plot(st_geometry(india), expandBB = c(-0.2, 0.5, 0, 1), col = "gray", lwd = 3) plot(world_asia[0], add = TRUE) text(indchi_coords[, 1], indchi_coords[, 2], indchi$name_long) par(old_par) ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## waldo::compare(st_geometry(world), world[0]) ## ----sfcs, echo=FALSE, fig.cap="Point, linestring and polygon geometries.", fig.asp=0.4---- old_par = par(mfrow = c(1, 3), pty = "s", mar = c(0, 3, 1, 0)) plot(st_as_sfc(c("POINT(5 2)")), axes = TRUE, main = "POINT") plot(st_as_sfc("LINESTRING(1 5, 4 4, 4 1, 2 2, 3 2)"), axes = TRUE, main = "LINESTRING") plot(st_as_sfc("POLYGON((1 5, 2 2, 4 1, 4 4, 1 5))"), col="gray", axes = TRUE, main = "POLYGON") par(old_par) ## ----polygon_hole, echo=FALSE, out.width="30%", eval=FALSE------------------------------------------ ## # not printed - enough of these figures already (RL) ## par(pty = "s") ## plot(st_as_sfc("POLYGON((1 5, 2 2, 4 1, 4 4, 1 5), (2 4, 3 4, 3 3, 2 3, 2 4))"), col = "gray", axes = TRUE, main = "POLYGON with a hole") ## ----multis, echo=FALSE, fig.cap="Illustration of multi* geometries.", fig.asp=0.4------------------ old_par = par(mfrow = c(1, 3), pty = "s", mar = c(0, 3, 1, 0)) plot(st_as_sfc("MULTIPOINT (5 2, 1 3, 3 4, 3 2)"), axes = TRUE, main = "MULTIPOINT") plot(st_as_sfc("MULTILINESTRING ((1 5, 4 4, 4 1, 2 2, 3 2), (1 2, 2 4))"), axes = TRUE, main = "MULTILINESTRING") plot(st_as_sfc("MULTIPOLYGON (((1 5, 2 2, 4 1, 4 4, 1 5), (0 2, 1 2, 1 3, 0 3, 0 2)))"), col = "gray", axes = TRUE, main = "MULTIPOLYGON") par(old_par) ## ----geomcollection, echo=FALSE, fig.asp=1, fig.cap="Illustration of a geometry collection.", out.width="33%"---- # Plotted - it is referenced in ch5 (st_cast) old_par = par(pty = "s", mar = c(2, 3, 3, 0)) plot(st_as_sfc("GEOMETRYCOLLECTION (MULTIPOINT (5 2, 1 3, 3 4, 3 2), LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2))"), axes = TRUE, main = "GEOMETRYCOLLECTION", col = 1) par(old_par) ## ----02-sfdiagram, fig.cap="Building blocks of sf objects.", echo=FALSE----------------------------- # source("code/02-sfdiagram.R") knitr::include_graphics("images/02-sfdiagram.png") ## ----02-spatial-data-33----------------------------------------------------------------------------- lnd_point = st_point(c(0.1, 51.5)) # sfg object lnd_geom = st_sfc(lnd_point, crs = 4326) # sfc object lnd_attrib = data.frame( # data.frame object name = "London", temperature = 25, date = as.Date("2017-06-21") ) lnd_sf = st_sf(lnd_attrib, geometry = lnd_geom) # sf object ## ----02-spatial-data-34, eval=FALSE----------------------------------------------------------------- ## lnd_sf ## #> Simple feature collection with 1 features and 3 fields ## #> ... ## #> name temperature date geometry ## #> 1 London 25 2017-06-21 POINT (0.1 51.5) ## ----02-spatial-data-35----------------------------------------------------------------------------- class(lnd_sf) ## ----02-spatial-data-36, eval=FALSE, echo=FALSE----------------------------------------------------- ## ruan_point = st_point(c(-9, 53)) ## # sfc object ## our_geometry = st_sfc(lnd_point, ruan_point, crs = 4326) ## # data.frame object ## our_attributes = data.frame( ## name = c("London", "Ruan"), ## temperature = c(25, 13), ## date = c(as.Date("2017-06-21"), as.Date("2017-06-22")), ## category = c("city", "village"), ## automatic = c(FALSE, TRUE)) ## # sf object ## sf_points = st_sf(our_attributes, geometry = our_geometry) ## ----02-spatial-data-18----------------------------------------------------------------------------- st_point(c(5, 2)) # XY point st_point(c(5, 2, 3)) # XYZ point st_point(c(5, 2, 1), dim = "XYM") # XYM point st_point(c(5, 2, 3, 1)) # XYZM point ## ----02-spatial-data-19----------------------------------------------------------------------------- # the rbind function simplifies the creation of matrices ## MULTIPOINT multipoint_matrix = rbind(c(5, 2), c(1, 3), c(3, 4), c(3, 2)) st_multipoint(multipoint_matrix) ## LINESTRING linestring_matrix = rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)) st_linestring(linestring_matrix) ## ----02-spatial-data-20----------------------------------------------------------------------------- ## POLYGON polygon_list = list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))) st_polygon(polygon_list) ## ----02-spatial-data-21----------------------------------------------------------------------------- ## POLYGON with a hole polygon_border = rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5)) polygon_hole = rbind(c(2, 4), c(3, 4), c(3, 3), c(2, 3), c(2, 4)) polygon_with_hole_list = list(polygon_border, polygon_hole) st_polygon(polygon_with_hole_list) ## ----02-spatial-data-22----------------------------------------------------------------------------- ## MULTILINESTRING multilinestring_list = list(rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)), rbind(c(1, 2), c(2, 4))) st_multilinestring((multilinestring_list)) ## ----02-spatial-data-23----------------------------------------------------------------------------- ## MULTIPOLYGON multipolygon_list = list(list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))), list(rbind(c(0, 2), c(1, 2), c(1, 3), c(0, 3), c(0, 2)))) st_multipolygon(multipolygon_list) ## ----02-spatial-data-24, eval=FALSE----------------------------------------------------------------- ## ## GEOMETRYCOLLECTION ## geometrycollection_list = list(st_multipoint(multipoint_matrix), ## st_linestring(linestring_matrix)) ## st_geometrycollection(geometrycollection_list) ## #> GEOMETRYCOLLECTION (MULTIPOINT (5 2, 1 3, 3 4, 3 2), ## #> LINESTRING (1 5, 4 4, 4 1, 2 2, 3 2)) ## ----02-spatial-data-25----------------------------------------------------------------------------- # sfc POINT point1 = st_point(c(5, 2)) point2 = st_point(c(1, 3)) points_sfc = st_sfc(point1, point2) points_sfc ## ----02-spatial-data-26----------------------------------------------------------------------------- # sfc POLYGON polygon_list1 = list(rbind(c(1, 5), c(2, 2), c(4, 1), c(4, 4), c(1, 5))) polygon1 = st_polygon(polygon_list1) polygon_list2 = list(rbind(c(0, 2), c(1, 2), c(1, 3), c(0, 3), c(0, 2))) polygon2 = st_polygon(polygon_list2) polygon_sfc = st_sfc(polygon1, polygon2) st_geometry_type(polygon_sfc) ## ----02-spatial-data-27----------------------------------------------------------------------------- # sfc MULTILINESTRING multilinestring_list1 = list(rbind(c(1, 5), c(4, 4), c(4, 1), c(2, 2), c(3, 2)), rbind(c(1, 2), c(2, 4))) multilinestring1 = st_multilinestring((multilinestring_list1)) multilinestring_list2 = list(rbind(c(2, 9), c(7, 9), c(5, 6), c(4, 7), c(2, 7)), rbind(c(1, 7), c(3, 8))) multilinestring2 = st_multilinestring((multilinestring_list2)) multilinestring_sfc = st_sfc(multilinestring1, multilinestring2) st_geometry_type(multilinestring_sfc) ## ----02-spatial-data-28----------------------------------------------------------------------------- # sfc GEOMETRY point_multilinestring_sfc = st_sfc(point1, multilinestring1) st_geometry_type(point_multilinestring_sfc) ## ----02-spatial-data-29----------------------------------------------------------------------------- st_crs(points_sfc) ## ----02-spatial-data-30, eval=FALSE----------------------------------------------------------------- ## # Set the CRS with an identifier referring to an 'EPSG' CRS code: ## points_sfc_wgs = st_sfc(point1, point2, crs = "EPSG:4326") ## st_crs(points_sfc_wgs) # print CRS (only first 4 lines of output shown) ## #> Coordinate Reference System: ## #> User input: EPSG:4326 ## #> wkt: ## #> GEOGCRS["WGS 84", ## #> ... ## ----sfheaers-setup, echo=FALSE--------------------------------------------------------------------- ## Detatch {sf} to remove 'print' methods ## because I want to show the underlying structure ## ## library(sf) will be called later # unloadNamespace("sf") # errors # pkgload::unload("sf") ## --------------------------------------------------------------------------------------------------- v = c(1, 1) v_sfg_sfh = sfheaders::sfg_point(obj = v) ## ----sfheaders-sfg_point, eval=FALSE---------------------------------------------------------------- ## v_sfg_sfh # printing without sf loaded ## #> [,1] [,2] ## #> [1,] 1 1 ## #> attr(,"class") ## #> [1] "XY" "POINT" "sfg" ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## v_sfg_sfh = sf::st_point(v) ## --------------------------------------------------------------------------------------------------- v_sfg_sf = st_point(v) print(v_sfg_sf) == print(v_sfg_sfh) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## # (although `sfg` objects created with **sfheaders** have a dimension while `sfg` objects created with the **sf** package do not) ## waldo::compare(v_sfg_sf, v_sfg_sfh) ## dim(v_sfg_sf) ## dim(v_sfg_sfh) ## attr(v_sfg_sfh, "dim") ## ----sfheaders-sfg_linestring----------------------------------------------------------------------- # matrices m = matrix(1:8, ncol = 2) sfheaders::sfg_linestring(obj = m) # data.frames df = data.frame(x = 1:4, y = 4:1) sfheaders::sfg_polygon(obj = df) ## ----sfheaders-sfc_point2, eval=FALSE--------------------------------------------------------------- ## sfheaders::sfc_point(obj = v) ## sfheaders::sfc_linestring(obj = m) ## sfheaders::sfc_polygon(obj = df) ## ----sfheaders-sfc_point, eval=FALSE---------------------------------------------------------------- ## sfheaders::sf_point(obj = v) ## sfheaders::sf_linestring(obj = m) ## sfheaders::sf_polygon(obj = df) ## ----sfheaders-crs---------------------------------------------------------------------------------- df_sf = sfheaders::sf_polygon(obj = df) st_crs(df_sf) = "EPSG:4326" ## --------------------------------------------------------------------------------------------------- sf_use_s2() ## --------------------------------------------------------------------------------------------------- india_buffer_with_s2 = st_buffer(india, 1) sf_use_s2(FALSE) india_buffer_without_s2 = st_buffer(india, 1) ## ----s2example, echo=FALSE, fig.cap="Example of the consequences of turning off the S2 geometry engine. Both representations of a buffer around India were created with the same command but the purple polygon object was created with S2 switched on, resulting in a buffer of 1 m. The larger light green polygon was created with S2 switched off, resulting in a buffer with inaccurate units of degrees longitude/latitude.", fig.asp=0.75---- library(tmap) tm1 = tm_shape(india_buffer_with_s2) + tm_fill(col = hcl.colors(4, palette = "purple green")[3]) + tm_shape(india) + tm_fill(col = "gray95") + tm_layout(main.title = "st_buffer() with dist = 1", title = "s2 switched on (default)") tm2 = tm_shape(india_buffer_without_s2) + tm_fill(col = hcl.colors(4, palette = "purple green")[3]) + tm_shape(india) + tm_fill(col = "gray95") + tm_layout(main.title = " ", title = "s2 switched off") tmap_arrange(tm1, tm2, ncol = 2) ## --------------------------------------------------------------------------------------------------- sf_use_s2(TRUE) ## Although the **sf**'s used of S2 makes sense in many cases, in some cases there are good reasons for turning S2 off for the duration of an R session or even for an entire project. ## As documented in issue [1771](https://github.com/r-spatial/sf/issues/1771) in **sf**'s GitHub repo, the default behavior can make code that would work with S2 turned off (and with older versions of **sf**) fail. ## These edge cases include operations on polygons that are not valid according to S2's stricter definition. ## If you see error messages such as `#> Error in s2_geography_from_wkb ...` it may be worth trying the command that generated the error message again, after turning off S2. ## To turn off S2 for the entirety of a project, you can create a file called .Rprofile in the root directory (the main folder) of your project containing the command `sf::sf_use_s2(FALSE)`. ## ----raster-intro-plot, echo = FALSE, fig.cap = "Raster data types: (A) cell IDs, (B) cell values, (C) a colored raster map.", fig.scap="Raster data types.", fig.asp=0.5, message=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/02-raster-intro-plot.R", print.eval = TRUE) ## ----raster-intro-plot2, echo=FALSE, fig.cap="Examples of continuous and categorical rasters.", warning=FALSE, message=FALSE---- source("code/02-raster-intro-plot2.R", print.eval = TRUE) # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146617327-45919232-a6a3-4d9d-a158-afa87f47381b.png") ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## # # test raster/terra conversions ## # See https://github.com/rspatial/terra/issues/399 ## ----02-spatial-data-37, message=FALSE-------------------------------------------------------------- raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") my_rast = rast(raster_filepath) class(my_rast) ## ----02-spatial-data-38----------------------------------------------------------------------------- my_rast ## ----basic-new-raster-plot, fig.cap="Basic raster plot."-------------------------------------------- plot(my_rast) ## ----02-spatial-data-41----------------------------------------------------------------------------- single_raster_file = system.file("raster/srtm.tif", package = "spDataLarge") single_rast = rast(raster_filepath) ## ----02-spatial-data-42----------------------------------------------------------------------------- new_raster = rast(nrows = 6, ncols = 6, resolution = 0.5, xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, vals = 1:36) ## ----02-spatial-data-45----------------------------------------------------------------------------- multi_raster_file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(multi_raster_file) multi_rast ## ----02-spatial-data-47----------------------------------------------------------------------------- nlyr(multi_rast) ## --------------------------------------------------------------------------------------------------- multi_rast3 = subset(multi_rast, 3) multi_rast4 = subset(multi_rast, "landsat_4") ## --------------------------------------------------------------------------------------------------- multi_rast34 = c(multi_rast3, multi_rast4) ## Most `SpatRaster` objects do not store raster values, but rather a pointer to the file itself. ## This has a significant side-effect -- they cannot be directly saved to `".rds"` or `".rda"` files or used in cluster computing. ## In these cases, there are two possible solutions: (1) use of the `wrap()` function that creates a special kind of temporary object that can be saved as an R object or used in cluster computing, or (2) save the object as a regular raster with `writeRaster()`. ## ----datum-fig, echo=FALSE, message=FALSE, fig.cap="(ref:datum-fig)", fig.scap="Geocentric and local geodetic datums on a geoid."---- knitr::include_graphics("images/02_datum_fig.png") ## ----vector-crs, echo=FALSE, fig.cap="Examples of geographic (WGS 84; left) and projected (NAD83 / UTM zone 12N; right) coordinate systems for a vector data type.", message=FALSE, fig.asp=0.56, fig.scap="Examples of geographic and projected CRSs (vector data)."---- # source("https://github.com/Robinlovelace/geocompr/raw/main/code/02-vector-crs.R") knitr::include_graphics("images/02_vector_crs.png") ## ----02-spatial-data-57----------------------------------------------------------------------------- luxembourg = world[world$name_long == "Luxembourg", ] ## ----02-spatial-data-58----------------------------------------------------------------------------- st_area(luxembourg) # requires the s2 package in recent versions of sf ## ----02-spatial-data-59----------------------------------------------------------------------------- st_area(luxembourg) / 1000000 ## ----02-spatial-data-60----------------------------------------------------------------------------- units::set_units(st_area(luxembourg), km^2) ## ----02-spatial-data-61----------------------------------------------------------------------------- res(my_rast) ## ----02-spatial-data-62, warning=FALSE, message=FALSE----------------------------------------------- repr = project(my_rast, "EPSG:26912") res(repr) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_02-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/03-attribute-operations.R ================================================ ## ----03-attribute-operations-1, message=FALSE------------------------------------------------------- library(sf) # vector data package introduced in Chapter 2 library(terra) # raster data package introduced in Chapter 2 library(dplyr) # tidyverse package for data frame manipulation ## ----03-attribute-operations-2, results='hide'------------------------------------------------------ library(spData) # spatial data package introduced in Chapter 2 ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## # Aim: find a bus stop in central London ## library(osmdata) ## london_coords = c(-0.1, 51.5) ## london_bb = c(-0.11, 51.49, -0.09, 51.51) ## bb = tmaptools::bb(london_bb) ## osm_data = opq(bbox = london_bb) |> ## add_osm_feature(key = "highway", value = "bus_stop") |> ## osmdata_sf() ## osm_data_points = osm_data$osm_points ## osm_data_points[4, ] ## point_vector = round(sf::st_coordinates(osm_data_points[4, ]), 3) ## point_df = data.frame(name = "London bus stop", point_vector) ## point_sf = sf::st_as_sf(point_df, coords = c("X", "Y")) ## ----03-attribute-operations-3, eval=FALSE---------------------------------------------------------- ## methods(class = "sf") # methods for sf objects, first 12 shown ## ----03-attribute-operations-4---------------------------------------------------------------------- #> [1] aggregate cbind coerce #> [4] initialize merge plot #> [7] print rbind [ #> [10] [[<- $<- show ## ----03-attribute-operations-5, eval=FALSE, echo=FALSE---------------------------------------------- ## # Another way to show sf methods: ## attributes(methods(class = "sf"))$info |> ## dplyr::filter(!visible) ## The geometry column of `sf` objects is typically called `geometry` or `geom` but any name can be used. ## The following command, for example, creates a geometry column named g: ## ## `st_sf(data.frame(n = world$name_long), g = world$geom)` ## ## This enables geometries imported from spatial databases to have a variety of names such as `wkb_geometry` and `the_geom`. ## ----03-attribute-operations-7---------------------------------------------------------------------- class(world) # it's an sf object and a (tidy) data frame dim(world) # it is a two-dimensional object, with 177 rows and 11 columns ## ----03-attribute-operations-8---------------------------------------------------------------------- world_df = st_drop_geometry(world) class(world_df) ncol(world_df) ## ----03-attribute-operations-9, eval=FALSE---------------------------------------------------------- ## world[1:6, ] # subset rows by position ## world[, 1:3] # subset columns by position ## world[1:6, 1:3] # subset rows and columns by position ## world[, c("name_long", "pop")] # columns by name ## world[, c(T, T, F, F, F, F, F, T, T, F, F)] # by logical indices ## world[, 888] # an index representing a non-existent column ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## # these fail ## world[c(1, 5), c(T, T)] ## world[c(1, 5), c(T, T, F, F, F, F, F, T, T, F, F, F)] ## ----03-attribute-operations-10--------------------------------------------------------------------- i_small = world$area_km2 < 10000 summary(i_small) # a logical vector small_countries = world[i_small, ] ## ----03-attribute-operations-11--------------------------------------------------------------------- small_countries = world[world$area_km2 < 10000, ] ## ----03-attribute-operations-12, eval=FALSE--------------------------------------------------------- ## small_countries = subset(world, area_km2 < 10000) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## # Aim: benchmark base vs. dplyr subsetting ## # Could move elsewhere? ## i = sample(nrow(world), size = 10) ## benchmark_subset = bench::mark( ## world[i, ], ## world |> slice(i) ## ) ## benchmark_subset[c("expression", "itr/sec", "mem_alloc")] ## # # October 2021 on laptop with CRAN version of dplyr: ## # # A tibble: 2 × 3 ## # expression `itr/sec` mem_alloc ## # ## # 1 world[i, ] 1744. 5.55KB ## # 2 world |> slice(i) 671. 4.45KB ## ----03-attribute-operations-14--------------------------------------------------------------------- world1 = select(world, name_long, pop) names(world1) ## ----03-attribute-operations-15--------------------------------------------------------------------- # all columns between name_long and pop (inclusive) world2 = select(world, name_long:pop) ## ----03-attribute-operations-16--------------------------------------------------------------------- # all columns except subregion and area_km2 (inclusive) world3 = select(world, -subregion, -area_km2) ## ----03-attribute-operations-17--------------------------------------------------------------------- world4 = select(world, name_long, population = pop) ## ----03-attribute-operations-18, eval=FALSE--------------------------------------------------------- ## world5 = world[, c("name_long", "pop")] # subset columns by name ## names(world5)[names(world5) == "pop"] = "population" # rename column manually ## ----03-attribute-operations-21, eval = FALSE------------------------------------------------------- ## pull(world, pop) ## world$pop ## world[["pop"]] ## ----03-attribute-operations-19, eval=FALSE, echo=FALSE--------------------------------------------- ## # create throw-away data frame ## d = data.frame(pop = 1:10, area = 1:10) ## # return data frame object when selecting a single column ## d[, "pop", drop = FALSE] # equivalent to d["pop"] ## select(d, pop) ## # return a vector when selecting a single column ## d[, "pop"] ## pull(d, pop) ## ----03-attribute-operations-20, echo=FALSE, eval=FALSE--------------------------------------------- ## x1 = d[, "pop", drop = FALSE] # equivalent to d["pop"] ## x2 = d["pop"] ## identical(x1, x2) ## ----03-attribute-operations-22, eval=FALSE--------------------------------------------------------- ## slice(world, 1:6) ## ----03-attribute-operations-23, eval=FALSE--------------------------------------------------------- ## world7 = filter(world ,area_km2 < 10000) # countries with a small area ## world7 = filter(world, lifeExp > 82) # with high life expectancy ## ----operators0, echo=FALSE------------------------------------------------------------------------- if (knitr::is_html_output()){ operators = c("`==`", "`!=`", "`>`, `<`", "`>=`, `<=`", "`&`, |, `!`") } else { operators = c("==", "!=", ">, <", ">=, <=", "&, |, !") } ## ----operators, echo=FALSE-------------------------------------------------------------------------- operators_exp = c("Equal to", "Not equal to", "Greater/Less than", "Greater/Less than or equal", "Logical operators: And, Or, Not") knitr::kable(tibble(Symbol = operators, Name = operators_exp), caption = paste("Comparison operators that return Booleans", "(TRUE/FALSE)."), caption.short = "Comparison operators that return Booleans.", booktabs = TRUE) ## ----03-attribute-operations-24--------------------------------------------------------------------- world7 = world |> filter(continent == "Asia") |> select(name_long, continent) |> slice(1:5) ## ----03-attribute-operations-25--------------------------------------------------------------------- world8 = slice( select( filter(world, continent == "Asia"), name_long, continent), 1:5) ## ----03-attribute-operations-25-2------------------------------------------------------------------- world9_filtered = filter(world, continent == "Asia") world9_selected = select(world9_filtered, continent) world9 = slice(world9_selected, 1:5) ## ----03-attribute-operations-26--------------------------------------------------------------------- world_agg1 = aggregate(pop ~ continent, FUN = sum, data = world, na.rm = TRUE) class(world_agg1) ## ----03-attribute-operations-27--------------------------------------------------------------------- world_agg2 = aggregate(world["pop"], list(world$continent), FUN = sum, na.rm = TRUE) class(world_agg2) nrow(world_agg2) ## ----03-attribute-operations-28--------------------------------------------------------------------- world_agg3 = world |> group_by(continent) |> summarize(pop = sum(pop, na.rm = TRUE)) ## ----03-attribute-operations-29--------------------------------------------------------------------- world_agg4 = world |> group_by(continent) |> summarize(pop = sum(pop, na.rm = TRUE), `area_sqkm` = sum(area_km2), n = n()) ## ----03-attribute-operations-30--------------------------------------------------------------------- world_agg5 = world |> st_drop_geometry() |> # drop the geometry for speed select(pop, continent, area_km2) |> # subset the columns of interest group_by(continent) |> # group by continent and summarize: summarize(Pop = sum(pop, na.rm = TRUE), Area = sum(area_km2), N = n()) |> mutate(Density = round(Pop / Area)) |> # calculate population density slice_max(Pop, n = 3) |> # keep only the top 3 arrange(desc(N)) # arrange in order of n. countries ## ----continents, echo=FALSE------------------------------------------------------------------------- options(scipen = 999) knitr::kable( world_agg5, caption = "The top three most populous continents ordered by number of countries.", caption.short = "Top three most populous continents.", booktabs = TRUE ) ## More details are provided in the help pages (which can be accessed via `?summarize` and `vignette(package = "dplyr")` and Chapter 5 of [R for Data Science](http://r4ds.had.co.nz/transform.html#grouped-summaries-with-summarize). ## ----03-attribute-operations-32, warning=FALSE------------------------------------------------------ world_coffee = left_join(world, coffee_data) class(world_coffee) ## ----coffeemap, fig.cap="World coffee production (thousand 60-kg bags) by country, 2017. Source: International Coffee Organization.", fig.scap="World coffee production by country."---- names(world_coffee) plot(world_coffee["coffee_production_2017"]) ## ----03-attribute-operations-33, warning=FALSE------------------------------------------------------ coffee_renamed = rename(coffee_data, nm = name_long) world_coffee2 = left_join(world, coffee_renamed, by = c(name_long = "nm")) ## ----03-attribute-operations-34, eval=FALSE, echo=FALSE--------------------------------------------- ## identical(world_coffee, world_coffee2) ## nrow(world) ## nrow(world_coffee) ## ----03-attribute-operations-35, warning=FALSE------------------------------------------------------ world_coffee_inner = inner_join(world, coffee_data) nrow(world_coffee_inner) ## ----03-attribute-operations-36--------------------------------------------------------------------- setdiff(coffee_data$name_long, world$name_long) ## ----03-attribute-operations-37--------------------------------------------------------------------- (drc = stringr::str_subset(world$name_long, "Dem*.+Congo")) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## world$name_long[grepl(pattern = "Dem*.+Congo", world$name_long)] # base R ## ----03-attribute-operations-38, eval=FALSE, echo=FALSE--------------------------------------------- ## # aim: test names in coffee_data and world objects ## str_subset(coffee_data$name_long, "Ivo|Congo,") ## .Last.value %in% str_subset(world$name_long, "Ivo|Dem*.+Congo") ## ----03-attribute-operations-39, warning=FALSE------------------------------------------------------ coffee_data$name_long[grepl("Congo,", coffee_data$name_long)] = drc world_coffee_match = inner_join(world, coffee_data) nrow(world_coffee_match) ## ----03-attribute-operations-40, warning=FALSE------------------------------------------------------ coffee_world = left_join(coffee_data, world) class(coffee_world) ## In most cases, the geometry column is only useful in an `sf` object. ## The geometry column can only be used for creating maps and spatial operations if R 'knows' it is a spatial object, defined by a spatial package such as **sf**. ## Fortunately, non-spatial data frames with a geometry list column (like `coffee_world`) can be coerced into an `sf` object as follows: `st_as_sf(coffee_world)`. ## ----03-attribute-operations-42--------------------------------------------------------------------- world_new = world # do not overwrite our original data world_new$pop_dens = world_new$pop / world_new$area_km2 ## ----03-attribute-operations-43, eval=FALSE--------------------------------------------------------- ## world |> ## mutate(pop_dens = pop / area_km2) ## ----03-attribute-operations-44, eval=FALSE--------------------------------------------------------- ## world |> ## transmute(pop_dens = pop / area_km2) ## ----03-attribute-operations-45, eval=FALSE--------------------------------------------------------- ## world_unite = world |> ## tidyr::unite("con_reg", continent:region_un, sep = ":", remove = TRUE) ## ----03-attribute-operations-46, eval=FALSE--------------------------------------------------------- ## world_separate = world_unite |> ## tidyr::separate(con_reg, c("continent", "region_un"), sep = ":") ## ----03-attribute-operations-47, echo=FALSE, eval=FALSE--------------------------------------------- ## identical(world, world_separate) ## ----03-attribute-operations-48, eval=FALSE--------------------------------------------------------- ## world |> ## rename(name = name_long) ## ----03-attribute-operations-49, eval=FALSE, echo=FALSE--------------------------------------------- ## abbreviate(names(world), minlength = 1) |> dput() ## ----03-attribute-operations-50, eval=FALSE--------------------------------------------------------- ## new_names = c("i", "n", "c", "r", "s", "t", "a", "p", "l", "gP", "geom") ## world_new_names = world |> ## setNames(new_names) ## ----03-attribute-operations-51--------------------------------------------------------------------- world_data = world |> st_drop_geometry() class(world_data) ## ----03-attribute-operations-52, message=FALSE, eval = FALSE---------------------------------------- ## elev = rast(nrows = 6, ncols = 6, resolution = 0.5, ## xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, ## vals = 1:36) ## ----03-attribute-operations-53, eval = FALSE------------------------------------------------------- ## grain_order = c("clay", "silt", "sand") ## grain_char = sample(grain_order, 36, replace = TRUE) ## grain_fact = factor(grain_char, levels = grain_order) ## grain = rast(nrows = 6, ncols = 6, resolution = 0.5, ## xmin = -1.5, xmax = 1.5, ymin = -1.5, ymax = 1.5, ## vals = grain_fact) ## ----03-attribute-operations-54, include = FALSE---------------------------------------------------- elev = rast(system.file("raster/elev.tif", package = "spData")) grain = rast(system.file("raster/grain.tif", package = "spData")) ## ----03-attribute-operations-56--------------------------------------------------------------------- levels(grain) = data.frame(value = c(0, 1, 2), wetness = c("wet", "moist", "dry")) levels(grain) ## ----cont-raster, echo = FALSE, message = FALSE, fig.asp=0.5, fig.cap = "Raster datasets with numeric (left) and categorical values (right).", fig.scap="Raster datasets with numeric and categorical values.", warning=FALSE---- # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146617366-7308535b-30f6-4c87-83f7-21702c7d993b.png") source("https://github.com/Robinlovelace/geocompr/raw/main/code/03-cont-raster-plot.R", print.eval = TRUE) ## Categorical raster objects can also store information about the colors associated with each value using a color table. ## The color table is a data frame with three (red, green, blue) or four (alpha) columns, where each row relates to one value. ## Color tables in **terra** can be viewed or set with the `coltab()` function (see `?coltab`). ## Importantly, saving a raster object with a color table to a file (e.g., GeoTIFF) will also save the color information. ## ----03-attribute-operations-58, eval = FALSE------------------------------------------------------- ## # row 1, column 1 ## elev[1, 1] ## # cell ID 1 ## elev[1] ## ----03-attribute-operations-60, results='hide'----------------------------------------------------- elev[1, 1] = 0 elev[] ## ----03-attribute-operations-61--------------------------------------------------------------------- elev[1, c(1, 2)] = 0 ## ----03-attribute-operations-61b, eval=FALSE-------------------------------------------------------- ## two_layers = c(grain, elev) ## two_layers[1] = cbind(c(1), c(4)) ## two_layers[] ## ----03-attribute-operations-62, eval = FALSE------------------------------------------------------- ## global(elev, sd) ## If you provide the `summary()` and `global()` functions with a multi-layered raster object, they will summarize each layer separately, as can be illustrated by running: `summary(c(elev, grain))`. ## ----03-attribute-operations-64, eval=FALSE--------------------------------------------------------- ## hist(elev) ## Some function names clash between packages (e.g., a function with the name `extract()` exist in both **terra** and **tidyr** packages). ## In addition to not loading packages by referring to functions verbosely (e.g., `tidyr::extract()`), another way to prevent function names clashes is by unloading the offending package with `detach()`. ## The following command, for example, unloads the **terra** package (this can also be done in the *package* tab which resides by default in the right-bottom pane in RStudio): `detach("package:terra", unload = TRUE, force = TRUE)`. ## The `force` argument makes sure that the package will be detached even if other packages depend on it. ## This, however, may lead to a restricted usability of packages depending on the detached package, and it is therefore not recommended. ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_03-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/04-spatial-operations.R ================================================ ## ----04-spatial-operations-1, message=FALSE, results='hide'----------------------------------------- library(sf) library(terra) library(dplyr) library(spData) ## ----04-spatial-operations-1-1---------------------------------------------------------------------- elev = rast(system.file("raster/elev.tif", package = "spData")) grain = rast(system.file("raster/grain.tif", package = "spData")) ## It is important to note that spatial operations that use two spatial objects rely on both objects having the same coordinate reference system, a topic that was introduced in Section \@ref(crs-intro) and which will be covered in more depth in Chapter \@ref(reproj-geo-data). ## ----04-spatial-operations-3------------------------------------------------------------------------ canterbury = nz |> filter(Name == "Canterbury") canterbury_height = nz_height[canterbury, ] ## ----nz-subset, echo=FALSE, warning=FALSE, fig.cap="Spatial subsetting with red triangles representing 101 high points in New Zealand, clustered near the central Canterbuy region (left). The points in Canterbury were created with the `[` subsetting operator (highlighted in gray, right).", fig.scap="Spatial subsetting.", message=FALSE---- library(tmap) p_hpnz1 = tm_shape(nz) + tm_polygons(col = "white") + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.25) + tm_layout(main.title = "High points in New Zealand", main.title.size = 1, bg.color = "lightblue") p_hpnz2 = tm_shape(nz) + tm_polygons(col = "white") + tm_shape(canterbury) + tm_fill(col = "gray") + tm_shape(canterbury_height) + tm_symbols(shape = 2, col = "red", size = 0.25) + tm_layout(main.title = "High points in Canterbury", main.title.size = 1, bg.color = "lightblue") tmap_arrange(p_hpnz1, p_hpnz2, ncol = 2) ## ----04-spatial-operations-4, eval=FALSE------------------------------------------------------------ ## nz_height[canterbury, , op = st_disjoint] ## Note the empty argument --- denoted with `, ,` --- in the preceding code chunk is included to highlight `op`, the third argument in `[` for `sf` objects. ## One can use this to change the subsetting operation in many ways. ## `nz_height[canterbury, 2, op = st_disjoint]`, for example, returns the same rows but only includes the second attribute column (see `` sf:::`[.sf` `` and the `?sf` for details). ## ----04-spatial-operations-6------------------------------------------------------------------------ sel_sgbp = st_intersects(x = nz_height, y = canterbury) class(sel_sgbp) sel_sgbp sel_logical = lengths(sel_sgbp) > 0 canterbury_height2 = nz_height[sel_logical, ] ## Note: another way to return a logical output is by setting `sparse = FALSE` (meaning 'return a dense matrix not a sparse one') in operators such as `st_intersects()`. The command `st_intersects(x = nz_height, y = canterbury, sparse = FALSE)[, 1]`, for example, would return an output identical to `sel_logical`. ## Note: the solution involving `sgbp` objects is more generalizable though, as it works for many-to-many operations and has lower memory requirements. ## --------------------------------------------------------------------------------------------------- canterbury_height3 = nz_height |> st_filter(y = canterbury, .predicate = st_intersects) ## ----04-spatial-operations-7b-old, eval=FALSE, echo=FALSE------------------------------------------- ## # Additional tests of subsetting ## canterbury_height4 = nz_height |> ## filter(st_intersects(x = _, y = canterbury, sparse = FALSE)) ## canterbury_height5 = nz_height |> ## filter(sel_logical) ## identical(canterbury_height3, canterbury_height4) ## identical(canterbury_height3, canterbury_height5) ## identical(canterbury_height2, canterbury_height4) ## identical(canterbury_height, canterbury_height4) ## waldo::compare(canterbury_height2, canterbury_height4) ## ----relations, echo=FALSE, fig.cap="Topological relations between vector geometries, inspired by figures 1 and 2 in Egenhofer and Herring (1990). The relations for which the function(x, y) is true are printed for each geometry pair, with x represented in pink and y represented in blue. The nature of the spatial relationship for each pair is described by the Dimensionally Extended 9-Intersection Model string.", fig.show='hold', message=FALSE, fig.asp=0.66, warning=FALSE---- # source("https://github.com/Robinlovelace/geocompr/raw/c4-v2-updates-rl/code/de_9im.R") source("code/de_9im.R") library(sf) xy2sfc = function(x, y) st_sfc(st_polygon(list(cbind(x, y)))) p1 = xy2sfc(x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0)) p2 = xy2sfc(x = c(0, 1, 1, 0), y = c(0, 0, 0.5, 0)) p3 = xy2sfc(x = c(0, 1, 1, 0), y = c(0, 0, 0.7, 0)) p4 = xy2sfc(x = c(0.7, 0.7, 0.9, 0.7), y = c(0.8, 0.5, 0.5, 0.8)) p5 = xy2sfc(x = c(0.6, 0.7, 1, 0.6), y = c(0.7, 0.5, 0.5, 0.7)) p6 = xy2sfc(x = c(0.1, 1, 1, 0.1), y = c(0, 0, 0.3, 0)) p7 = xy2sfc(x = c(0.05, 0.05, 0.6, 0.5, 0.05), y = c(0.4, 0.97, 0.97, 0.4, 0.4)) # todo: add 3 more with line/point relations? tmap::tmap_arrange(de_9im(p1, p2), de_9im(p1, p3), de_9im(p1, p4), de_9im(p7, p1), de_9im(p1, p5), de_9im(p1, p6), nrow = 2) ## --------------------------------------------------------------------------------------------------- polygon_matrix = cbind( x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0) ) polygon_sfc = st_sfc(st_polygon(list(polygon_matrix))) ## --------------------------------------------------------------------------------------------------- line_sfc = st_sfc(st_linestring(cbind( x = c(0.4, 1), y = c(0.2, 0.5) ))) # create points point_df = data.frame( x = c(0.2, 0.7, 0.4), y = c(0.1, 0.2, 0.8) ) point_sf = st_as_sf(point_df, coords = c("x", "y")) ## ----relation-objects, echo=FALSE, fig.cap="Points (`point_df` 1 to 3), line and polygon objects arranged to illustrate topological relations.", fig.asp=1, out.width="50%", fig.scap="Demonstration of topological relations."---- par(pty = "s") plot(polygon_sfc, border = "red", col = "gray", axes = TRUE) plot(line_sfc, lwd = 5, add = TRUE) plot(point_sf, add = TRUE, lab = 1:4, cex = 2) text(point_df[, 1] + 0.02, point_df[, 2] + 0.04, 1:3, cex = 1.3) ## ----04-spatial-operations-9, eval=FALSE------------------------------------------------------------ ## st_intersects(point_sf, polygon_sfc) ## #> Sparse geometry binary predicate... `intersects' ## #> 1: 1 ## #> 2: (empty) ## #> 3: 1 ## ----04-spatial-operations-10----------------------------------------------------------------------- st_intersects(point_sf, polygon_sfc, sparse = FALSE) ## ----04-spatial-operations-9-2, eval=FALSE---------------------------------------------------------- ## st_within(point_sf, polygon_sfc) ## st_touches(point_sf, polygon_sfc) ## ----04-spatial-operations-11----------------------------------------------------------------------- st_disjoint(point_sf, polygon_sfc, sparse = FALSE)[, 1] ## ----04-spatial-operations-14----------------------------------------------------------------------- st_is_within_distance(point_sf, polygon_sfc, dist = 0.2, sparse = FALSE)[, 1] ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## # verify distances to the polygon with reference to paragraph above: ## st_distance(point_sf, polygon_sfc) ## # [,1] ## # [1,] 0.0000000 ## # [2,] 0.1341641 ## # [3,] 0.0000000 ## Functions for calculating topological relations use spatial indices to largely speed up spatial query performance. ## They achieve that using the Sort-Tile-Recursive (STR) algorithm. ## The `st_join` function, mentioned in the next section, also uses the spatial indexing. ## You can learn more at https://www.r-spatial.org/r/2017/06/22/spatial-index.html. ## ----04-spatial-operations-16, eval=FALSE, echo=FALSE----------------------------------------------- ## # other tests ## st_overlaps(point_sf, polygon_sfc, sparse = FALSE) ## st_covers(point_sf, polygon_sfc, sparse = FALSE) ## st_covered_by(point_sf, polygon_sfc, sparse = FALSE) ## ----04-spatial-operations-17, eval=FALSE, echo=FALSE----------------------------------------------- ## st_contains(a, p[2, ], sparse = TRUE) ## ----04-spatial-operations-18, eval=FALSE, echo=FALSE----------------------------------------------- ## # starting simpler so commented ## a1 = st_polygon(list(rbind(c(-1, -1), c(1, -1), c(1, 1), c(-1, -1)))) ## a2 = st_polygon(list(rbind(c(2, 0), c(2, 2), c(3, 2), c(3, 0), c(2, 0)))) ## a = st_sfc(a1, a2) ## ## b1 = a1 * 0.5 ## b2 = a2 * 0.4 + c(1, 0.5) ## b = st_sfc(b1, b2) ## ## l1 = st_linestring(x = matrix(c(0, 3, -1, 1), , 2)) ## l2 = st_linestring(x = matrix(c(-1, -1, -0.5, 1), , 2)) ## l = st_sfc(l1, l2) ## ## p = st_multipoint(x = matrix(c(0.5, 1, -1, 0, 1, 0.5), , 2)) ## ## plot(a, border = "red", axes = TRUE) ## plot(b, border = "green", add = TRUE) ## plot(l, add = TRUE) ## plot(p, add = TRUE) ## ----de-9im, echo=FALSE, eval=FALSE----------------------------------------------------------------- ## # Todo one day: revive this ## b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points ## b = st_buffer(b, dist = 1) # convert points to circles ## bsf = sf::st_sf(data.frame(Object = c("a", "b")), geometry = b) ## b9 = replicate(bsf, n = 9, simplify = FALSE) ## b9sf = do.call(rbind, b9) ## domains = c("Interior", "Boundary", "Exterior") ## b9sf$domain_a = rep(rep(domains, 3), each = 2) ## b9sf$domain_b = rep(rep(domains, each = 3), each = 2) ## library(ggplot2) ## ggplot(b9sf) + ## geom_sf() + ## facet_grid(domain_a ~ domain_b) ## ## plot(b9sf) ## tmap_arrange( ## tm_shape(b) + tm_polygons(alpha = 0.5) + tm_layout(title = "Interior-Interior"), ## tm_shape(b) + tm_polygons(alpha = 0.5) + tm_layout(title = "Interior-Boundary"), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## tm_shape(b) + tm_polygons(alpha = 0.5), ## nrow = 3 ## ) ## ## plot(b) ## text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y")) # add text ## ----de9imgg, echo=FALSE, warning=FALSE, fig.cap="Illustration of how the Dimensionally Extended 9 Intersection Model (DE-9IM) works. Colors not in the legend represent the overlap between different components. The thick lines highlight two-dimensional intersections, e.g. between the boundary of object x and the interior of object y, shown in the middle top facet.", message=FALSE---- p1_2 = st_as_sf(c(p1, p3)) ii = st_as_sf(st_intersection(p1, p3)) ii$Object = "Intersection" ii$domain_a = "Interior" ii$domain_b = "Interior" bi = st_sf(x = st_intersection( st_cast(p1, "LINESTRING"), st_difference(p3, st_buffer(st_cast(p3, "LINESTRING"), dist = 0.01)) )) bi = st_buffer(bi, dist = 0.01) bi$Object = "Intersection" bi$domain_a = "Boundary" bi$domain_b = "Interior" ei = st_sf(x = st_difference(p3, p1)) ei$Object = "Intersection" ei$domain_a = "Exterior" ei$domain_b = "Interior" ib = st_sf(x = st_intersection( st_cast(p3, "LINESTRING"), st_difference(p1, st_buffer(st_cast(p1, "LINESTRING"), dist = 0.005)) )) ib = st_buffer(ib, dist = 0.01) ib$Object = "Intersection" ib$domain_a = "Interior" ib$domain_b = "Boundary" bb = st_cast(ii, "POINT") bb_line = st_sf(x = st_sfc(st_linestring(matrix(c(1, 0.5, 1, 0.7), nrow = 2, byrow = TRUE)))) bb_line_buffer = st_buffer(bb_line, dist = 0.01) bb_buffer = st_buffer(bb, dist = 0.01) bb = st_union(bb_buffer, bb_line_buffer) bb$Object = "Intersection" bb$domain_a = "Boundary" bb$domain_b = "Boundary" eb = st_sf(x = st_difference( st_cast(p3, "LINESTRING"), p1 )) eb = st_buffer(eb, dist = 0.01) eb$Object = "Intersection" eb$domain_a = "Exterior" eb$domain_b = "Boundary" ie = st_sf(x = st_difference(p1, p3)) ie$Object = "Intersection" ie$domain_a = "Interior" ie$domain_b = "Exterior" be = st_sf(x = st_difference( st_cast(p1, "LINESTRING"), p3 )) be = st_buffer(be, dist = 0.01) be$Object = "Intersection" be$domain_a = "Boundary" be$domain_b = "Exterior" ee = st_sf(x = st_difference( st_buffer(st_union(p1, p3), 0.02), st_union(p1, p3) )) ee$Object = "Intersection" ee$domain_a = "Exterior" ee$domain_b = "Exterior" b9 = replicate(p1_2, n = 9, simplify = FALSE) b9sf = do.call(rbind, b9) b9sf$Object = rep(c("x", "y"), 9) domains = c("Interior", "Boundary", "Exterior") b9sf$domain_a = rep(rep(domains, 3), each = 2) b9sf$domain_b = rep(rep(domains, each = 3), each = 2) b9sf = rbind(b9sf, ii, bi, ei, ib, bb, eb, ie, be, ee) b9sf$domain_a = ordered(b9sf$domain_a, levels = c("Interior", "Boundary", "Exterior")) b9sf$domain_b = ordered(b9sf$domain_b, levels = c("Interior", "Boundary", "Exterior")) b9sf = b9sf |> mutate(alpha = case_when( Object == "x" ~ 0.1, Object == "y" ~ 0.1, TRUE ~ 0.2 )) library(ggplot2) ggplot(b9sf) + geom_sf(aes(fill = Object, alpha = alpha)) + facet_grid(domain_b ~ domain_a) + scale_fill_manual(values = c("red", "lightblue", "yellow"), position = "top", name = "") + scale_alpha_continuous(range = c(0.3, 0.9)) + guides(alpha = "none") + theme_void() + theme(legend.position = "top") ## ----de9emtable, echo=FALSE------------------------------------------------------------------------- # See https://github.com/Robinlovelace/geocompr/issues/699 pattern = st_relate(p1, p3) matrix_de_9im = function(pattern) { string = unlist(strsplit(pattern , "")) matrix_de_9im = matrix(string, nrow = 3, byrow = TRUE) colnames(matrix_de_9im) = c("I", "B", "E") row.names(matrix_de_9im) = c("I", "B", "E") return(matrix_de_9im) } m = matrix_de_9im(pattern) colnames(m) = c("Interior (x)", "Boundary (x)", "Exterior (x)") rownames(m) = c("Interior (y)", "Boundary (y)", "Exterior (y)") knitr::kable(m, caption = "Relations between interiors, boundaries and exteriors of geometries x and y.") ## --------------------------------------------------------------------------------------------------- xy2sfc = function(x, y) st_sfc(st_polygon(list(cbind(x, y)))) x = xy2sfc(x = c(0, 0, 1, 1, 0), y = c(0, 1, 1, 0.5, 0)) y = xy2sfc(x = c(0.7, 0.7, 0.9, 0.7), y = c(0.8, 0.5, 0.5, 0.8)) st_relate(x, y) ## --------------------------------------------------------------------------------------------------- st_queen = function(x, y) st_relate(x, y, pattern = "F***T****") st_rook = function(x, y) st_relate(x, y, pattern = "F***1****") ## ----queenscode, fig.show='hide'-------------------------------------------------------------------- grid = st_make_grid(x, n = 3) grid_sf = st_sf(grid) grid_sf$queens = lengths(st_queen(grid, grid[5])) > 0 plot(grid, col = grid_sf$queens) grid_sf$rooks = lengths(st_rook(grid, grid[5])) > 0 plot(grid, col = grid_sf$rooks) ## ----queens, fig.cap="Demonstration of custom binary spatial predicates for finding 'queen' (left) and 'rook' (right) relations to the central square in a grid with 9 geometries.", echo=FALSE, warning=FALSE---- tm_shape(grid_sf) + tm_fill(col = c("queens", "rooks"), palette = c("white", "black")) + tm_shape(grid_sf) + tm_borders(col = "gray", lwd = 2) + tm_layout(frame = FALSE, legend.show = FALSE, panel.labels = c("queen", "rook")) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## st_lineoverlap = function(x, y) st_relate(x, y, pattern = "T*1******") ## line1 = st_sfc(st_linestring(cbind( ## x = c(0, 0.8), ## y = c(0, 0) ## ))) ## line2 = st_sfc(st_linestring(cbind( ## x = c(0.1, 0.5), ## y = c(0, 0) ## ))) ## line3 = st_sfc(st_linestring(cbind( ## x = c(0, 0.5), ## y = c(0, 0.2) ## ))) ## st_queen(line1, line2) ## st_relate(line1, line2) ## st_relate(line1, line3) ## st_lineoverlap(line1, line2) ## st_lineoverlap(line1, line3) ## de_9im(line1, line2) ## # test the function ## rnet = pct::get_pct_rnet(region = "isle-of-wight") ## osm_net = osmextract::oe_get_network(place = "isle-of-wight", mode = "driving") ## sel = st_relate(rnet, osm_net, pattern = "T*1******") ## summary(lengths(sel) > 0) ## rnet_joined1 = st_join(rnet, osm_net, join = st_lineoverlap) ## rnet_joined2 = st_join(rnet, osm_net, join = st_relate, pattern = "T*1******") ## rnet_joined3 = st_join(rnet, osm_net) ## summary(is.na(rnet_joined1$osm_id)) ## summary(is.na(rnet_joined2$osm_id)) ## summary(is.na(rnet_joined3$osm_id)) ## sel_relates = st_relate(rnet[1, ], osm_net) ## dim(sel_relates) ## sel_table = table(sel_relates) ## sel_table ## dim(sel_table) ## sel_restrictive = sel_relates[1, ] == "0F1FF0102" ## summary(sel_restrictive) ## nrow(osm_net) ## mapview::mapview(rnet[1, ]) + mapview::mapview(osm_net[sel_restrictive, ]) ## ## rnet_approx = rnet ## st_precision(rnet_approx) = 100 ## head(st_coordinates(rnet_approx)) ## ## sel_relates = st_relate(rnet_approx[1, ], osm_net) ## dim(sel_relates) ## sel_table = table(sel_relates) ## sel_table ## ## ----04-spatial-operations-19----------------------------------------------------------------------- set.seed(2018) # set seed for reproducibility (bb = st_bbox(world)) # the world's bounds random_df = data.frame( x = runif(n = 10, min = bb[1], max = bb[3]), y = runif(n = 10, min = bb[2], max = bb[4]) ) random_points = random_df |> st_as_sf(coords = c("x", "y")) |> # set coordinates st_set_crs("EPSG:4326") # set geographic CRS ## ----04-spatial-operations-20, message=FALSE-------------------------------------------------------- world_random = world[random_points, ] nrow(world_random) random_joined = st_join(random_points, world["name_long"]) ## ----spatial-join, echo=FALSE, fig.cap="Illustration of a spatial join. A new attribute variable is added to random points (top left) from source world object (top right) resulting in the data represented in the final panel.", fig.asp=0.5, warning=FALSE, message=FALSE, out.width="100%", fig.scap="Illustration of a spatial join."---- # source("https://github.com/Robinlovelace/geocompr/raw/main/code/04-spatial-join.R") source("code/04-spatial-join.R") tmap_arrange(jm1, jm2, jm3, jm4, nrow = 2, ncol = 2) ## ----04-spatial-operations-21, eval=FALSE----------------------------------------------------------- ## plot(st_geometry(cycle_hire), col = "blue") ## plot(st_geometry(cycle_hire_osm), add = TRUE, pch = 3, col = "red") ## ----04-spatial-operations-22, message=FALSE-------------------------------------------------------- any(st_touches(cycle_hire, cycle_hire_osm, sparse = FALSE)) ## ----04-spatial-operations-23, echo=FALSE, eval=FALSE----------------------------------------------- ## # included to show alternative ways of showing there's no overlap ## sum(st_geometry(cycle_hire) %in% st_geometry(cycle_hire_osm)) ## sum(st_coordinates(cycle_hire)[, 1] %in% st_coordinates(cycle_hire_osm)[, 1]) ## ----cycle-hire, fig.cap="The spatial distribution of cycle hire points in London based on official data (blue) and OpenStreetMap data (red).", echo=FALSE, warning=FALSE, fig.scap="The spatial distribution of cycle hire points in London."---- if (knitr::is_latex_output()){ knitr::include_graphics("images/cycle-hire-1.png") } else if (knitr::is_html_output()){ # library(tmap) # osm_tiles = tmaptools::read_osm(tmaptools::bb(cycle_hire, ext = 1.3), type = "https://korona.geog.uni-heidelberg.de/tiles/roadsg/x={x}&y={y}&z={z}") # qtm(osm_tiles) + # tm_shape(cycle_hire) + # tm_bubbles(col = "blue", alpha = 0.5, size = 0.2) + # tm_shape(cycle_hire_osm) + # tm_bubbles(col = "red", alpha = 0.5, size = 0.2) + # tm_scale_bar() library(leaflet) leaflet() |> # addProviderTiles(providers$OpenStreetMap.BlackAndWhite) |> addCircles(data = cycle_hire) |> addCircles(data = cycle_hire_osm, col = "red") } ## ----04-spatial-operations-24----------------------------------------------------------------------- sel = st_is_within_distance(cycle_hire, cycle_hire_osm, dist = 20) summary(lengths(sel) > 0) ## ----04-spatial-operations-24-without-s2-test, eval=FALSE, echo=FALSE------------------------------- ## sf::sf_use_s2(FALSE) ## sel = st_is_within_distance(cycle_hire, cycle_hire_osm, dist = 20) ## summary(lengths(sel) > 0) ## # still works: must be lwgeom or some other magic! ## ----04-spatial-operations-24-projected, eval=FALSE, echo=FALSE------------------------------------- ## # This chunk contains the non-overlapping join on projected data, a step that is no longer needed: ## # Note that, before performing the relation, both objects are transformed into a projected CRS. ## # These projected objects are created below (note the affix `_P`, short for projected): ## cycle_hire_P = st_transform(cycle_hire, 27700) ## cycle_hire_osm_P = st_transform(cycle_hire_osm, 27700) ## sel = st_is_within_distance(cycle_hire_P, cycle_hire_osm_P, dist = 20) ## summary(lengths(sel) > 0) ## ----04-spatial-operations-25----------------------------------------------------------------------- z = st_join(cycle_hire, cycle_hire_osm, st_is_within_distance, dist = 20) nrow(cycle_hire) nrow(z) ## ----04-spatial-operations-26----------------------------------------------------------------------- z = z |> group_by(id) |> summarize(capacity = mean(capacity)) nrow(z) == nrow(cycle_hire) ## ----04-spatial-operations-27, eval=FALSE----------------------------------------------------------- ## plot(cycle_hire_osm["capacity"]) ## plot(z["capacity"]) ## ----04-spatial-operations-28----------------------------------------------------------------------- nz_agg = aggregate(x = nz_height, by = nz, FUN = mean) ## ----spatial-aggregation, echo=FALSE, fig.cap="Average height of the top 101 high points across the regions of New Zealand.", fig.asp=1, message=FALSE, out.width="50%"---- library(tmap) tm_shape(nz_agg) + tm_fill("elevation", breaks = seq(27, 30, by = 0.5) * 1e2) + tm_borders() ## ----04-spatial-operations-29----------------------------------------------------------------------- nz_agg2 = st_join(x = nz, y = nz_height) |> group_by(Name) |> summarize(elevation = mean(elevation, na.rm = TRUE)) ## ----test-tidy-spatial-join, eval=FALSE, echo=FALSE------------------------------------------------- ## plot(nz_agg) ## plot(nz_agg2) ## # aggregate looses the name of aggregating objects ## ----areal-example, echo=FALSE, fig.cap="Congruent (left) and incongruent (right) areal units with respect to larger aggregating zones (translucent blue borders).", fig.asp=0.2, fig.scap="Congruent and incongruent areal units."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/04-areal-example.R", print.eval = TRUE) ## ----04-spatial-operations-30----------------------------------------------------------------------- iv = incongruent["value"] # keep only the values to be transferred agg_aw = st_interpolate_aw(iv, aggregating_zones, extensive = TRUE) agg_aw$value ## ----04-spatial-operations-31, warning=FALSE-------------------------------------------------------- nz_highest = nz_height |> slice_max(n = 1, order_by = elevation) canterbury_centroid = st_centroid(canterbury) st_distance(nz_highest, canterbury_centroid) ## ----04-spatial-operations-32----------------------------------------------------------------------- co = filter(nz, grepl("Canter|Otag", Name)) st_distance(nz_height[1:3, ], co) ## ----04-spatial-operations-33, eval=FALSE----------------------------------------------------------- ## plot(st_geometry(co)[2]) ## plot(st_geometry(nz_height)[2:3], add = TRUE) ## ----04-spatial-operations-34, eval = FALSE--------------------------------------------------------- ## id = cellFromXY(elev, xy = matrix(c(0.1, 0.1), ncol = 2)) ## elev[id] ## # the same as ## terra::extract(elev, matrix(c(0.1, 0.1), ncol = 2)) ## ----04-spatial-operations-35, eval=FALSE----------------------------------------------------------- ## clip = rast(xmin = 0.9, xmax = 1.8, ymin = -0.45, ymax = 0.45, ## resolution = 0.3, vals = rep(1, 9)) ## elev[clip] ## # we can also use extract ## # terra::extract(elev, ext(clip)) ## ----raster-subset, echo = FALSE, fig.cap = "Original raster (left). Raster mask (middle). Output of masking a raster (right).", fig.scap="Subsetting raster values."---- knitr::include_graphics("images/04_raster_subset.png") ## ----04-spatial-operations-36, eval=FALSE----------------------------------------------------------- ## elev[1:2, drop = FALSE] # spatial subsetting with cell IDs ## #> class : SpatRaster ## #> dimensions : 1, 2, 1 (nrow, ncol, nlyr) ## #> ... ## ----04-spatial-operations-37, echo=FALSE, eval=FALSE----------------------------------------------- ## # aim: illustrate the result of previous spatial subsetting example ## x = elev[1, 1:2, drop = FALSE] ## plot(x) ## ----04-spatial-operations-38, eval=FALSE----------------------------------------------------------- ## # create raster mask ## rmask = elev ## values(rmask) = sample(c(NA, TRUE), 36, replace = TRUE) ## ----04-spatial-operations-38b, eval=FALSE---------------------------------------------------------- ## # spatial subsetting ## elev[rmask, drop = FALSE] # with [ operator ## mask(elev, rmask) # with mask() ## ----04-spatial-operations-38c, eval=FALSE---------------------------------------------------------- ## elev[elev < 20] = NA ## ----04-spatial-operations-41, eval = FALSE--------------------------------------------------------- ## elev + elev ## elev^2 ## log(elev) ## elev > 5 ## ----04-local-operations, echo=FALSE, fig.cap="Examples of different local operations of the elev raster object: adding two rasters, squaring, applying logarithmic transformation, and performing a logical operation."---- knitr::include_graphics("images/04-local-operations.png") ## ----04-spatial-operations-40----------------------------------------------------------------------- rcl = matrix(c(0, 12, 1, 12, 24, 2, 24, 36, 3), ncol = 3, byrow = TRUE) rcl ## ----04-spatial-operations-40b, eval = FALSE-------------------------------------------------------- ## recl = classify(elev, rcl = rcl) ## --------------------------------------------------------------------------------------------------- multi_raster_file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(multi_raster_file) ## --------------------------------------------------------------------------------------------------- ndvi_fun = function(nir, red){ (nir - red) / (nir + red) } ## --------------------------------------------------------------------------------------------------- ndvi_rast = lapp(multi_rast[[c(4, 3)]], fun = ndvi_fun) ## ----04-ndvi, echo=FALSE, fig.cap="RGB image (left) and NDVI values (right) calculated for the example satellite file of Zion National Park"---- knitr::include_graphics("images/04-ndvi.png") ## ----04-spatial-operations-42, eval = FALSE--------------------------------------------------------- ## r_focal = focal(elev, w = matrix(1, nrow = 3, ncol = 3), fun = min) ## ----focal-example, echo = FALSE, fig.cap = "Input raster (left) and resulting output raster (right) due to a focal operation - finding the minimum value in 3-by-3 moving windows.", fig.scap="Illustration of a focal operation."---- knitr::include_graphics("images/04_focal_example.png") ## ----04-spatial-operations-43----------------------------------------------------------------------- z = zonal(elev, grain, fun = "mean") z ## ----04-spatial-operations-44, eval = FALSE--------------------------------------------------------- ## aut = geodata::elevation_30s(country = "AUT", path = tempdir()) ## ch = geodata::elevation_30s(country = "CHE", path = tempdir()) ## aut_ch = merge(aut, ch) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_04-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/05-geometry-operations.R ================================================ ## ----05-geometry-operations-1, message=FALSE-------------------------------------------------------- library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ## ----05-geometry-operations-2----------------------------------------------------------------------- seine_simp = st_simplify(seine, dTolerance = 2000) # 2000 m ## ----seine-simp, echo=FALSE, fig.cap="Comparison of the original and simplified geometry of the seine object.", warning=FALSE, fig.scap="Simplification in action.", message=FALSE---- library(tmap) p_simp1 = tm_shape(seine) + tm_lines() + tm_layout(main.title = "Original data") p_simp2 = tm_shape(seine_simp) + tm_lines() + tm_layout(main.title = "st_simplify") tmap_arrange(p_simp1, p_simp2, ncol = 2) ## ----05-geometry-operations-3----------------------------------------------------------------------- object.size(seine) object.size(seine_simp) ## ----05-geometry-operations-4----------------------------------------------------------------------- us_states2163 = st_transform(us_states, "EPSG:2163") us_states2163 = us_states2163 ## ----05-geometry-operations-5----------------------------------------------------------------------- us_states_simp1 = st_simplify(us_states2163, dTolerance = 100000) # 100 km ## ----05-geometry-operations-6, warning=FALSE, message=FALSE----------------------------------------- # proportion of points to retain (0-1; default 0.05) us_states_simp2 = rmapshaper::ms_simplify(us_states2163, keep = 0.01, keep_shapes = TRUE) ## ----05-geometry-operations-6b, warning=FALSE------------------------------------------------------- us_states_simp3 = smoothr::smooth(us_states2163, method = 'ksmooth', smoothness = 6) ## ----us-simp, echo=FALSE, fig.cap="Polygon simplification in action, comparing the original geometry of the contiguous United States with simplified versions, generated with functions from sf (top-right), rmapshaper (bottom-left), and smoothr (bottom-right) packages.", warning=FALSE, fig.asp=0.3, fig.scap="Polygon simplification in action."---- library(tmap) p_ussimp1 = tm_shape(us_states2163) + tm_polygons() + tm_layout(main.title = "Original data") p_ussimp2 = tm_shape(us_states_simp1) + tm_polygons() + tm_layout(main.title = "st_simplify") p_ussimp3 = tm_shape(us_states_simp2) + tm_polygons() + tm_layout(main.title = "ms_simplify") p_ussimp4 = tm_shape(us_states_simp3) + tm_polygons() + tm_layout(main.title = "smooth(method=ksmooth)") tmap_arrange(p_ussimp1, p_ussimp2, p_ussimp3, p_ussimp4, ncol = 2, nrow = 2) ## ----05-geometry-operations-7, warning=FALSE-------------------------------------------------------- nz_centroid = st_centroid(nz) seine_centroid = st_centroid(seine) ## ----05-geometry-operations-8, warning=FALSE-------------------------------------------------------- nz_pos = st_point_on_surface(nz) seine_pos = st_point_on_surface(seine) ## ----centr, warning=FALSE, echo=FALSE, fig.cap="Centroids (black points) and 'points on surface' (red points) of New Zealand's regions (left) and the Seine (right) datasets.", fig.scap="Centroid vs. point on surface operations."---- p_centr1 = tm_shape(nz) + tm_borders() + tm_shape(nz_centroid) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_shape(nz_pos) + tm_symbols(shape = 1, col = "red", size = 0.5) p_centr2 = tm_shape(seine) + tm_lines() + tm_shape(seine_centroid) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_shape(seine_pos) + tm_symbols(shape = 1, col = "red", size = 0.5) tmap_arrange(p_centr1, p_centr2, ncol = 2) ## ----05-geometry-operations-9----------------------------------------------------------------------- seine_buff_5km = st_buffer(seine, dist = 5000) seine_buff_50km = st_buffer(seine, dist = 50000) ## ----buffs, echo=FALSE, fig.cap="Buffers around the Seine dataset of 5 km (left) and 50 km (right). Note the colors, which reflect the fact that one buffer is created per geometry feature.", fig.show='hold', out.width="75%", fig.scap="Buffers around the seine dataset."---- p_buffs1 = tm_shape(seine_buff_5km) + tm_polygons(col = "name") + tm_shape(seine) + tm_lines() + tm_layout(main.title = "5 km buffer", legend.show = FALSE) p_buffs2 = tm_shape(seine_buff_50km) + tm_polygons(col = "name") + tm_shape(seine) + tm_lines() + tm_layout(main.title = "50 km buffer", legend.show = FALSE) tmap_arrange(p_buffs1, p_buffs2, ncol = 2) ## The third and final argument of `st_buffer()` is `nQuadSegs`, which means 'number of segments per quadrant' and is set by default to 30 (meaning circles created by buffers are composed of $4 \times 30 = 120$ lines). ## This argument rarely needs to be set. ## Unusual cases where it may be useful include when the memory consumed by the output of a buffer operation is a major concern (in which case it should be reduced) or when very high precision is needed (in which case it should be increased). ## ----nQuadSegs, eval=FALSE, echo=FALSE-------------------------------------------------------------- ## # Demonstrate nQuadSegs ## seine_buff_simple = st_buffer(seine, dist = 50000, nQuadSegs = 3) ## plot(seine_buff_simple, key.pos = NULL, main = "50 km buffer") ## plot(seine, key.pos = NULL, lwd = 3, pal = rainbow, add = TRUE) ## seine_points = st_cast(seine[1, ], "POINT") ## buff_single = st_buffer(seine_points[1, ], 50000, 2) ## buff_points = st_cast(buff_single, "POINT") ## plot(st_geometry(buff_single), add = TRUE) ## ----05-geometry-operations-11---------------------------------------------------------------------- nz_sfc = st_geometry(nz) ## ----05-geometry-operations-12---------------------------------------------------------------------- nz_shift = nz_sfc + c(0, 100000) ## ----05-geometry-operations-13, echo=FALSE,eval=FALSE----------------------------------------------- ## nz_scale0 = nz_sfc * 0.5 ## ----05-geometry-operations-14---------------------------------------------------------------------- nz_centroid_sfc = st_centroid(nz_sfc) nz_scale = (nz_sfc - nz_centroid_sfc) * 0.5 + nz_centroid_sfc ## ----05-geometry-operations-15---------------------------------------------------------------------- rotation = function(a){ r = a * pi / 180 #degrees to radians matrix(c(cos(r), sin(r), -sin(r), cos(r)), nrow = 2, ncol = 2) } ## ----05-geometry-operations-16---------------------------------------------------------------------- nz_rotate = (nz_sfc - nz_centroid_sfc) * rotation(30) + nz_centroid_sfc ## ----affine-trans, echo=FALSE, fig.cap="Affine transformations: shift, scale and rotate.", warning=FALSE, eval=TRUE, fig.scap="Affine transformations."---- st_crs(nz_shift) = st_crs(nz_sfc) st_crs(nz_scale) = st_crs(nz_sfc) st_crs(nz_rotate) = st_crs(nz_sfc) p_at1 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_shift) + tm_polygons(col = "red") + tm_layout(main.title = "Shift") p_at2 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_scale) + tm_polygons(col = "red") + tm_layout(main.title = "Scale") p_at3 = tm_shape(nz_sfc) + tm_polygons() + tm_shape(nz_rotate) + tm_polygons(col = "red") + tm_layout(main.title = "Rotate") tmap_arrange(p_at1, p_at2, p_at3, ncol = 3) ## ----05-geometry-operations-17, echo=FALSE,eval=FALSE----------------------------------------------- ## nz_scale_rotate = (nz_sfc - nz_centroid_sfc) * 0.25 * rotation(90) + nz_centroid_sfc ## ----05-geometry-operations-18, echo=FALSE,eval=FALSE----------------------------------------------- ## shearing = function(hx, hy){ ## matrix(c(1, hy, hx, 1), nrow = 2, ncol = 2) ## } ## nz_shear = (nz_sfc - nz_centroid_sfc) * shearing(1.1, 0) + nz_centroid_sfc ## ----05-geometry-operations-19, echo=FALSE,eval=FALSE----------------------------------------------- ## plot(nz_sfc) ## plot(nz_shear, add = TRUE, col = "red") ## ----05-geometry-operations-20---------------------------------------------------------------------- nz_scale_sf = st_set_geometry(nz, nz_scale) ## ----points, fig.cap="Overlapping circles.", fig.asp=0.4, crop = TRUE------------------------------- b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles plot(b, border = "gray") text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y"), cex = 3) # add text ## ----circle-intersection, fig.cap="Overlapping circles with a gray color indicating intersection between them.", fig.asp=0.4, fig.scap="Overlapping circles showing intersection types.", crop = TRUE---- x = b[1] y = b[2] x_and_y = st_intersection(x, y) plot(b, border = "gray") plot(x_and_y, col = "lightgray", border = "gray", add = TRUE) # intersecting area ## ----venn-clip, echo=FALSE, fig.cap="Spatial equivalents of logical operators.", warning=FALSE------ source("https://github.com/Robinlovelace/geocompr/raw/main/code/05-venn-clip.R") # source("code/05-venn-clip.R") # for testing local version, todo: remove or change ## ----venn-subset, fig.cap="Randomly distributed points within the bounding box enclosing circles x and y. The point that intersects with both objects x and y is highlighted.", fig.height=6, fig.width=9, fig.asp=0.4, fig.scap="Randomly distributed points within the bounding box. Note that only one point intersects with both x and y, highlighted with a red circle.", echo=FALSE---- bb = st_bbox(st_union(x, y)) box = st_as_sfc(bb) set.seed(2017) p = st_sample(x = box, size = 10) p_xy1 = p[x_and_y] plot(box, border = "gray", lty = 2) plot(x, add = TRUE, border = "gray") plot(y, add = TRUE, border = "gray") plot(p, add = TRUE) plot(p_xy1, cex = 3, col = "red", add = TRUE) text(x = c(-0.5, 1.5), y = 1, labels = c("x", "y"), cex = 2) ## ----venn-subset-to-show, eval=FALSE---------------------------------------------------------------- ## bb = st_bbox(st_union(x, y)) ## box = st_as_sfc(bb) ## set.seed(2017) ## p = st_sample(x = box, size = 10) ## x_and_y = st_intersection(x, y) ## ----05-geometry-operations-21---------------------------------------------------------------------- p_xy1 = p[x_and_y] p_xy2 = st_intersection(p, x_and_y) sel_p_xy = st_intersects(p, x, sparse = FALSE)[, 1] & st_intersects(p, y, sparse = FALSE)[, 1] p_xy3 = p[sel_p_xy] ## ----05-geometry-operations-22, echo=FALSE, eval=FALSE---------------------------------------------- ## # test if objects are identical: ## identical(p_xy1, p_xy2) ## identical(p_xy2, p_xy3) ## identical(p_xy1, p_xy3) ## waldo::compare(p_xy1, p_xy2) # the same except attribute names ## waldo::compare(p_xy2, p_xy3) # the same except attribute names ## ## ## # An alternative way to sample from the bb ## bb = st_bbox(st_union(x, y)) ## pmulti = st_multipoint(pmat) ## box = st_convex_hull(pmulti) ## ----05-geometry-operations-23---------------------------------------------------------------------- regions = aggregate(x = us_states[, "total_pop_15"], by = list(us_states$REGION), FUN = sum, na.rm = TRUE) regions2 = us_states |> group_by(REGION) |> summarize(pop = sum(total_pop_15, na.rm = TRUE)) ## ----05-geometry-operations-24, echo=FALSE---------------------------------------------------------- # st_join(buff, africa[, "pop"]) |> # summarize(pop = sum(pop, na.rm = TRUE)) # summarize(africa[buff, "pop"], pop = sum(pop, na.rm = TRUE)) ## ----us-regions, fig.cap="Spatial aggregation on contiguous polygons, illustrated by aggregating the population of US states into regions, with population represented by color. Note the operation automatically dissolves boundaries between states.", echo=FALSE, warning=FALSE, fig.asp=0.2, out.width="100%", fig.scap="Spatial aggregation on contiguous polygons."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/05-us-regions.R", print.eval = TRUE) ## ----05-geometry-operations-25---------------------------------------------------------------------- us_west = us_states[us_states$REGION == "West", ] us_west_union = st_union(us_west) ## ----05-geometry-operations-26, message=FALSE------------------------------------------------------- texas = us_states[us_states$NAME == "Texas", ] texas_union = st_union(us_west_union, texas) ## ----05-geometry-operations-27, echo=FALSE, eval=FALSE---------------------------------------------- ## plot(texas_union) ## # aim: experiment with st_union ## us_south2 = st_union(us_west[1, ], us_west[6, ]) ## plot(us_southhwest) ## ----05-geometry-operations-28---------------------------------------------------------------------- multipoint = st_multipoint(matrix(c(1, 3, 5, 1, 3, 1), ncol = 2)) ## ----05-geometry-operations-29---------------------------------------------------------------------- linestring = st_cast(multipoint, "LINESTRING") polyg = st_cast(multipoint, "POLYGON") ## ----single-cast, echo = FALSE, fig.cap="Examples of a linestring and a polygon casted from a multipoint geometry.", warning=FALSE, fig.asp=0.3, fig.scap="Examples of casting operations."---- p_sc1 = tm_shape(st_sfc(multipoint)) + tm_symbols(shape = 1, col = "black", size = 0.5) + tm_layout(main.title = "MULTIPOINT", inner.margins = c(0.05, 0.05, 0.05, 0.05)) p_sc2 = tm_shape(st_sfc(linestring)) + tm_lines() + tm_layout(main.title = "LINESTRING") p_sc3 = tm_shape(st_sfc(polyg)) + tm_polygons(border.col = "black") + tm_layout(main.title = "POLYGON") tmap_arrange(p_sc1, p_sc2, p_sc3, ncol = 3) ## ----05-geometry-operations-30---------------------------------------------------------------------- multipoint_2 = st_cast(linestring, "MULTIPOINT") multipoint_3 = st_cast(polyg, "MULTIPOINT") all.equal(multipoint, multipoint_2) all.equal(multipoint, multipoint_3) ## For single simple feature geometries (`sfg`), `st_cast()` also provides geometry casting from non-multi-types to multi-types (e.g., `POINT` to `MULTIPOINT`) and from multi-types to non-multi-types. ## However, when casting from multi-types to non-multi-types only the first element of the old object would remain in the output object. ## ----05-geometry-operations-32, include=FALSE------------------------------------------------------- cast_all = function(xg) { lapply(c("MULTIPOLYGON", "MULTILINESTRING", "MULTIPOINT", "POLYGON", "LINESTRING", "POINT"), function(x) st_cast(xg, x)) } t = cast_all(multipoint) t2 = cast_all(polyg) ## ----sfs-st-cast, echo=FALSE------------------------------------------------------------------------ sfs_st_cast = read.csv("extdata/sfs-st-cast.csv") abbreviate_geomtypes = function(geomtypes) { geomtypes_new = gsub(pattern = "POINT", replacement = "POI", x = geomtypes) geomtypes_new = gsub(pattern = "POLYGON", replacement = "POL", x = geomtypes_new) geomtypes_new = gsub(pattern = "LINESTRING", replacement = "LIN", x = geomtypes_new) geomtypes_new = gsub(pattern = "MULTI", replacement = "M", x = geomtypes_new) geomtypes_new = gsub(pattern = "GEOMETRYCOLLECTION", replacement = "GC", x = geomtypes_new) geomtypes_new } sfs_st_cast$input_geom = abbreviate_geomtypes(sfs_st_cast$input_geom) names(sfs_st_cast) = abbreviate_geomtypes(names(sfs_st_cast)) names(sfs_st_cast)[1] = "" knitr::kable(sfs_st_cast, caption = paste("Geometry casting on simple feature geometries", "(see Section 2.1) with input type by row and", "output type by column"), caption.short = "Geometry casting on simple feature geometries.", booktabs = TRUE) |> kableExtra::add_footnote("Note: Values like (1) represent the number of features; NA means the operation is not possible. Abbreviations: POI, LIN, POL and GC refer to POINT, LINESTRING, POLYGON and GEOMETRYCOLLECTION. The MULTI version of these geometry types is indicated by a preceding M, e.g., MPOI is the acronym for MULTIPOINT.", notation = "none") ## ----05-geometry-operations-33---------------------------------------------------------------------- multilinestring_list = list(matrix(c(1, 4, 5, 3), ncol = 2), matrix(c(4, 4, 4, 1), ncol = 2), matrix(c(2, 4, 2, 2), ncol = 2)) multilinestring = st_multilinestring(multilinestring_list) multilinestring_sf = st_sf(geom = st_sfc(multilinestring)) multilinestring_sf ## ----05-geometry-operations-34---------------------------------------------------------------------- linestring_sf2 = st_cast(multilinestring_sf, "LINESTRING") linestring_sf2 ## ----line-cast, echo=FALSE, fig.cap="Examples of type casting between MULTILINESTRING (left) and LINESTRING (right).", warning=FALSE, fig.scap="Examples of type casting."---- p_lc1 = tm_shape(multilinestring_sf) + tm_lines(lwd = 3) + tm_layout(main.title = "MULTILINESTRING") linestring_sf2$name = c("Riddle Rd", "Marshall Ave", "Foulke St") p_lc2 = tm_shape(linestring_sf2) + tm_lines(lwd = 3, col = "name", palette = "Set2") + tm_layout(main.title = "LINESTRING", legend.show = FALSE) tmap_arrange(p_lc1, p_lc2, ncol = 2) ## ----05-geometry-operations-35---------------------------------------------------------------------- linestring_sf2$name = c("Riddle Rd", "Marshall Ave", "Foulke St") linestring_sf2$length = st_length(linestring_sf2) linestring_sf2 ## ----05-geometry-operations-36---------------------------------------------------------------------- elev = rast(system.file("raster/elev.tif", package = "spData")) clip = rast(xmin = 0.9, xmax = 1.8, ymin = -0.45, ymax = 0.45, resolution = 0.3, vals = rep(1, 9)) elev[clip, drop = FALSE] ## ----extend-example0-------------------------------------------------------------------------------- elev = rast(system.file("raster/elev.tif", package = "spData")) elev_2 = extend(elev, c(1, 2)) ## ----extend-example, fig.cap = "Original raster (left) and the same raster (right) extended by one row on the top and bottom and two columns on the left and right.", fig.scap="Extending rasters.", echo=FALSE, fig.asp=0.5---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/05-extend-example.R", print.eval = TRUE) ## ----05-geometry-operations-37, error=TRUE---------------------------------------------------------- elev_3 = elev + elev_2 ## ----05-geometry-operations-38---------------------------------------------------------------------- elev_4 = extend(elev, elev_2) ## ----05-geometry-operations-39---------------------------------------------------------------------- origin(elev_4) ## --------------------------------------------------------------------------------------------------- # change the origin origin(elev_4) = c(0.25, 0.25) ## ----origin-example, fig.cap="Rasters with identical values but different origins.", echo=FALSE----- elev_poly = st_as_sf(as.polygons(elev, dissolve = FALSE)) elev4_poly = st_as_sf(as.polygons(elev_4, dissolve = FALSE)) tm_shape(elev4_poly) + tm_grid() + tm_polygons(col = "elev") + tm_shape(elev_poly) + tm_polygons(col = "elev") + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = c(0.1, 0.12, 0, 0)) # # See https://github.com/Robinlovelace/geocompr/issues/695 # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146618199-786fe3ad-9718-4dd0-a640-41180fc17e63.png") ## ----05-geometry-operations-40---------------------------------------------------------------------- dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) dem_agg = aggregate(dem, fact = 5, fun = mean) ## ----aggregate-example, fig.cap = "Original raster (left). Aggregated raster (right).", echo=FALSE---- p_ar1 = tm_shape(dem) + tm_raster(style = "cont", legend.show = FALSE) + tm_layout(main.title = "A. Original", frame = FALSE) p_ar2 = tm_shape(dem_agg) + tm_raster(style = "cont", legend.show = FALSE) + tm_layout(main.title = "B. Aggregated", frame = FALSE) tmap_arrange(p_ar1, p_ar2, ncol = 2) ## ----05-geometry-operations-41---------------------------------------------------------------------- dem_disagg = disagg(dem_agg, fact = 5, method = "bilinear") identical(dem, dem_disagg) ## ----bilinear, echo = FALSE, fig.width=8, fig.height=10, fig.cap="The distance-weighted average of the four closest input cells determine the output when using the bilinear method for disaggregation.", fig.scap="Bilinear disaggregation in action.", warning=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/05-bilinear.R", print.eval = TRUE) # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/146619205-3c0c2e3f-9e8b-4fda-b014-9c342a4befbb.png") ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## target_rast = rast(xmin = 794600, xmax = 798200, ## ymin = 8931800, ymax = 8935400, ## resolution = 150, crs = "EPSG:32717") ## ## target_rast_p = st_as_sf(as.polygons(target_rast)) ## dem_resampl1 = resample(dem, target_rast, method = "near") ## ## tm1 = tm_shape(dem) + ## tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + ## tm_layout(frame = FALSE) ## ## tm2 = tm_shape(dem) + ## tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + ## tm_layout(frame = FALSE) + ## tm_shape(target_rast_p) + ## tm_borders() ## ## tm3 = tm_shape(dem_resampl1) + ## tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + ## tm_layout(frame = FALSE) ## ## tmap_arrange(tm1, tm2, tm3, nrow = 1) ## ----05-geometry-operations-42---------------------------------------------------------------------- target_rast = rast(xmin = 794600, xmax = 798200, ymin = 8931800, ymax = 8935400, resolution = 150, crs = "EPSG:32717") ## ----05-geometry-operations-42b--------------------------------------------------------------------- dem_resampl = resample(dem, y = target_rast, method = "bilinear") ## ----resampl, echo=FALSE, fig.cap="Visual comparison of the original raster and five different resampling methods."---- dem_resampl1 = resample(dem, target_rast, method = "near") dem_resampl2 = resample(dem, target_rast, method = "bilinear") dem_resampl3 = resample(dem, target_rast, method = "cubic") dem_resampl4 = resample(dem, target_rast, method = "cubicspline") dem_resampl5 = resample(dem, target_rast, method = "lanczos") library(tmap) tm1 = tm_shape(dem) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "Original raster") tm2 = tm_shape(dem_resampl1) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "near") tm3 = tm_shape(dem_resampl2) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "bilinear") tm4 = tm_shape(dem_resampl3) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "cubic") tm5 = tm_shape(dem_resampl4) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "cubicspline") tm6 = tm_shape(dem_resampl5) + tm_raster(breaks = seq(200, 1100, by = 150), legend.show = FALSE) + tm_layout(frame = FALSE, main.title = "lanczos") tmap_arrange(tm1, tm2, tm3, tm4, tm5, tm6) ## Most geometry operations in **terra** are user-friendly, rather fast, and work on large raster objects. ## However, there could be some cases, when **terra** is not the most performant either for extensive rasters or many raster files, and some alternatives should be considered. ## ## The most established alternatives come with the GDAL library. ## It contains several utility functions, including: ## ## - `gdalinfo` - lists various information about a raster file, including its resolution, CRS, bounding box, and more ## - `gdal_translate` - converts raster data between different file formats ## - `gdal_rasterize` - converts vector data into raster files ## - `gdalwarp` - allows for raster mosaicing, resampling, cropping, and reprojecting ## ## All of the above functions are written in C++, but can be called in R using the **gdalUtilities** package. ## Importantly, all of these functions expect a raster file path as an input and often return their output as a raster file (for example, `gdalUtilities::gdal_translate("my_file.tif", "new_file.tif", t_srs = "EPSG:4326")`) ## This is very different from the usual **terra** approach, which expects `SpatRaster` objects as inputs. ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_05-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/06-raster-vector.R ================================================ ## ----06-raster-vector-1, message=FALSE-------------------------------------------------------------- library(dplyr) library(terra) library(sf) ## ----06-raster-vector-2, results='hide'------------------------------------------------------------- srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) zion = st_transform(zion, crs(srtm)) ## ----06-raster-vector-3----------------------------------------------------------------------------- srtm_cropped = crop(srtm, zion) ## ----06-raster-vector-4----------------------------------------------------------------------------- srtm_masked = mask(srtm, zion) ## ----06-raster-vector-5----------------------------------------------------------------------------- srtm_cropped = crop(srtm, zion) srtm_final = mask(srtm_cropped, zion) ## ----06-raster-vector-6----------------------------------------------------------------------------- srtm_inv_masked = mask(srtm, zion, inverse = TRUE) ## ----cropmask, echo = FALSE, fig.cap="Raster cropping and raster masking.", fig.asp=0.36, fig.width = 10, warning=FALSE---- library(tmap) library(rcartocolor) terrain_colors = carto_pal(7, "Geyser") pz1 = tm_shape(srtm) + tm_raster(palette = terrain_colors, legend.show = FALSE, style = "cont") + tm_shape(zion) + tm_borders(lwd = 2) + tm_layout(main.title = "A. Original", inner.margins = 0) pz2 = tm_shape(srtm_cropped) + tm_raster(palette = terrain_colors, legend.show = FALSE, style = "cont") + tm_shape(zion) + tm_borders(lwd = 2) + tm_layout(main.title = "B. Crop", inner.margins = 0) pz3 = tm_shape(srtm_masked) + tm_raster(palette = terrain_colors, legend.show = FALSE, style = "cont") + tm_shape(zion) + tm_borders(lwd = 2) + tm_layout(main.title = "C. Mask", inner.margins = 0) pz4 = tm_shape(srtm_inv_masked) + tm_raster(palette = terrain_colors, legend.show = FALSE, style = "cont") + tm_shape(zion) + tm_borders(lwd = 2) + tm_layout(main.title = "D. Inverse mask", inner.margins = 0) tmap_arrange(pz1, pz2, pz3, pz4, ncol = 4, asp = NA) ## ----06-raster-vector-8----------------------------------------------------------------------------- data("zion_points", package = "spDataLarge") elevation = terra::extract(srtm, zion_points) zion_points = cbind(zion_points, elevation) ## ----06-raster-vector-9, echo=FALSE, eval=FALSE----------------------------------------------------- ## library(dplyr) ## zion_points2 = zion_points ## zion_points2$a = 1 ## zion_points2 = zion_points2 |> group_by(a) |> summarise() ## elevation = terra::extract(srtm, zion_points2) ## zion_points = cbind(zion_points, elevation) ## ----pointextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Locations of points used for raster extraction.", fig.asp=0.57---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/06-pointextr.R", print.eval = TRUE) ## ----06-raster-vector-11---------------------------------------------------------------------------- zion_transect = cbind(c(-113.2, -112.9), c(37.45, 37.2)) |> st_linestring() |> st_sfc(crs = crs(srtm)) |> st_sf(geometry = _) ## ----06-raster-vector-12, eval=FALSE, echo=FALSE---------------------------------------------------- ## # Aim: show how extraction works with non-straight lines by ## # using this alternative line object: ## zion_transect2 = cbind(c(-113.2, -112.9, -113.2), c(36.45, 37.2, 37.5)) |> ## st_linestring() |> ## st_sfc(crs = crs(srtm)) |> ## st_sf() ## zion_transect = rbind(zion_transect, zion_transect2) ## ----06-raster-vector-13, warning=FALSE------------------------------------------------------------- zion_transect$id = 1:nrow(zion_transect) zion_transect = st_segmentize(zion_transect, dfMaxLength = 250) zion_transect = st_cast(zion_transect, "POINT") ## ----06-raster-vector-14---------------------------------------------------------------------------- zion_transect = zion_transect |> group_by(id) |> mutate(dist = st_distance(geometry)[, 1]) ## ----06-raster-vector-15---------------------------------------------------------------------------- zion_elev = terra::extract(srtm, zion_transect) zion_transect = cbind(zion_transect, zion_elev) ## ----lineextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Location of a line used for raster extraction (left) and the elevation along this line (right).", fig.scap="Line-based raster extraction."---- library(tmap) library(grid) library(ggplot2) zion_transect_line = cbind(c(-113.2, -112.9), c(37.45, 37.2)) |> st_linestring() |> st_sfc(crs = crs(srtm)) |> st_sf() zion_transect_points = st_cast(zion_transect, "POINT")[c(1, nrow(zion_transect)), ] zion_transect_points$name = c("start", "end") rast_poly_line = tm_shape(srtm) + tm_raster(palette = terrain_colors, title = "Elevation (m)", legend.show = TRUE, style = "cont") + tm_shape(zion) + tm_borders(lwd = 2) + tm_shape(zion_transect_line) + tm_lines(col = "black", lwd = 4) + tm_shape(zion_transect_points) + tm_text("name", bg.color = "white", bg.alpha = 0.75, auto.placement = TRUE) + tm_layout(legend.frame = TRUE, legend.position = c("right", "top")) plot_transect = ggplot(zion_transect, aes(as.numeric(dist), srtm)) + geom_line() + labs(x = "Distance (m)", y = "Elevation (m a.s.l.)") + theme_bw() + # facet_wrap(~id) + theme(plot.margin = unit(c(5.5, 15.5, 5.5, 5.5), "pt")) grid.newpage() pushViewport(viewport(layout = grid.layout(2, 2, heights = unit(c(0.25, 5), "null")))) grid.text("A. Line extraction", vp = viewport(layout.pos.row = 1, layout.pos.col = 1)) grid.text("B. Elevation along the line", vp = viewport(layout.pos.row = 1, layout.pos.col = 2)) print(rast_poly_line, vp = viewport(layout.pos.row = 2, layout.pos.col = 1)) print(plot_transect, vp = viewport(layout.pos.row = 2, layout.pos.col = 2)) ## ----06-raster-vector-17, eval=FALSE, echo=FALSE---------------------------------------------------- ## # aim: create zion_many to test multi-polygon results ## n = 3 ## zion_many = st_sample(x = zion, size = n) |> ## st_buffer(dist = 500) |> ## st_sf(data.frame(v = 1:n), geometry = _) ## plot(zion_many) ## ## # for continuous data: ## zion_srtm_values1 = terra::extract(x = srtm, y = zion_many, fun = min) ## zion_srtm_values2 = terra::extract(x = srtm, y = zion_many, fun = mean) ## zion_srtm_values3 = terra::extract(x = srtm, y = zion_many, fun = max) ## ## # for categories ## nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) ## zion_many2 = st_transform(zion_many, st_crs(nlcd)) ## zion_nlcd = terra::extract(nlcd, zion_many2) ## count(zion_nlcd, levels) ## ----06-raster-vector-18---------------------------------------------------------------------------- zion_srtm_values = terra::extract(x = srtm, y = zion) ## ----06-raster-vector-19---------------------------------------------------------------------------- group_by(zion_srtm_values, ID) |> summarize(across(srtm, list(min = min, mean = mean, max = max))) ## ----06-raster-vector-20, warning=FALSE, message=FALSE---------------------------------------------- nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) zion2 = st_transform(zion, st_crs(nlcd)) zion_nlcd = terra::extract(nlcd, zion2) zion_nlcd |> group_by(ID, levels) |> count() ## ----polyextr, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Area used for continuous (left) and categorical (right) raster extraction."---- rast_poly_srtm = tm_shape(srtm) + tm_raster(palette = terrain_colors, title = "Elevation (m)", legend.show = TRUE, style = "cont") + tm_shape(zion) + tm_polygons(lwd = 2, alpha = 0.3) + tm_layout(main.title = "A. Continuous data extraction", main.title.size = 1, legend.frame = TRUE, legend.position = c("left", "bottom")) rast_poly_nlcd = tm_shape(nlcd) + tm_raster(drop.levels = TRUE, title = "Land cover", legend.show = TRUE) + tm_shape(zion) + tm_polygons(lwd = 2, alpha = 0.3) + tm_layout(main.title = "B. Categorical data extraction", main.title.size = 1, legend.frame = TRUE, legend.position = c("left", "bottom")) tmap_arrange(rast_poly_srtm, rast_poly_nlcd, ncol = 2) ## Polygons usually have irregular shapes, and therefore, a polygon can overlap only some parts of a raster's cells. ## To get more detailed results, the `extract()` function has an argument called `exact`. ## With `exact = TRUE`, we get one more column `fraction` in the output data frame, which contains a fraction of each cell that is covered by the polygon. ## This could be useful to calculate a weighted mean for continuous rasters or more precise coverage for categorical rasters. ## By default, it is `FALSE` as this operation requires more computations. ## The `exactextractr::exact_extract()` function always computes the coverage fraction of the polygon in each cell. ## ----06-raster-vector-23, include=FALSE------------------------------------------------------------- zion_srtm_values = terra::extract(x = srtm, y = zion, exact = FALSE) ## ----06-raster-vector-24---------------------------------------------------------------------------- cycle_hire_osm = spData::cycle_hire_osm cycle_hire_osm_projected = st_transform(cycle_hire_osm, "EPSG:27700") raster_template = rast(ext(cycle_hire_osm_projected), resolution = 1000, crs = st_crs(cycle_hire_osm_projected)$wkt) ## ----06-raster-vector-25---------------------------------------------------------------------------- ch_raster1 = rasterize(cycle_hire_osm_projected, raster_template, field = 1) ## ----06-raster-vector-26---------------------------------------------------------------------------- ch_raster2 = rasterize(cycle_hire_osm_projected, raster_template, fun = "length") ## ----06-raster-vector-27---------------------------------------------------------------------------- ch_raster3 = rasterize(cycle_hire_osm_projected, raster_template, field = "capacity", fun = sum) ## ----vector-rasterization1, echo=FALSE, fig.cap="Examples of point rasterization.", warning=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/06-vector-rasterization1.R", print.eval = TRUE) ## ----06-raster-vector-29---------------------------------------------------------------------------- california = dplyr::filter(us_states, NAME == "California") california_borders = st_cast(california, "MULTILINESTRING") raster_template2 = rast(ext(california), resolution = 0.5, crs = st_crs(california)$wkt) ## ----06-raster-vector-30---------------------------------------------------------------------------- california_raster1 = rasterize(california_borders, raster_template2, touches = TRUE) ## ----06-raster-vector-31---------------------------------------------------------------------------- california_raster2 = rasterize(california, raster_template2) ## ----vector-rasterization2, echo=FALSE, fig.cap="Examples of line and polygon rasterizations.", warning=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/06-vector-rasterization2.R", print.eval = TRUE) ## Be careful with the wording! ## In R, vectorization refers to the possibility of replacing `for`-loops and alike by doing things like `1:10 / 2` (see also @wickham_advanced_2019). ## ----06-raster-vector-34---------------------------------------------------------------------------- elev = rast(system.file("raster/elev.tif", package = "spData")) elev_point = as.points(elev) |> st_as_sf() ## ----raster-vectorization1, echo=FALSE, fig.cap="Raster and point representation of the elev object.", warning=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/06-raster-vectorization1.R", print.eval = TRUE) ## ----06-raster-vector-36, eval=FALSE---------------------------------------------------------------- ## dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ## cl = as.contour(dem) ## plot(dem, axes = FALSE) ## plot(cl, add = TRUE) ## ----contour-tmap, echo=FALSE, message=FALSE, fig.cap="DEM with hillshading, showing the southern flank of Mt. Mongón overlaid with contour lines.", fig.scap="DEM with hillshading.", warning=FALSE, fig.asp=0.56---- # hs = shade(slope = terrain(dem, "slope", unit = "radians"), # aspect = terrain(dem, "aspect", unit = "radians")) # plot(hs, col = gray(0:100 / 100), legend = FALSE) # # overlay with DEM # plot(dem, col = terrain.colors(25), alpha = 0.5, legend = FALSE, add = TRUE) # # add contour lines # contour(dem, col = "white", add = TRUE) knitr::include_graphics("images/06-contour-tmap.png") ## ----06-raster-vector-39---------------------------------------------------------------------------- grain = rast(system.file("raster/grain.tif", package = "spData")) grain_poly = as.polygons(grain) |> st_as_sf() ## ----06-raster-vector-40, echo=FALSE, fig.cap="Vectorization of raster (left) into polygons (dissolve = FALSE; center) and aggregated polygons (dissolve = TRUE; right).", warning=FALSE, fig.asp=0.4, fig.scap="Vectorization."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/06-raster-vectorization2.R", print.eval = TRUE) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_06-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/07-reproj.R ================================================ ## ----06-reproj-1, message=FALSE, warning=FALSE------------------------------------------------------ library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ## --------------------------------------------------------------------------------------------------- st_crs("EPSG:4326") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## sf::st_crs("ESRI:54030") ## #> Coordinate Reference System: ## #> User input: ESRI:54030 ## #> wkt: ## #> PROJCRS["World_Robinson", ## #> BASEGEOGCRS["WGS 84", ## #> DATUM["World Geodetic System 1984", ## #> ELLIPSOID["WGS 84",6378137,298.257223563, ## #> LENGTHUNIT["metre",1]]], ## #> ... ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## sf::st_crs("urn:ogc:def:crs:EPSG::4326") ## ----02-spatial-data-52, message=FALSE, results='hide'---------------------------------------------- vector_filepath = system.file("shapes/world.gpkg", package = "spData") new_vector = read_sf(vector_filepath) ## ----02-spatial-data-53, eval=FALSE----------------------------------------------------------------- ## st_crs(new_vector) # get CRS ## #> Coordinate Reference System: ## #> User input: WGS 84 ## #> wkt: ## #> ... ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## # Aim: capture crs for comparison with updated CRS ## new_vector_crs = st_crs(new_vector) ## ----02-spatial-data-54----------------------------------------------------------------------------- new_vector = st_set_crs(new_vector, "EPSG:4326") # set CRS ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## waldo::compare(new_vector_crs, st_crs(new_vector)) ## # `old$input`: "WGS 84" ## # `new$input`: "EPSG:4326" ## ----02-spatial-data-55----------------------------------------------------------------------------- raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") my_rast = rast(raster_filepath) cat(crs(my_rast)) # get CRS ## ----02-spatial-data-56----------------------------------------------------------------------------- crs(my_rast) = "EPSG:26912" # set CRS ## ----06-reproj-2------------------------------------------------------------------------------------ london = data.frame(lon = -0.1, lat = 51.5) |> st_as_sf(coords = c("lon", "lat")) st_is_longlat(london) ## ----06-reproj-3------------------------------------------------------------------------------------ london_geo = st_set_crs(london, "EPSG:4326") st_is_longlat(london_geo) ## ----s2geos, fig.cap="The behavior of the geometry operations in the sf package depending on the input data's CRS.", echo=FALSE---- 'digraph G3 { layout=dot rankdir=TB node [shape = rectangle]; rec1 [label = "Spatial data" shape = oval]; rec2 [label = "Geographic CRS" shape = diamond]; rec3 [label = "Projected CRS\nor CRS is missing" shape = diamond] rec4 [label = "S2 enabled\n(default)" shape = diamond] rec5 [label = "S2 disabled" shape = diamond] rec6 [label = "sf uses s2library for \ngeometry operations" center = true]; rec7 [label = "sf uses GEOS for \ngeometry operations" center = true]; rec8 [label = "Result" shape = oval weight=100]; rec9 [label = "Result" shape = oval weight=100]; rec1 -> rec2; rec1 -> rec3; rec2 -> rec4; rec2 -> rec5; rec3 -> rec7; rec4 -> rec6; rec5 -> rec7; rec6 -> rec8; rec7 -> rec9; }' -> s2geos # # exported manually; the code below returns a low res version of png # tmp = DiagrammeR::grViz(s2geos) # htmlwidgets::saveWidget(widget = tmp, file = "images/07-s2geos.html") # # tmp # tmp = DiagrammeRsvg::export_svg(tmp) # library(htmltools) # html_print(HTML(tmp)) # tmp = charToRaw(tmp) # # rsvg::rsvg_png(tmp, "images/07-s2geos.png") # webshot::webshot(url = "images/07-s2geos.html", file = "images/07-s2geos.png", vwidth = 800, vheight = 600) # download.file( # "https://user-images.githubusercontent.com/1825120/188572856-7946ae32-98de-444c-9f48-b1d7afcf9345.png", # destfile = "images/07-s2geos.png" # ) # browseURL("images/07-s2geos.png") knitr::include_graphics("images/07-s2geos.png") ## ----06-reproj-4-1---------------------------------------------------------------------------------- london_buff_no_crs = st_buffer(london, dist = 1) # incorrect: no CRS london_buff_s2 = st_buffer(london_geo, dist = 1e5) # silent use of s2 london_buff_s2_100_cells = st_buffer(london_geo, dist = 1e5, max_cells = 100) ## ----06-reproj-4-2---------------------------------------------------------------------------------- sf::sf_use_s2(FALSE) london_buff_lonlat = st_buffer(london_geo, dist = 1) # incorrect result sf::sf_use_s2(TRUE) ## The distance between two lines of longitude, called meridians, is around 111 km at the equator (execute `geosphere::distGeo(c(0, 0), c(1, 0))` to find the precise distance). ## This shrinks to zero at the poles. ## At the latitude of London, for example, meridians are less than 70 km apart (challenge: execute code that verifies this). ## ## Lines of latitude, by contrast, are equidistant from each other irrespective of latitude: they are always around 111 km apart, including at the equator and near the poles (see Figures \@ref(fig:crs-buf) to \@ref(fig:wintriproj)). ## ----06-reproj-6------------------------------------------------------------------------------------ london_proj = data.frame(x = 530000, y = 180000) |> st_as_sf(coords = 1:2, crs = "EPSG:27700") ## ----06-reproj-7, eval=FALSE------------------------------------------------------------------------ ## st_crs(london_proj) ## #> Coordinate Reference System: ## #> User input: EPSG:27700 ## #> wkt: ## #> PROJCRS["OSGB36 / British National Grid", ## #> BASEGEOGCRS["OSGB36", ## #> DATUM["Ordnance Survey of Great Britain 1936", ## #> ELLIPSOID["Airy 1830",6377563.396,299.3249646, ## #> LENGTHUNIT["metre",1]]], ## #> ... ## ----06-reproj-8------------------------------------------------------------------------------------ london_buff_projected = st_buffer(london_proj, 1e5) ## ----crs-buf-old, include=FALSE, eval=FALSE--------------------------------------------------------- ## uk = rnaturalearth::ne_countries(scale = 50) |> ## st_as_sf() |> ## filter(grepl(pattern = "United Kingdom|Ire", x = name_long)) ## plot(london_buff_s2, graticule = st_crs(4326), axes = TRUE, reset = FALSE, lwd = 2) ## plot(london_buff_s2_100_cells, lwd = 9, add = TRUE) ## plot(st_geometry(uk), add = TRUE, border = "gray", lwd = 3) ## uk_proj = uk |> ## st_transform("EPSG:27700") ## plot(london_buff_projected, graticule = st_crs("EPSG:27700"), axes = TRUE, reset = FALSE, lwd = 2) ## plot(london_proj, add = TRUE) ## plot(st_geometry(uk_proj), add = TRUE, border = "gray", lwd = 3) ## plot(london_buff_lonlat, graticule = st_crs("EPSG:27700"), axes = TRUE, reset = FALSE, lwd = 2) ## plot(london_proj, add = TRUE) ## plot(st_geometry(uk), add = TRUE, border = "gray", lwd = 3) ## ----crs-buf, fig.cap="Buffers around London showing results created with the S2 spherical geometry engine on lon/lat data (left), projected data (middle) and lon/lat data without using spherical geometry (right). The left plot illustrates the result of buffering unprojected data with sf, which calls Google's S2 spherical geometry engine by default with `max_cells = 1000` (thin line). The thick 'blocky' line illustrates the result of the same operation with `max_cells = 100`.", fig.scap="Buffers around London with a geographic and projected CRS.", echo=FALSE, fig.asp=0.39, fig.width = 8---- uk = rnaturalearth::ne_countries(scale = 50) |> st_as_sf() |> filter(grepl(pattern = "United Kingdom|Ire", x = name_long)) library(tmap) tm1 = tm_shape(london_buff_s2, bbox = st_bbox(london_buff_s2_100_cells)) + tm_graticules(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(london_buff_s2_100_cells) + tm_borders(col = "black", lwd = 1.5) + tm_shape(uk) + tm_polygons(lty = 3, alpha = 0.2, col = "#567D46") + tm_shape(london_proj) + tm_symbols() tm2 = tm_shape(london_buff_projected, bbox = st_bbox(london_buff_s2_100_cells)) + tm_grid(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(uk) + tm_polygons(lty = 3, alpha = 0.2, col = "#567D46") + tm_shape(london_proj) + tm_symbols() tm3 = tm_shape(london_buff_lonlat, bbox = st_bbox(london_buff_s2_100_cells)) + tm_graticules(lwd = 0.2) + tm_borders(col = "black", lwd = 0.5) + tm_shape(uk) + tm_polygons(lty = 3, alpha = 0.2, col = "#567D46") + tm_shape(london_proj) + tm_symbols() tmap_arrange(tm1, tm2, tm3, nrow = 1) ## ----06-reproj-9, eval=FALSE------------------------------------------------------------------------ ## st_distance(london_geo, london_proj) ## # > Error: st_crs(x) == st_crs(y) is not TRUE ## ----06-reproj-12, eval=FALSE, echo=FALSE----------------------------------------------------------- ## utm_nums_n = 32601:32660 ## utm_nums_s = 32701:32760 ## crs_data = rgdal::make_EPSG() ## crs_data[grep(utm_nums_n[1], crs_data$code), ] # zone 1N ## crs_data[grep(utm_nums_n[60], crs_data$code), ] # zone 60N ## crs_data[grep(utm_nums_s[1], crs_data$code), ] ## crs_data[grep(utm_nums_s[60], crs_data$code), ] ## crs_data[grep("UTM zone 60N", crs_data$note), ] # many ## crs_data[grep("UTM zone 60S", crs_data$note), ] # many ## crs_data[grep("UTM zone 60S", crs_data$note), ] # many ## crs_utm = crs_data[grepl("utm", crs_data$prj4), ] # 1066 ## crs_utm_zone = crs_utm[grepl("zone=", crs_utm$prj4), ] ## crs_utm_south = crs_utm[grepl("south", crs_utm$prj4), ] ## ----06-reproj-13----------------------------------------------------------------------------------- lonlat2UTM = function(lonlat) { utm = (floor((lonlat[1] + 180) / 6) %% 60) + 1 if (lonlat[2] > 0) { utm + 32600 } else{ utm + 32700 } } ## ----06-reproj-14, echo=FALSE, eval=FALSE----------------------------------------------------------- ## stplanr::geo_code("Auckland") ## ----06-reproj-15----------------------------------------------------------------------------------- lonlat2UTM(c(174.7, -36.9)) lonlat2UTM(st_coordinates(london)) ## ----06-reproj-10----------------------------------------------------------------------------------- london2 = st_transform(london_geo, "EPSG:27700") ## ----06-reproj-11----------------------------------------------------------------------------------- st_distance(london2, london_proj) ## --------------------------------------------------------------------------------------------------- st_crs(cycle_hire_osm) ## ----06-reproj-16----------------------------------------------------------------------------------- crs_lnd = st_crs(london_geo) class(crs_lnd) names(crs_lnd) ## --------------------------------------------------------------------------------------------------- crs_lnd$Name crs_lnd$proj4string crs_lnd$epsg ## ----06-reproj-18, eval=FALSE----------------------------------------------------------------------- ## cycle_hire_osm_projected = st_transform(cycle_hire_osm, "EPSG:27700") ## st_crs(cycle_hire_osm_projected) ## #> Coordinate Reference System: ## #> User input: EPSG:27700 ## #> wkt: ## #> PROJCRS["OSGB36 / British National Grid", ## #> ... ## ----06-reproj-19----------------------------------------------------------------------------------- crs_lnd_new = st_crs("EPSG:27700") crs_lnd_new$Name crs_lnd_new$proj4string crs_lnd_new$epsg ## Printing a spatial object in the console automatically returns its coordinate reference system. ## To access and modify it explicitly, use the `st_crs` function, for example, `st_crs(cycle_hire_osm)`. ## Reprojection of the regular rasters is also known as warping. ## Additionally, there is a second similar operation called "transformation". ## Instead of resampling all of the values, it leaves all values intact but recomputes new coordinates for every raster cell, changing the grid geometry. ## For example, it could convert the input raster (a regular grid) into a curvilinear grid. ## The transformation operation can be performed in R using [the **stars** package](https://r-spatial.github.io/stars/articles/stars5.html). ## ---- include=FALSE--------------------------------------------------------------------------------- #test the above idea library(terra) library(sf) con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) con_raster_ea = project(con_raster, "EPSG:32612", method = "bilinear") con_poly = st_as_sf(as.polygons(con_raster>0)) con_poly_ea = st_transform(con_poly, "EPSG:32612") plot(con_raster) plot(con_poly, col = NA, add = TRUE, lwd = 4) plot(con_raster_ea) plot(con_poly_ea, col = NA, add = TRUE, lwd = 4) ## ----06-reproj-29, results='hide'------------------------------------------------------------------- cat_raster = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) crs(cat_raster) #> PROJCRS["NAD83 / UTM zone 12N", #> ... ## ----06-reproj-30----------------------------------------------------------------------------------- unique(cat_raster) ## ----06-reproj-31----------------------------------------------------------------------------------- cat_raster_wgs84 = project(cat_raster, "EPSG:4326", method = "near") ## ----catraster, echo=FALSE-------------------------------------------------------------------------- tibble( CRS = c("NAD83", "WGS84"), nrow = c(nrow(cat_raster), nrow(cat_raster_wgs84)), ncol = c(ncol(cat_raster), ncol(cat_raster_wgs84)), ncell = c(ncell(cat_raster), ncell(cat_raster_wgs84)), resolution = c(mean(res(cat_raster)), mean(res(cat_raster_wgs84), na.rm = TRUE)), unique_categories = c(length(unique(values(cat_raster))), length(unique(values(cat_raster_wgs84))))) |> knitr::kable(caption = paste("Key attributes in the original ('cat\\_raster')", "and projected ('cat\\_raster\\_wgs84')", "categorical raster datasets."), caption.short = paste("Key attributes in the original and", "projected raster datasets"), digits = 4, booktabs = TRUE) ## ----06-reproj-32----------------------------------------------------------------------------------- con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) crs(con_raster) ## ----06-reproj-34----------------------------------------------------------------------------------- con_raster_ea = project(con_raster, "EPSG:32612", method = "bilinear") crs(con_raster_ea) ## ----rastercrs, echo=FALSE-------------------------------------------------------------------------- tibble( CRS = c("WGS84", "UTM zone 12N"), nrow = c(nrow(con_raster), nrow(con_raster_ea)), ncol = c(ncol(con_raster), ncol(con_raster_ea)), ncell = c(ncell(con_raster), ncell(con_raster_ea)), resolution = c(mean(res(con_raster)), mean(res(con_raster_ea), na.rm = TRUE)), mean = c(mean(values(con_raster)), mean(values(con_raster_ea), na.rm = TRUE))) |> knitr::kable(caption = paste("Key attributes in the original ('con\\_raster')", "and projected ('con\\_raster\\_ea') continuous raster", "datasets."), caption.short = paste("Key attributes in the original and", "projected raster datasets"), digits = 4, booktabs = TRUE) ## Of course, the limitations of 2D Earth projections apply as much to vector as to raster data. ## At best we can comply with two out of three spatial properties (distance, area, direction). ## Therefore, the task at hand determines which projection to choose. ## For instance, if we are interested in a density (points per grid cell or inhabitants per grid cell) we should use an equal-area projection (see also Chapter \@ref(location)). ## --------------------------------------------------------------------------------------------------- zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) ## ---- warning=FALSE--------------------------------------------------------------------------------- zion_centr = st_centroid(zion) zion_centr_wgs84 = st_transform(zion_centr, "EPSG:4326") st_as_text(st_geometry(zion_centr_wgs84)) ## --------------------------------------------------------------------------------------------------- my_wkt = 'PROJCS["Custom_AEQD", GEOGCS["GCS_WGS_1984", DATUM["WGS_1984", SPHEROID["WGS_1984",6378137.0,298.257223563]], PRIMEM["Greenwich",0.0], UNIT["Degree",0.0174532925199433]], PROJECTION["Azimuthal_Equidistant"], PARAMETER["Central_Meridian",-113.0263], PARAMETER["Latitude_Of_Origin",37.29818], UNIT["Meter",1.0]]' ## --------------------------------------------------------------------------------------------------- zion_aeqd = st_transform(zion, my_wkt) ## ----06-reproj-22----------------------------------------------------------------------------------- world_mollweide = st_transform(world, crs = "+proj=moll") ## ----mollproj, fig.cap="Mollweide projection of the world.", warning=FALSE, message=FALSE, echo=FALSE---- library(tmap) world_mollweide_gr = st_graticule(lat = c(-89.9, seq(-80, 80, 20), 89.9)) |> st_transform(crs = "+proj=moll") tm_shape(world_mollweide_gr) + tm_lines(col = "gray") + tm_shape(world_mollweide) + tm_borders(col = "black") ## ----06-reproj-23----------------------------------------------------------------------------------- world_wintri = st_transform(world, crs = "+proj=wintri") ## ----06-reproj-23-tests, eval=FALSE, echo=FALSE----------------------------------------------------- ## world_wintri = lwgeom::st_transform_proj(world, crs = "+proj=wintri") ## world_wintri2 = st_transform(world, crs = "+proj=wintri") ## world_tissot = st_transform(world, crs = "+proj=tissot +lat_1=60 +lat_2=65") ## waldo::compare(world_wintri$geom[1], world_wintri2$geom[1]) ## world_tpers = st_transform(world, crs = "+proj=tpers +h=5500000 +lat_0=40") ## plot(st_cast(world_tpers, "MULTILINESTRING")) # fails ## plot(st_coordinates(world_tpers)) # fails ## world_tpers_complete = world_tpers[st_is_valid(world_tpers), ] ## world_tpers_complete = world_tpers_complete[!st_is_empty(world_tpers_complete), ] ## plot(world_tpers_complete["pop"]) ## ----wintriproj, fig.cap="Winkel tripel projection of the world.", echo=FALSE----------------------- world_wintri_gr = st_graticule(lat = c(-89.9, seq(-80, 80, 20), 89.9)) |> st_transform(crs = "+proj=wintri") library(tmap) tm_shape(world_wintri_gr) + tm_lines(col = "gray") + tm_shape(world_wintri) + tm_borders(col = "black") ## The three main functions for transformation of simple features coordinates are `sf::st_transform()`, `sf::sf_project()`, and `lwgeom::st_transform_proj()`. ## `st_transform()` uses the GDAL interface to PROJ, while `sf_project()` (which works with two-column numeric matrices, representing points) and `lwgeom::st_transform_proj()` use PROJ directly. ## `st_tranform()` is appropriate for most situations, and provides a set of the most often used parameters and well-defined transformations. ## `sf_project()` may be suited for point transformations when speed is important. ## `st_transform_proj()` allows for greater customization of a projection, which includes cases when some of the PROJ parameters (e.g., `+over`) or projection (`+proj=wintri`) is not available in `st_transform()`. ## ----06-reproj-25, eval=FALSE, echo=FALSE----------------------------------------------------------- ## # demo of sf_project ## mat_lonlat = as.matrix(data.frame(x = 0:20, y = 50:70)) ## plot(mat_lonlat) ## mat_projected = sf_project(from = st_crs(4326)$proj4string, to = st_crs(27700)$proj4string, pts = mat_lonlat) ## plot(mat_projected) ## ----06-reproj-27----------------------------------------------------------------------------------- world_laea2 = st_transform(world, crs = "+proj=laea +x_0=0 +y_0=0 +lon_0=-74 +lat_0=40") ## ----laeaproj2, fig.cap="Lambert azimuthal equal-area projection of the world centered on New York City.", fig.scap="Lambert azimuthal equal-area projection centered on New York City.", warning=FALSE, echo=FALSE---- # Currently fails, see https://github.com/Robinlovelace/geocompr/issues/460 world_laea2_g = st_graticule(ndiscr = 10000) |> st_transform("+proj=laea +x_0=0 +y_0=0 +lon_0=-74 +lat_0=40.1 +ellps=WGS84 +no_defs") |> st_geometry() tm_shape(world_laea2_g) + tm_lines(col = "gray") + tm_shape(world_laea2) + tm_borders(col = "black") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/72223267-c79a4780-3564-11ea-9d7e-9644523e349b.png") ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_07-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/08-read-write-plot.R ================================================ ## ----07-read-write-plot-1, message=FALSE------------------------------------------------------------ library(sf) library(terra) library(dplyr) library(spData) ## ----07-read-write-plot-2, eval=FALSE--------------------------------------------------------------- ## download.file(url = "https://hs.pangaea.de/Maps/PeRL/PeRL_permafrost_landscapes.zip", ## destfile = "PeRL_permafrost_landscapes.zip", ## mode = "wb") ## unzip("PeRL_permafrost_landscapes.zip") ## canada_perma_land = read_sf("PeRL_permafrost_landscapes/canada_perma_land.shp") ## ----datapackages, echo=FALSE, warning=FALSE-------------------------------------------------------- datapackages = tibble::tribble( ~`Package`, ~Description, "osmdata", "Download and import small OpenStreetMap datasets.", "osmextract", "Download and import large OpenStreetMap datasets.", "geodata", "Download and import imports administrative, elevation, WorldClim data.", "rnaturalearth", "Access to Natural Earth vector and raster data.", "rnoaa", "Imports National Oceanic and Atmospheric Administration (NOAA) climate data." ) knitr::kable(datapackages, caption = "Selected R packages for geographic data retrieval.", caption.short = "Selected R packages for geographic data retrieval.", booktabs = TRUE) |> kableExtra::kable_styling(latex_options="scale_down") ## ----07-read-write-plot-3--------------------------------------------------------------------------- library(rnaturalearth) usa = ne_countries(country = "United States of America") # United States borders class(usa) # alternative way of accessing the data, with geodata # geodata::gadm("USA", level = 0, path = tempdir()) ## ----07-read-write-plot-4--------------------------------------------------------------------------- usa_sf = st_as_sf(usa) ## ----07-read-write-plot-5, eval=FALSE--------------------------------------------------------------- ## library(geodata) ## worldclim_prec = worldclim_global("prec", res = 10, path = tempdir()) ## class(worldclim_prec) ## ----07-read-write-plot-6, eval=FALSE--------------------------------------------------------------- ## library(osmdata) ## parks = opq(bbox = "leeds uk") |> ## add_osm_feature(key = "leisure", value = "park") |> ## osmdata_sf() ## ----07-read-write-plot-7, eval=FALSE--------------------------------------------------------------- ## world2 = spData::world ## world3 = read_sf(system.file("shapes/world.gpkg", package = "spData")) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## library(tidygeocoder) ## geo_df = data.frame(address = "54 Frith St, London W1D 4SJ, UK") ## geo_df = geocode(geo_df, address, method = "osm") ## geo_df ## ---- eval=FALSE------------------------------------------------------------------------------------ ## geo_sf = st_as_sf(geo_df, coords = c("lat", "long"), crs = "EPSG:4326") ## ----07-read-write-plot-8--------------------------------------------------------------------------- library(httr) base_url = "http://www.fao.org" endpoint = "/figis/geoserver/wfs" q = list(request = "GetCapabilities") res = GET(url = modify_url(base_url, path = endpoint), query = q) res$url ## ----07-read-write-plot-9, eval=FALSE--------------------------------------------------------------- ## txt = content(res, "text") ## xml = xml2::read_xml(txt) ## ----07-read-write-plot-10, eval=FALSE-------------------------------------------------------------- ## xml ## #> {xml_document} ... ## #> [1] \n GeoServer WFS... ## #> [2] \n UN-FAO Fishe... ## #> ... ## ----07-read-write-plot-11, echo=FALSE, eval=FALSE-------------------------------------------------- ## library(xml2) ## library(curl) ## library(httr) ## base_url = "http://www.fao.org/figis/geoserver/wfs" ## q = list(request = "GetCapabilities") ## res = GET(url = base_url, query = q) ## doc = xmlParse(res) ## root = xmlRoot(doc) ## names(root) ## names(root[["FeatureTypeList"]]) ## root[["FeatureTypeList"]][["FeatureType"]][["Name"]] ## tmp = xmlSApply(root[["FeatureTypeList"]], function(x) xmlValue(x[["Name"]])) ## ----07-read-write-plot-12, eval=FALSE-------------------------------------------------------------- ## qf = list(request = "GetFeature", typeName = "area:FAO_AREAS") ## file = tempfile(fileext = ".gml") ## GET(url = base_url, path = endpoint, query = qf, write_disk(file)) ## fao_areas = read_sf(file) ## ----formats, echo=FALSE---------------------------------------------------------------------------- file_formats = tibble::tribble(~Name, ~Extension, ~Info, ~Type, ~Model, "ESRI Shapefile", ".shp (the main file)", "Popular format consisting of at least three files. No support for: files > 2GB; mixed types; names > 10 chars; cols > 255.", "Vector", "Partially open", "GeoJSON", ".geojson", "Extends the JSON exchange format by including a subset of the simple feature representation; mostly used for storing coordinates in longitude and latitude; it is extended by the TopoJSON format", "Vector", "Open", "KML", ".kml", "XML-based format for spatial visualization, developed for use with Google Earth. Zipped KML file forms the KMZ format.", "Vector", "Open", "GPX", ".gpx", "XML schema created for exchange of GPS data.", "Vector", "Open", "FlatGeobuf", ".fgb", "Single file format allowing for quick reading and writing of vector data. Has streaming capabilities.", "Vector", "Open", "GeoTIFF", ".tif/.tiff", "Popular raster format. A TIFF file containing additional spatial metadata.", "Raster", "Open", "Arc ASCII", ".asc", "Text format where the first six lines represent the raster header, followed by the raster cell values arranged in rows and columns.", "Raster", "Open", "SQLite/SpatiaLite", ".sqlite", "Standalone relational database, SpatiaLite is the spatial extension of SQLite.", "Vector and raster", "Open", "ESRI FileGDB", ".gdb", "Spatial and nonspatial objects created by ArcGIS. Allows: multiple feature classes; topology. Limited support from GDAL.", "Vector and raster", "Proprietary", "GeoPackage", ".gpkg", "Lightweight database container based on SQLite allowing an easy and platform-independent exchange of geodata", "Vector and (very limited) raster", "Open" ) knitr::kable(file_formats, caption = "Selected spatial file formats.", caption.short = "Selected spatial file formats.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "7em") |> kableExtra::column_spec(3, width = "14em") |> kableExtra::column_spec(5, width = "7em") ## ----07-read-write-plot-17, eval=FALSE-------------------------------------------------------------- ## sf_drivers = st_drivers() ## head(sf_drivers, n = 3) ## summary(sf_drivers[-c(1:2)]) ## ----drivers, echo=FALSE---------------------------------------------------------------------------- sf_drivers = st_drivers() |> dplyr::filter(name %in% c("ESRI Shapefile", "GeoJSON", "KML", "GPX", "GPKG", "FlatGeobuf")) |> tibble::as_tibble() # remove unhelpful row names knitr::kable(head(sf_drivers, n = 6), caption = paste("Popular drivers/formats for reading/writing", "vector data."), caption.short = "Sample of available vector drivers.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "7em") ## ----07-read-write-plot-19-------------------------------------------------------------------------- f = system.file("shapes/world.gpkg", package = "spData") world = read_sf(f, quiet = TRUE) ## --------------------------------------------------------------------------------------------------- tanzania = read_sf(f, query = 'SELECT * FROM world WHERE name_long = "Tanzania"') ## ---- eval=FALSE, echo=FALSE------------------------------------------------------------------------ ## tanzania = read_sf(f, query = 'SELECT * FROM world WHERE FID = 0') ## --------------------------------------------------------------------------------------------------- tanzania_buf = st_buffer(tanzania, 50000) tanzania_buf_geom = st_geometry(tanzania_buf) tanzania_buf_wkt = st_as_text(tanzania_buf_geom) ## --------------------------------------------------------------------------------------------------- tanzania_neigh = read_sf(f, wkt_filter = tanzania_buf_wkt) ## ----readsfquery, echo=FALSE, message=FALSE, fig.cap="Reading a subset of the vector data using a query (A) and a wkt filter (B)."---- library(tmap) tm1 = tm_shape(tanzania) + tm_polygons(lwd = 2) + tm_text(text = "name_long") + tm_scale_bar(c(0, 200, 400), position = c("left", "bottom")) + tm_layout(main.title = "A. query") tanzania_neigh[tanzania_neigh$iso_a2 == "CD", "name_long"] = "Democratic\nRepublic\nof the Congo" tm2 = tm_shape(tanzania_neigh) + tm_polygons() + tm_text(text = "name_long", size = "AREA", auto.placement = FALSE, remove.overlap = FALSE, root = 6, legend.size.show = FALSE) + tm_shape(tanzania_buf) + tm_polygons(col = "red", border.col = "red", alpha = 0.05) + tm_add_legend(type = "fill", labels = "50km buffer around Tanzania", col = "red", alpha = 0.1, border.col = "red") + tm_scale_bar(c(0, 200, 400), position = c("right", "bottom")) + tm_layout(legend.width = 0.5, legend.position = c("left", "bottom"), main.title = "B. wkt_filter") tmap_arrange(tm1, tm2) ## ----07-read-write-plot-20, results='hide'---------------------------------------------------------- cycle_hire_txt = system.file("misc/cycle_hire_xy.csv", package = "spData") cycle_hire_xy = read_sf(cycle_hire_txt, options = c("X_POSSIBLE_NAMES=X", "Y_POSSIBLE_NAMES=Y")) ## ----07-read-write-plot-21, results='hide'---------------------------------------------------------- world_txt = system.file("misc/world_wkt.csv", package = "spData") world_wkt = read_sf(world_txt, options = "GEOM_POSSIBLE_NAMES=WKT") # the same as world_wkt2 = st_read(world_txt, options = "GEOM_POSSIBLE_NAMES=WKT", quiet = TRUE, stringsAsFactors = FALSE, as_tibble = TRUE) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## identical(world_wkt, world_wkt2) ## Not all of the supported vector file formats store information about their coordinate reference system. ## In these situations, it is possible to add the missing information using the `st_set_crs()` function. ## Please refer also to Section \@ref(crs-intro) for more information. ## ----07-read-write-plot-23-------------------------------------------------------------------------- u = "https://developers.google.com/kml/documentation/KML_Samples.kml" download.file(u, "KML_Samples.kml") st_layers("KML_Samples.kml") kml = read_sf("KML_Samples.kml", layer = "Placemarks") ## ---- echo=FALSE, results='hide'-------------------------------------------------------------------- file.remove("KML_Samples.kml") ## ----07-read-write-plot-24, message=FALSE----------------------------------------------------------- raster_filepath = system.file("raster/srtm.tif", package = "spDataLarge") single_layer = rast(raster_filepath) ## ----07-read-write-plot-25-------------------------------------------------------------------------- multilayer_filepath = system.file("raster/landsat.tif", package = "spDataLarge") multilayer_rast = rast(multilayer_filepath) ## --------------------------------------------------------------------------------------------------- myurl = "/vsicurl/https://zenodo.org/record/5774954/files/clm_snow.prob_esacci.dec_p.90_500m_s0..0cm_2000..2012_v2.0.tif" snow = rast(myurl) snow ## --------------------------------------------------------------------------------------------------- rey = data.frame(lon = -21.94, lat = 64.15) snow_rey = extract(snow, rey) snow_rey ## ----07-read-write-plot-27, echo=FALSE, results='hide'---------------------------------------------- world_files = list.files(pattern = "world*") file.remove(world_files) ## ----07-read-write-plot-28-------------------------------------------------------------------------- write_sf(obj = world, dsn = "world.gpkg") ## ----07-read-write-plot-29, error=TRUE-------------------------------------------------------------- write_sf(obj = world, dsn = "world.gpkg") ## ----07-read-write-plot-31, results='hide'---------------------------------------------------------- write_sf(obj = world, dsn = "world_many_layers.gpkg", append = TRUE) ## ----07-read-write-plot-32-------------------------------------------------------------------------- st_write(obj = world, dsn = "world2.gpkg") ## ----07-read-write-plot-33, eval=FALSE-------------------------------------------------------------- ## write_sf(cycle_hire_xy, "cycle_hire_xy.csv", layer_options = "GEOMETRY=AS_XY") ## write_sf(world_wkt, "world_wkt.csv", layer_options = "GEOMETRY=AS_WKT") ## ---- echo=FALSE, results='hide'-------------------------------------------------------------------- file.remove(world_files) ## ----datatypes, echo=FALSE-------------------------------------------------------------------------- dT = tibble::tribble( ~`Data type`, ~`Minimum value`, ~`Maximum value`, "INT1U", "0", "255", "INT2S", "-32,767", "32,767", "INT2U", "0", "65,534", "INT4S", "-2,147,483,647", "2,147,483,647", "INT4U", "0", "4,294,967,296", "FLT4S", "-3.4e+38", "3.4e+38", "FLT8S", "-1.7e+308", "1.7e+308" ) knitr::kable(dT, caption = "Data types supported by the terra package.", caption.short = "Data types supported by the terra package.", booktabs = TRUE) ## ----07-read-write-plot-34, eval=FALSE-------------------------------------------------------------- ## writeRaster(single_layer, filename = "my_raster.tif", datatype = "INT2U") ## ----07-read-write-plot-35, eval=FALSE-------------------------------------------------------------- ## writeRaster(x = single_layer, filename = "my_raster.tif", ## gdal = c("COMPRESS=NONE"), overwrite = TRUE) ## ----07-read-write-plot-35b, eval=FALSE------------------------------------------------------------- ## writeRaster(x = single_layer, filename = "my_raster.tif", ## filetype = "COG", overwrite = TRUE) ## ----07-read-write-plot-36, eval=FALSE-------------------------------------------------------------- ## png(filename = "lifeExp.png", width = 500, height = 350) ## plot(world["lifeExp"]) ## dev.off() ## ----07-read-write-plot-37, eval=FALSE-------------------------------------------------------------- ## library(tmap) ## tmap_obj = tm_shape(world) + tm_polygons(col = "lifeExp") ## tmap_save(tmap_obj, filename = "lifeExp_tmap.png") ## ----07-read-write-plot-38, eval=FALSE-------------------------------------------------------------- ## library(mapview) ## mapview_obj = mapview(world, zcol = "lifeExp", legend = TRUE) ## mapshot(mapview_obj, file = "my_interactive_map.html") ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_08-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/09-mapping.R ================================================ ## ----08-mapping-1, message=FALSE-------------------------------------------------------------------- library(sf) library(raster) library(dplyr) library(spData) library(spDataLarge) ## ----08-mapping-2, message=FALSE-------------------------------------------------------------------- library(tmap) # for static and interactive maps library(leaflet) # for interactive maps library(ggplot2) # tidyverse data visualization package ## ----08-mapping-3, eval=FALSE----------------------------------------------------------------------- ## # Add fill layer to nz shape ## tm_shape(nz) + ## tm_fill() ## # Add border layer to nz shape ## tm_shape(nz) + ## tm_borders() ## # Add fill and border layers to nz shape ## tm_shape(nz) + ## tm_fill() + ## tm_borders() ## ----tmshape, echo=FALSE, message=FALSE, fig.cap="New Zealand's shape plotted with fill (left), border (middle) and fill and border (right) layers added using tmap functions.", fig.scap="New Zealand's shape plotted using tmap functions."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-tmshape.R", print.eval = TRUE) ## `qtm()` is a handy function to create **q**uick **t**hematic **m**aps (hence the snappy name). ## It is concise and provides a good default visualization in many cases: ## `qtm(nz)`, for example, is equivalent to `tm_shape(nz) + tm_fill() + tm_borders()`. ## Further, layers can be added concisely using multiple `qtm()` calls, such as `qtm(nz) + qtm(nz_height)`. ## The disadvantage is that it makes aesthetics of individual layers harder to control, explaining why we avoid teaching it in this chapter. ## ----08-mapping-4----------------------------------------------------------------------------------- map_nz = tm_shape(nz) + tm_polygons() class(map_nz) ## ----08-mapping-5, results='hide'------------------------------------------------------------------- map_nz1 = map_nz + tm_shape(nz_elev) + tm_raster(alpha = 0.7) ## ----08-mapping-6----------------------------------------------------------------------------------- nz_water = st_union(nz) |> st_buffer(22200) |> st_cast(to = "LINESTRING") map_nz2 = map_nz1 + tm_shape(nz_water) + tm_lines() ## ----08-mapping-7----------------------------------------------------------------------------------- map_nz3 = map_nz2 + tm_shape(nz_height) + tm_dots() ## ----tmlayers, message=FALSE, fig.cap="Maps with additional layers added to the final map of Figure 9.1.", fig.scap="Additional layers added to the output of Figure 9.1."---- tmap_arrange(map_nz1, map_nz2, map_nz3) ## ----tmstatic, message=FALSE, fig.cap="The impact of changing commonly used fill and border aesthetics to fixed values.", fig.scap="The impact of changing commonly used aesthetics."---- ma1 = tm_shape(nz) + tm_fill(col = "red") ma2 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) ma3 = tm_shape(nz) + tm_borders(col = "blue") ma4 = tm_shape(nz) + tm_borders(lwd = 3) ma5 = tm_shape(nz) + tm_borders(lty = 2) ma6 = tm_shape(nz) + tm_fill(col = "red", alpha = 0.3) + tm_borders(col = "blue", lwd = 3, lty = 2) tmap_arrange(ma1, ma2, ma3, ma4, ma5, ma6) ## ----08-mapping-8, echo=FALSE, eval=FALSE----------------------------------------------------------- ## # aim: show what happpens when names clash ## library(tmap) ## library(spData) ## nz$red = 1:nrow(nz) ## qtm(nz, "red") ## ----08-mapping-9, eval=FALSE----------------------------------------------------------------------- ## plot(st_geometry(nz), col = nz$Land_area) # works ## tm_shape(nz) + tm_fill(col = nz$Land_area) # fails ## #> Error: Fill argument neither colors nor valid variable name(s) ## ----08-mapping-10, fig.show='hide', message=FALSE-------------------------------------------------- tm_shape(nz) + tm_fill(col = "Land_area") ## ----tmcol, message=FALSE, fig.cap="Comparison of base (left) and tmap (right) handling of a numeric color field.", fig.scap="Comparison of base graphics and tmap", echo=FALSE, out.width="45%", fig.show='hold', warning=FALSE, message=FALSE---- plot(nz["Land_area"]) tm_shape(nz) + tm_fill(col = "Land_area") ## ----08-mapping-11---------------------------------------------------------------------------------- legend_title = expression("Area (km"^2*")") map_nza = tm_shape(nz) + tm_fill(col = "Land_area", title = legend_title) + tm_borders() ## ----08-mapping-12, eval=FALSE---------------------------------------------------------------------- ## tm_shape(nz) + tm_polygons(col = "Median_income") ## breaks = c(0, 3, 4, 5) * 10000 ## tm_shape(nz) + tm_polygons(col = "Median_income", breaks = breaks) ## tm_shape(nz) + tm_polygons(col = "Median_income", n = 10) ## tm_shape(nz) + tm_polygons(col = "Median_income", palette = "BuGn") ## ----tmpal, message=FALSE, fig.cap="Settings that affect color settings. The results show (from left to right): default settings, manual breaks, n breaks, and the impact of changing the palette.", fig.scap="Settings that affect color settings.", echo=FALSE, fig.asp=0.56---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-tmpal.R", print.eval = TRUE) ## ----break-styles, message=FALSE, fig.cap="Different binning methods set using the style argument in tmap.", , fig.scap="Different binning methods using tmap.", echo=FALSE---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-break-styles.R", print.eval = TRUE) ## Although `style` is an argument of **tmap** functions, in fact it originates as an argument in `classInt::classIntervals()` --- see the help page of this function for details. ## ----08-mapping-13, eval=FALSE---------------------------------------------------------------------- ## tm_shape(nz) + tm_polygons("Population", palette = "Blues") ## tm_shape(nz) + tm_polygons("Population", palette = "YlOrBr") ## ----colpal, echo=FALSE, message=FALSE, fig.cap="Examples of categorical, sequential and diverging palettes.", out.width="50%"---- library(RColorBrewer) many_palette_plotter = function(color_names, n, titles){ n_colors = length(color_names) ylim = c(0, n_colors) par(mar = c(0, 5, 0, 0)) plot(1, 1, xlim = c(0, max(n)), ylim = ylim, type = "n", axes = FALSE, bty = "n", xlab = "", ylab = "") for(i in seq_len(n_colors)){ one_color = brewer.pal(n = n, name = color_names[i]) rect(xleft = 0:(n - 1), ybottom = i - 1, xright = 1:n, ytop = i - 0.2, col = one_color, border = "light gray") } text(rep(-0.1, n_colors), (1: n_colors) - 0.6, labels = titles, xpd = TRUE, adj = 1) } many_palette_plotter(c("PRGn", "YlGn", "Set2"), 7, titles = c("Diverging", "Sequential", "Categorical")) ## ----na-sb, message=FALSE, fig.cap="Map with additional elements - a north arrow and scale bar.", out.width="50%", fig.asp=1, fig.scap="Map with a north arrow and scale bar."---- map_nz + tm_compass(type = "8star", position = c("left", "top")) + tm_scale_bar(breaks = c(0, 100, 200), text.size = 1) ## ----08-mapping-14, eval=FALSE---------------------------------------------------------------------- ## map_nz + tm_layout(title = "New Zealand") ## map_nz + tm_layout(scale = 5) ## map_nz + tm_layout(bg.color = "lightblue") ## map_nz + tm_layout(frame = FALSE) ## ----layout1, message=FALSE, fig.cap="Layout options specified by (from left to right) title, scale, bg.color and frame arguments.", fig.scap="Layout options specified by the tmap arguments.", echo=FALSE, fig.asp=0.56---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-layout1.R", print.eval = TRUE) ## ----layout2, message=FALSE, fig.cap="Selected layout options.", echo=FALSE, fig.asp=0.56---- # todo: add more useful settings to this plot source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-layout2.R", print.eval = TRUE) ## ----layout3, message=FALSE, fig.cap="Selected color-related layout options.", echo=FALSE, fig.asp=0.56---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-layout3.R", print.eval = TRUE) ## ----08-mapping-15, eval=FALSE---------------------------------------------------------------------- ## map_nza + tm_style("bw") ## map_nza + tm_style("classic") ## map_nza + tm_style("cobalt") ## map_nza + tm_style("col_blind") ## ----tmstyles, message=FALSE, fig.cap="Selected tmap styles.", fig.scap="Selected tmap styles.", echo=FALSE, fig.asp=0.56---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-tmstyles.R", print.eval = TRUE) ## A preview of predefined styles can be generated by executing `tmap_style_catalogue()`. ## This creates a folder called `tmap_style_previews` containing nine images. ## Each image, from `tm_style_albatross.png` to `tm_style_white.png`, shows a faceted map of the world in the corresponding style. ## Note: `tmap_style_catalogue()` takes some time to run. ## ----urban-facet, message=FALSE, fig.cap="Faceted map showing the top 30 largest urban agglomerations from 1970 to 2030 based on population projections by the United Nations.", fig.scap="Faceted map showing urban agglomerations.", fig.asp=0.5---- urb_1970_2030 = urban_agglomerations |> filter(year %in% c(1970, 1990, 2010, 2030)) tm_shape(world) + tm_polygons() + tm_shape(urb_1970_2030) + tm_symbols(col = "black", border.col = "white", size = "population_millions") + tm_facets(by = "year", nrow = 2, free.coords = FALSE) ## ----08-mapping-16---------------------------------------------------------------------------------- nz_region = st_bbox(c(xmin = 1340000, xmax = 1450000, ymin = 5130000, ymax = 5210000), crs = st_crs(nz_height)) |> st_as_sfc() ## ----08-mapping-17---------------------------------------------------------------------------------- nz_height_map = tm_shape(nz_elev, bbox = nz_region) + tm_raster(style = "cont", palette = "YlGn", legend.show = TRUE) + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 1) + tm_scale_bar(position = c("left", "bottom")) ## ----08-mapping-18---------------------------------------------------------------------------------- nz_map = tm_shape(nz) + tm_polygons() + tm_shape(nz_height) + tm_symbols(shape = 2, col = "red", size = 0.1) + tm_shape(nz_region) + tm_borders(lwd = 3) ## ----insetmap1, message=FALSE, fig.cap="Inset map providing a context - location of the central part of the Southern Alps in New Zealand.", fig.scap="Inset map providing a context."---- library(grid) nz_height_map print(nz_map, vp = viewport(0.8, 0.27, width = 0.5, height = 0.5)) ## ----08-mapping-19---------------------------------------------------------------------------------- us_states_map = tm_shape(us_states, projection = 2163) + tm_polygons() + tm_layout(frame = FALSE) ## ----08-mapping-20---------------------------------------------------------------------------------- hawaii_map = tm_shape(hawaii) + tm_polygons() + tm_layout(title = "Hawaii", frame = FALSE, bg.color = NA, title.position = c("LEFT", "BOTTOM")) alaska_map = tm_shape(alaska) + tm_polygons() + tm_layout(title = "Alaska", frame = FALSE, bg.color = NA) ## ----insetmap2, message=FALSE, fig.cap="Map of the United States."---------------------------------- us_states_map print(hawaii_map, vp = grid::viewport(0.35, 0.1, width = 0.2, height = 0.1)) print(alaska_map, vp = grid::viewport(0.15, 0.15, width = 0.3, height = 0.3)) ## ----urban-animated, message=FALSE, fig.cap="Animated map showing the top 30 largest urban agglomerations from 1950 to 2030 based on population projects by the United Nations. Animated version available online at: geocompr.robinlovelace.net.", fig.scap="Animated map showing the top 30 largest 'urban agglomerations'.", echo=FALSE---- if (knitr::is_latex_output()){ knitr::include_graphics("images/urban-animated.png") } else if (knitr::is_html_output()){ knitr::include_graphics("images/urban-animated.gif") } ## ----08-mapping-21, echo=FALSE, eval=FALSE---------------------------------------------------------- ## source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-urban-animation.R") ## ----08-mapping-22---------------------------------------------------------------------------------- urb_anim = tm_shape(world) + tm_polygons() + tm_shape(urban_agglomerations) + tm_dots(size = "population_millions") + tm_facets(along = "year", free.coords = FALSE) ## ----08-mapping-23, eval=FALSE---------------------------------------------------------------------- ## tmap_animation(urb_anim, filename = "urb_anim.gif", delay = 25) ## ----08-mapping-24, echo=FALSE, eval=FALSE---------------------------------------------------------- ## source("https://github.com/Robinlovelace/geocompr/raw/main/code/09-usboundaries.R") ## ----animus, echo=FALSE, message=FALSE, fig.cap="Animated map showing population growth, state formation and boundary changes in the United States, 1790-2010. Animated version available online at geocompr.robinlovelace.net.", fig.scap="Animated map showing boundary changes in the United States."---- u_animus_html = "https://user-images.githubusercontent.com/1825120/38543030-5794b6f0-3c9b-11e8-9da9-10ec1f3ea726.gif" u_animus_pdf = "images/animus.png" if (knitr::is_latex_output()){ knitr::include_graphics(u_animus_pdf) } else if (knitr::is_html_output()){ knitr::include_graphics(u_animus_html) } ## ----08-mapping-25, eval=FALSE---------------------------------------------------------------------- ## tmap_mode("view") ## map_nz ## ----tmview, message=FALSE, fig.cap="Interactive map of New Zealand created with tmap in view mode. Interactive version available online at: geocompr.robinlovelace.net.", fig.scap="Interactive map of New Zealand.", echo=FALSE---- if (knitr::is_latex_output()){ knitr::include_graphics("images/tmview-1.png") } else if (knitr::is_html_output()){ # tmap_mode("view") # m_tmview = map_nz # tmap_save(m_tmview, "tmview-1.html") # file.copy("tmview-1.html", "~/geocompr/geocompr.github.io/static/img/tmview-1.html") knitr::include_url("https://geocompr.github.io/img/tmview-1.html") } ## ----08-mapping-26, eval=FALSE---------------------------------------------------------------------- ## map_nz + tm_basemap(server = "OpenTopoMap") ## ----08-mapping-27, eval=FALSE---------------------------------------------------------------------- ## world_coffee = left_join(world, coffee_data, by = "name_long") ## facets = c("coffee_production_2016", "coffee_production_2017") ## tm_shape(world_coffee) + tm_polygons(facets) + ## tm_facets(nrow = 1, sync = TRUE) ## ----sync, message=FALSE, fig.cap="Faceted interactive maps of global coffee production in 2016 and 2017 in sync, demonstrating tmap's view mode in action.", fig.scap="Faceted interactive maps of global coffee production.", echo=FALSE---- knitr::include_graphics("images/interactive-facets.png") ## ----08-mapping-28---------------------------------------------------------------------------------- tmap_mode("plot") ## ----08-mapping-29, eval=FALSE---------------------------------------------------------------------- ## mapview::mapview(nz) ## ----mapview, message=FALSE, fig.cap="Illustration of mapview in action.", echo=FALSE--------------- knitr::include_graphics("images/mapview.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979522-e8277398-573e-11e8-8c55-d72c6bcc58a4.png") # mv = mapview::mapview(nz) # mv@map ## ----08-mapping-30, eval=FALSE---------------------------------------------------------------------- ## trails |> ## st_transform(st_crs(franconia)) |> ## st_intersection(franconia[franconia$district == "Oberfranken", ]) |> ## st_collection_extract("LINE") |> ## mapview(color = "red", lwd = 3, layer.name = "trails") + ## mapview(franconia, zcol = "district", burst = TRUE) + ## breweries ## ----mapview2, message=FALSE, fig.cap="Using mapview at the end of a sf-based pipe expression.", echo=FALSE, warning=FALSE---- knitr::include_graphics("images/mapview-example.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39979271-5f515256-573d-11e8-9ede-e472ca007d73.png") # commented out because interactive version not working # mv2 = trails |> # st_transform(st_crs(franconia)) |> # st_intersection(franconia[franconia$district == "Oberfranken", ]) |> # st_collection_extract("LINE") |> # mapview(color = "red", lwd = 3, layer.name = "trails") + # mapview(franconia, zcol = "district", burst = TRUE) + # breweries # mv2@map ## Note that the following block assumes the access token is stored in your R environment as `MAPBOX=your_unique_key`. ## This can be added with `edit_r_environ()` from the **usethis** package. ## https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv ## curl -i https://git.io -F "url=https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/3d-heatmap/heatmap-data.csv" \ ## -F "code=geocompr-mapdeck" ## ----08-mapping-33, eval=FALSE---------------------------------------------------------------------- ## library(mapdeck) ## set_token(Sys.getenv("MAPBOX")) ## crash_data = read.csv("https://git.io/geocompr-mapdeck") ## crash_data = na.omit(crash_data) ## ms = mapdeck_style("dark") ## mapdeck(style = ms, pitch = 45, location = c(0, 52), zoom = 4) |> ## add_grid(data = crash_data, lat = "lat", lon = "lng", cell_size = 1000, ## elevation_scale = 50, layer_id = "grid_layer", ## colour_range = viridisLite::plasma(6)) ## ----mapdeck, echo=FALSE, fig.cap="Map generated by mapdeck, representing road traffic casualties across the UK. Height of 1 km cells represents number of crashes.", fig.scap="Map generated by mapdeck."---- knitr::include_graphics("images/mapdeck-mini.png") ## ----08-mapping-35, eval=FALSE, echo=FALSE---------------------------------------------------------- ## library(mapdeck) ## set_token(Sys.getenv("MAPBOX")) ## df = read.csv("https://git.io/geocompr-mapdeck") ## ms = mapdeck_style('dark') ## mapdeck(style = ms, pitch = 45, location = c(0, 52), zoom = 4) |> ## # add_grid(data = df, lat = "lat", lon = "lng", cell_size = 1000, ## # elevation_scale = 50, layer_id = "grid_layer", ## # colour_range = viridisLite::plasma(5)) |> ## add_polygon(data = lnd, layer_id = "polygon_layer") ## ----08-mapping-36, eval=FALSE, echo=FALSE---------------------------------------------------------- ## library(sf) ## str(roads) ## mapdeck( ## , style = mapdeck_style('dark') ## , location = c(145, -37.8) ## , zoom = 10 ## ) |> ## add_path( ## data = roads ## , stroke_colour = "RIGHT_LOC" ## , layer_id = "path_layer" ## , tooltip = "ROAD_NAME" ## , auto_highlight = TRUE ## ) ## ----leaflet-code, echo=TRUE, eval=FALSE------------------------------------------------------------ ## pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes) ## leaflet(data = cycle_hire) |> ## addProviderTiles(providers$CartoDB.Positron) |> ## addCircles(col = ~pal(nbikes), opacity = 0.9) |> ## addPolygons(data = lnd, fill = FALSE) |> ## addLegend(pal = pal, values = ~nbikes) |> ## setView(lng = -0.1, 51.5, zoom = 12) |> ## addMiniMap() ## ----leaflet, message=FALSE, fig.cap="The leaflet package in action, showing cycle hire points in London. See interactive version [online](https://geocompr.github.io/img/leaflet.html).", fig.scap="The leaflet package in action.", echo=FALSE---- if (knitr::is_latex_output() | knitr::is_html_output()){ knitr::include_graphics("images/leaflet-1.png") } else { # pre-generated for https://github.com/ropensci/stplanr/issues/385 # pal = colorNumeric("RdYlBu", domain = cycle_hire$nbikes) # m = leaflet(data = cycle_hire) |> # addProviderTiles(providers$CartoDB.Positron) |> # addCircles(col = ~pal(nbikes), opacity = 0.9) |> # addPolygons(data = lnd, fill = FALSE) |> # addLegend(pal = pal, values = ~nbikes) |> # setView(lng = -0.1, 51.5, zoom = 12) |> # addMiniMap() # htmlwidgets::saveWidget(m, "leaflet.html") # browseURL("leaflet.html") # file.rename("leaflet.html", "~/geocompr/geocompr.github.io/static/img/leaflet.html") # abort old way of including - mixed content issues knitr::include_url("https://geocompr.github.io/img/leaflet.html") } ## In **shiny** apps these are often split into `ui.R` (short for user interface) and `server.R` files, naming conventions used by `shiny-server`, a server-side Linux application for serving shiny apps on public-facing websites. ## `shiny-server` also serves apps defined by a single `app.R` file in an 'app folder'. ## Learn more at: https://github.com/rstudio/shiny-server. ## ----08-mapping-37, eval=FALSE---------------------------------------------------------------------- ## library(shiny) # for shiny apps ## library(leaflet) # renderLeaflet function ## library(spData) # loads the world dataset ## ui = fluidPage( ## sliderInput(inputId = "life", "Life expectancy", 49, 84, value = 80), ## leafletOutput(outputId = "map") ## ) ## server = function(input, output) { ## output$map = renderLeaflet({ ## leaflet() |> ## # addProviderTiles("OpenStreetMap.BlackAndWhite") |> ## addPolygons(data = world[world$lifeExp < input$life, ])}) ## } ## shinyApp(ui, server) ## ----lifeApp, echo=FALSE, message=FALSE, fig.cap="Screenshot showing minimal example of a web mapping application created with shiny.", fig.scap="Minimal example of a web mapping application."---- # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/39690606-8f9400c8-51d2-11e8-84d7-f4a66a477d2a.png") knitr::include_graphics("images/shiny-app.png") ## There are a number of ways to run a **shiny** app. ## For RStudio users, the simplest way is probably to click on the 'Run App' button located in the top right of the source pane when an `app.R`, `ui.R` or `server.R` script is open. ## **shiny** apps can also be initiated by using `runApp()` with the first argument being the folder containing the app code and data: `runApp("CycleHireApp")` in this case (which assumes a folder named `CycleHireApp` containing the `app.R` script is in your working directory). ## You can also launch apps from a Unix command line with the command `Rscript -e 'shiny::runApp("CycleHireApp")'`. ## ----CycleHireApp-html, echo=FALSE, message=FALSE, fig.cap="Hire a cycle App, a simple web mapping application for finding the closest cycle hiring station based on your location and requirement of cycles. Interactive version available online at geocompr.robinlovelace.net.",fig.scap="Cycle Hire App, a simple web mapping application.", eval=knitr::is_html_output(), out.width="600"---- ## ## knitr::include_app("https://shiny.robinlovelace.net/CycleHireApp/") ## ----CycleHireApp-latex, echo=FALSE, message=FALSE, fig.cap="Hire a cycle App, a simple web mapping application for finding the closest cycle hiring station based on your location and requirement of cycles. Interactive version available online at geocompr.robinlovelace.net.", fig.scap="coffeeApp, a simple web mapping application.", eval=knitr::is_latex_output()---- ## knitr::include_graphics("images/CycleHireApp-2.png") ## ----nz-plot, message=FALSE, fig.cap="Map of New Zealand created with plot(). The legend to the right refers to elevation (1000 m above sea level).", fig.scap="Map of New Zealand created with plot()."---- g = st_graticule(nz, lon = c(170, 175), lat = c(-45, -40, -35)) plot(nz_water, graticule = g, axes = TRUE, col = "blue") raster::plot(nz_elev / 1000, add = TRUE) plot(st_geometry(nz), add = TRUE) ## ----nz-gg, out.width="50%", message=FALSE, fig.cap="Map of New Zealand created with ggplot2."------ library(ggplot2) g1 = ggplot() + geom_sf(data = nz, aes(fill = Median_income)) + geom_sf(data = nz_height) + scale_x_continuous(breaks = c(170, 175)) g1 ## ----08-mapping-38, eval=FALSE, echo=FALSE---------------------------------------------------------- ## plotly::ggplotly(g1) ## ----map-gpkg, echo=FALSE, message=FALSE, warning=FALSE--------------------------------------------- gpkg_df = readr::read_csv("extdata/generic_map_pkgs.csv") map_gpkg_df = select(gpkg_df, Package = package, Title = title) map_gpkg_df$Title[map_gpkg_df$Package == "leaflet"] = "Create Interactive Web Maps with Leaflet" knitr::kable(map_gpkg_df, caption = "Selected general-purpose mapping packages.", caption.short = "Selected general-purpose mapping packages.", booktabs = TRUE) |> kableExtra::column_spec(2, width = "9cm") ## ----map-spkg, echo=FALSE, message=FALSE------------------------------------------------------------ spkg_df = readr::read_csv("extdata/specific_map_pkgs.csv") map_spkg_df = select(spkg_df, Package = package, Title = title) knitr::kable(map_spkg_df, caption = paste("Selected specific-purpose mapping packages,", "with associated metrics."), caption.short = "Selected specific-purpose mapping packages.", booktabs = TRUE) ## ----08-mapping-39, fig.show='hide', message=FALSE-------------------------------------------------- library(cartogram) nz_carto = cartogram_cont(nz, "Median_income", itermax = 5) tm_shape(nz_carto) + tm_polygons("Median_income") ## ----cartomap1, echo=FALSE, message=FALSE, fig.cap="Comparison of standard map (left) and continuous area cartogram (right).", fig.scap="Comparison of standard map and continuous area cartogram."---- carto_map1 = tm_shape(nz) + tm_polygons("Median_income", title = "Median income (NZD)", palette = "Greens") carto_map2 = tm_shape(nz_carto) + tm_polygons("Median_income", title = "Median income (NZD)", palette = "Greens") tmap_arrange(carto_map1, carto_map2) ## ----08-mapping-40, fig.show='hide', message=FALSE-------------------------------------------------- us_states2163 = st_transform(us_states, 2163) us_states2163_ncont = cartogram_ncont(us_states2163, "total_pop_15") us_states2163_dorling = cartogram_dorling(us_states2163, "total_pop_15") ## ----cartomap2, echo=FALSE, message=FALSE, fig.cap="Comparison of non-continuous area cartogram (left) and Dorling cartogram (right).", fig.scap="Comparison of cartograms.", fig.asp=0.32---- carto_map3 = tm_shape(us_states2163_ncont) + tm_polygons("total_pop_15", title = "Total population", palette = "BuPu") + tm_layout(legend.show = FALSE) carto_map4 = tm_shape(us_states2163_dorling) + tm_polygons("total_pop_15", title = "Total population", palette = "BuPu") + tm_layout(legend.show = FALSE) carto_map_34legend = tm_shape(us_states2163_dorling) + tm_polygons("total_pop_15", title = "Total population", palette = "BuPu") + tm_layout(legend.only = TRUE) tmap_arrange(carto_map3, carto_map4, carto_map_34legend, ncol = 3) ## ----08-mapping-41, warning=FALSE------------------------------------------------------------------- africa = world |> filter(continent == "Africa", !is.na(iso_a2)) |> left_join(worldbank_df, by = "iso_a2") |> select(name, subregion, gdpPercap, HDI, pop_growth) |> st_transform("+proj=aea +lat_1=20 +lat_2=-23 +lat_0=0 +lon_0=25") ## ----08-mapping-42, results='hide'------------------------------------------------------------------ zion = st_read((system.file("vector/zion.gpkg", package = "spDataLarge"))) data(nlcd, package = "spDataLarge") ================================================ FILE: code/chapters/10-gis.R ================================================ ## ----09-gis-1, message=FALSE------------------------------------------------------------------------ library(sf) library(terra) ## ----09-gis-1-2, message=FALSE, eval=FALSE---------------------------------------------------------- ## # remotes::install_github("r-spatial/qgisprocess") ## library(qgisprocess) ## library(Rsagacmd) ## library(rgrass) ## library(rstac) ## library(gdalcubes) ## A command line interface is a means of interacting with computer programs in which the user issues commands via successive lines of text (command lines). ## `bash` in Linux and `PowerShell` in Windows are its common examples. ## CLIs can be augmented with IDEs such as RStudio for R, which provides code auto-completion and other features to improve the user experience. ## ----gis-comp, echo=FALSE, message=FALSE------------------------------------------------------------ library(dplyr) d = tibble("GIS" = c("QGIS", "SAGA", "GRASS"), "First release" = c("2002", "2004", "1982"), "No. functions" = c(">1000", ">600", ">500"), "Support" = c("hybrid", "hybrid", "hybrid")) knitr::kable(x = d, caption = paste("Comparison between three open-source GIS.", "Hybrid refers to the support of vector and", "raster operations."), caption.short = "Comparison between three open-source GIS.", booktabs = TRUE) #|> # kableExtra::add_footnote(label = "Comparing downloads of different providers is rather difficult (see http://spatialgalaxy.net/2011/12/19/qgis-users-around-the-world), and here also useless since every Windows QGIS download automatically also downloads SAGA and GRASS.", notation = "alphabet") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## library(qgisprocess) ## #> Using 'qgis_process' at 'qgis_process'. ## #> QGIS version: 3.20.3-Odense ## #> ... ## ----providers, eval=FALSE-------------------------------------------------------------------------- ## qgis_providers() ## #> # A tibble: 6 × 2 ## #> provider provider_title ## #> ## #> 1 3d QGIS (3D) ## #> 2 gdal GDAL ## #> 3 grass7 GRASS ## #> 4 native QGIS (native c++) ## #> 5 qgis QGIS ## #> 6 saga SAGA ## ----09-gis-4--------------------------------------------------------------------------------------- data("incongruent", "aggregating_zones", package = "spData") incongr_wgs = st_transform(incongruent, "EPSG:4326") aggzone_wgs = st_transform(aggregating_zones, "EPSG:4326") ## ----uniondata, echo=FALSE, fig.cap="Illustration of two areal units: incongruent (black lines) and aggregating zones (red borders). "---- library(tmap) tm_shape(incongr_wgs) + tm_polygons(border.col = "gray5") + tm_shape(aggzone_wgs) + tm_borders(alpha = 0.5, col = "red") + tm_add_legend(type = "line", labels = c("incongr_wgs", "aggzone_wgs"), col = c("gray5", "red"), lwd = 3) + tm_scale_bar(position = c("left", "bottom"), breaks = c(0, 0.5, 1)) + tm_layout(frame = FALSE, legend.text.size = 1) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## qgis_algo = qgis_algorithms() ## ---- eval=FALSE------------------------------------------------------------------------------------ ## grep("union", qgis_algo$algorithm, value = TRUE) ## #> [1] "native:union" "saga:fuzzyunionor" "saga:polygonunion" ## ----09-gis-6, eval=FALSE--------------------------------------------------------------------------- ## alg = "native:union" ## qgis_show_help(alg) ## ----09-gis-7, eval=FALSE--------------------------------------------------------------------------- ## union = qgis_run_algorithm(alg, INPUT = incongr_wgs, OVERLAY = aggzone_wgs) ## union ## ---- eval=FALSE------------------------------------------------------------------------------------ ## union_sf = st_as_sf(union) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## grep("clean", qgis_algo$algorithm, value = TRUE) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## qgis_show_help("grass7:v.clean") ## ----09-gis-7c, eval=FALSE-------------------------------------------------------------------------- ## clean = qgis_run_algorithm("grass7:v.clean", input = union_sf, ## tool = 10, threshold = 25000) ## clean_sf = st_as_sf(clean) ## ----sliver, echo=FALSE, fig.cap="Sliver polygons colored in red (left panel). Cleaned polygons (right panel)."---- knitr::include_graphics("images/10-sliver.png") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## library(qgisprocess) ## library(terra) ## dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## dem_slope = terrain(dem, unit = "radians") ## dem_aspect = terrain(dem, v = "aspect", unit = "radians") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## qgis_algo = qgis_algorithms() ## grep("wetness", qgis_algo$algorithm, value = TRUE) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## qgis_show_help("saga:sagawetnessindex") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## dem_wetness = qgis_run_algorithm("saga:sagawetnessindex", DEM = dem) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## dem_wetness_twi = qgis_as_terra(dem_wetness$TWI) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## grep("geomorphon", qgis_algo$algorithm, value = TRUE) ## qgis_show_help("grass7:r.geomorphon") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## dem_geomorph = qgis_run_algorithm("grass7:r.geomorphon", elevation = dem, ## `-m` = TRUE, search = 120) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## dem_geomorph_terra = qgis_as_terra(dem_geomorph$forms) ## ----qgis-raster-map, echo=FALSE, fig.cap="Topographic wetness index (TWI, left panel) and geomorphons (right panel) derived for the Mongón study area."---- knitr::include_graphics("images/10-qgis-raster-map.png") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## library(Rsagacmd) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## saga = saga_gis(raster_backend = "terra", vector_backend = "sf") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## sg = saga$imagery_segmentation$seed_generation ## ---- eval=FALSE------------------------------------------------------------------------------------ ## ndvi_seeds = sg(ndvi, band_width = 2) ## plot(ndvi_seeds$seed_grid) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## srg = saga$imagery_segmentation$seeded_region_growing ## ndvi_srg = srg(ndvi_seeds$seed_grid, ndvi, method = 1) ## plot(ndvi_srg$segments) ## ---- eval=FALSE------------------------------------------------------------------------------------ ## ndvi_segments = as.polygons(ndvi_srg$segments) |> ## st_as_sf() ## ----sagasegments, echo=FALSE, fig.cap="Normalized difference vegetation index (NDVI, left panel) and NDVi-based segments derived using t he seeded region growing algorithm for the Mongón study area."---- knitr::include_graphics("images/10-saga-segments.png") ## ----09-gis-24-------------------------------------------------------------------------------------- data("cycle_hire", package = "spData") points = cycle_hire[1:25, ] ## ----09-gis-25, eval=FALSE-------------------------------------------------------------------------- ## library(osmdata) ## b_box = st_bbox(points) ## london_streets = opq(b_box) |> ## add_osm_feature(key = "highway") |> ## osmdata_sf() ## london_streets = london_streets[["osm_lines"]] ## london_streets = select(london_streets, osm_id) ## ----09-gis-30, eval=FALSE-------------------------------------------------------------------------- ## library(rgrass) ## link2GI::linkGRASS(london_streets, ver_select = TRUE) ## ----09-gis-31, eval=FALSE-------------------------------------------------------------------------- ## write_VECT(terra::vect(london_streets), vname = "london_streets") ## write_VECT(terra::vect(points[, 1]), vname = "points") ## ----09-gis-32, eval=FALSE-------------------------------------------------------------------------- ## execGRASS(cmd = "v.clean", input = "london_streets", output = "streets_clean", ## tool = "break", flags = "overwrite") ## To learn about the possible arguments and flags of the GRASS GIS modules you can you the `help` flag. ## For example, try `execGRASS("g.region", flags = "help")`. ## ----09-gis-32b, eval=FALSE------------------------------------------------------------------------- ## execGRASS(cmd = "v.net", input = "streets_clean", output = "streets_points_con", ## points = "points", operation = "connect", threshold = 0.001, ## flags = c("overwrite", "c")) ## ----09-gis-33, eval=FALSE-------------------------------------------------------------------------- ## execGRASS(cmd = "v.net.salesman", input = "streets_points_con", ## output = "shortest_route", center_cats = paste0("1-", nrow(points)), ## flags = "overwrite") ## ----09-gis-34, eval=FALSE-------------------------------------------------------------------------- ## route = read_VECT("shortest_route") |> ## st_as_sf() |> ## st_geometry() ## mapview::mapview(route) + points ## ----grass-mapview, fig.cap="Shortest route (blue line) between 24 cycle hire stations (blue dots) on the OSM street network of London.", fig.scap="Shortest route between 24 cycle hire stations.", echo=FALSE, out.width="80%"---- knitr::include_graphics("images/10_shortest_route.png") ## ----09-gis-35, eval=FALSE, echo=FALSE-------------------------------------------------------------- ## library(mapview) ## m_1 = mapview(route) + points ## mapview::mapshot(m_1, ## file = file.path(getwd(), "images/09_shortest_route.png"), ## remove_controls = c("homeButton", "layersControl", ## "zoomControl")) ## ----09-gis-27, eval=FALSE-------------------------------------------------------------------------- ## library(link2GI) ## link = findGRASS() ## ---- eval=FALSE------------------------------------------------------------------------------------ ## library(rgrass) ## grass_path = link$instDir[[1]] ## initGRASS(gisBase = grass_path, gisDbase = tempdir(), ## location = "london", mapset = "PERMANENT", override = TRUE) ## ----09-gis-29, eval=FALSE-------------------------------------------------------------------------- ## execGRASS("g.proj", flags = c("c", "quiet"), srid = "EPSG:4326") ## b_box = st_bbox(london_streets) ## execGRASS("g.region", flags = c("quiet"), ## n = as.character(b_box["ymax"]), s = as.character(b_box["ymin"]), ## e = as.character(b_box["xmax"]), w = as.character(b_box["xmin"]), ## res = "1") ## ---- eval=FALSE------------------------------------------------------------------------------------ ## link2GI::linkGDAL() ## ----09-gis-36, eval=FALSE, message=FALSE----------------------------------------------------------- ## our_filepath = system.file("shapes/world.gpkg", package = "spData") ## cmd = paste("ogrinfo -al -so", our_filepath) ## system(cmd) ## #> INFO: Open of `.../spData/shapes/world.gpkg' ## #> using driver `GPKG' successful. ## #> ## #> Layer name: world ## #> Geometry: Multi Polygon ## #> Feature Count: 177 ## #> Extent: (-180.000000, -89.900000) - (179.999990, 83.645130) ## #> Layer SRS WKT: ## #> ... ## ----09-gis-37, eval=FALSE-------------------------------------------------------------------------- ## library(RPostgreSQL) ## conn = dbConnect(drv = PostgreSQL(), ## dbname = "rtafdf_zljbqm", host = "db.qgiscloud.com", ## port = "5432", user = "rtafdf_zljbqm", password = "d3290ead") ## ----09-gis-38, eval=FALSE-------------------------------------------------------------------------- ## dbListTables(conn) ## #> [1] "spatial_ref_sys" "topology" "layer" "restaurants" ## #> [5] "highways" ## ----09-gis-39, eval=FALSE-------------------------------------------------------------------------- ## dbListFields(conn, "highways") ## #> [1] "qc_id" "wkb_geometry" "gid" "feature" ## #> [5] "name" "state" ## ----09-gis-40, eval=FALSE-------------------------------------------------------------------------- ## query = paste( ## "SELECT *", ## "FROM highways", ## "WHERE name = 'US Route 1' AND state = 'MD';") ## us_route = read_sf(conn, query = query, geom = "wkb_geometry") ## ----09-gis-41, eval=FALSE-------------------------------------------------------------------------- ## query = paste( ## "SELECT ST_Union(ST_Buffer(wkb_geometry, 35000))::geometry", ## "FROM highways", ## "WHERE name = 'US Route 1' AND state = 'MD';") ## buf = read_sf(conn, query = query) ## ----09-gis-42, eval=FALSE, warning=FALSE----------------------------------------------------------- ## query = paste( ## "SELECT *", ## "FROM restaurants r", ## "WHERE EXISTS (", ## "SELECT gid", ## "FROM highways", ## "WHERE", ## "ST_DWithin(r.wkb_geometry, wkb_geometry, 35000) AND", ## "name = 'US Route 1' AND", ## "state = 'MD' AND", ## "r.franchise = 'HDE');" ## ) ## hardees = read_sf(conn, query = query) ## ----09-gis-43, eval=FALSE-------------------------------------------------------------------------- ## RPostgreSQL::postgresqlCloseConnection(conn) ## ----09-gis-44, echo=FALSE-------------------------------------------------------------------------- load("extdata/postgis_data.Rdata") ## ----postgis, echo=FALSE, fig.cap="Visualization of the output of previous PostGIS commands showing the highway (black line), a buffer (light yellow) and four restaurants (red points) within the buffer.", fig.scap="Visualization of the output of previous PostGIS commands."---- # plot the results of the queries library(tmap) tm_shape(buf) + tm_polygons(col = "#FFFDD0", border.alpha = 0.3) + tm_shape(us_route) + tm_lines(col = "black", lwd = 3) + tm_shape(hardees) + tm_symbols(col = "#F10C26") + tm_add_legend(type = "line", col = "black", lwd = 3, labels = "The US Route 1 highway") + tm_add_legend(type = "fill", col = "#FFFDD0", border.alpha = 0.3, label = "35km buffer") + tm_add_legend(type = "symbol", col = "#F10C26", labels = "Restaurants") + tm_layout(frame = FALSE, legend.outside = TRUE, legend.outside.size = 0.3) ## ----09-stac-example, eval = FALSE------------------------------------------------------------------ ## library(rstac) ## # Connect to the STAC-API endpoint for Sentinel-2 data ## # and search for images intersecting our AOI ## s = stac("https://earth-search.aws.element84.com/v0") ## items = s |> ## stac_search(collections = "sentinel-s2-l2a-cogs", ## bbox = c(7.1, 51.8, 7.2, 52.8), ## datetime = "2020-01-01/2020-12-31") |> ## post_request() |> items_fetch() ## ----09-gdalcubes-example, eval = FALSE------------------------------------------------------------- ## library(gdalcubes) ## # Filter images from STAC response by cloud cover ## # and create an image collection object ## collection = stac_image_collection(items$features, ## property_filter = function(x) {x[["eo:cloud_cover"]] < 10}) ## # Define extent, resolution (250m, daily) and CRS of the target data cube ## v = cube_view(srs = "EPSG:3857", extent = collection, dx = 250, dy = 250, ## dt = "P1D") # "P1D" is an ISO 8601 duration string ## # Create and process the data cube ## cube = raster_cube(collection, v) |> ## select_bands(c("B04", "B08")) |> ## apply_pixel("(B08-B04)/(B08+B04)", "NDVI") |> ## reduce_time("max(NDVI)") ## # gdalcubes_options(parallel = 8) ## # plot(cube, zlim = c(0, 1)) ## ----09-openeo-example, eval=FALSE------------------------------------------------------------------ ## library(openeo) ## con = connect(host = "https://openeo.cloud") ## p = processes() # load available processes ## collections = list_collections() # load available collections ## formats = list_file_formats() # load available output formats ## # Load Sentinel-2 collection ## s2 = p$load_collection(id = "SENTINEL2_L2A", ## spatial_extent = list(west = 7.5, east = 8.5, ## north = 51.1, south = 50.1), ## temporal_extent = list("2021-01-01", "2021-01-31"), ## bands = list("B04","B08")) ## # Compute NDVI vegetation index ## compute_ndvi = p$reduce_dimension(data = s2, dimension = "bands", ## reducer = function(data, context) { ## (data[2] - data[1]) / (data[2] + data[1]) ## }) ## # Compute maximum over time ## reduce_max = p$reduce_dimension(data = compute_ndvi, dimension = "t", ## reducer = function(x, y) {max(x)}) ## # Export as GeoTIFF ## result = p$save_result(reduce_max, formats$output$GTiff) ## # Login, see https://docs.openeo.cloud/getting-started/r/#authentication ## login(login_type = "oidc", provider = "egi", ## config = list(client_id = "...", secret = "...")) ## # Execute processes ## compute_result(graph = result, output_file = tempfile(fileext = ".tif")) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_10-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/11-algorithms.R ================================================ ## ----10-algorithms-1-------------------------------------------------------------------------------- source("code/11-hello.R") ## ----codecheck, echo=FALSE, fig.cap="Code checking in RStudio. This example, from the script 11-centroid-alg.R, highlights an unclosed curly bracket on line 19.", fig.scap="Illustration of 'code checking' in RStudio."---- knitr::include_graphics("images/codecheck.png") ## A useful tool for reproducibility is the **reprex** package. ## Its main function `reprex()` tests lines of R code to check if they are reproducible, and provides markdown output to facilitate communication on sites such as GitHub. ## See the web page reprex.tidyverse.org for details. ## ----10-algorithms-2, eval=FALSE-------------------------------------------------------------------- ## poly_mat = cbind( ## x = c(0, 0, 9, 9, 0), ## y = c(0, 9, 9, 0, 0) ## ) ## source("https://raw.githubusercontent.com/Robinlovelace/geocompr/master/code/11-centroid-alg.R") ## ----10-algorithms-3, echo=FALSE-------------------------------------------------------------------- poly_mat = cbind( x = c(0, 9, 9, 0, 0), y = c(0, 0, 9, 9, 0) ) if (curl::has_internet()) { source("https://raw.githubusercontent.com/Robinlovelace/geocompr/master/code/11-centroid-alg.R") } else { source("code/11-centroid-setup.R") } ## ----centroid-setup, echo=FALSE, eval=FALSE--------------------------------------------------------- ## # show where the data came from: ## source("code/11-centroid-setup.R") ## ----10-algorithms-4-------------------------------------------------------------------------------- # generate a simple matrix representation of a polygon: x_coords = c(10, 20, 12, 0, 0, 10) y_coords = c(0, 15, 20, 10, 0, 0) poly_mat = cbind(x_coords, y_coords) ## ----10-algorithms-5-------------------------------------------------------------------------------- # create a point representing the origin: Origin = poly_mat[1, ] # create 'triangle matrix': T1 = rbind(Origin, poly_mat[2:3, ], Origin) # find centroid (drop = FALSE preserves classes, resulting in a matrix): C1 = (T1[1, , drop = FALSE] + T1[2, , drop = FALSE] + T1[3, , drop = FALSE]) / 3 ## ----polymat, echo=FALSE, fig.cap="Polygon centroid calculation problem.", fig.height="100", warning=FALSE---- # initial plot: can probably delete this: old_par = par(pty = "s") plot(poly_mat) lines(poly_mat) lines(T1, col = "blue", lwd = 5) text(x = C1[ ,1], y = C1[, 2], "C1") par(old_par) ## ----10-algorithms-6-------------------------------------------------------------------------------- # calculate the area of the triangle represented by matrix T1: abs(T1[1, 1] * (T1[2, 2] - T1[3, 2]) + T1[2, 1] * (T1[3, 2] - T1[1, 2]) + T1[3, 1] * (T1[1, 2] - T1[2, 2]) ) / 2 ## ----10-algorithms-7-------------------------------------------------------------------------------- i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) { rbind(Origin, poly_mat[x:(x + 1), ], Origin) }) C_list = lapply(T_all, function(x) (x[1, ] + x[2, ] + x[3, ]) / 3) C = do.call(rbind, C_list) A = vapply(T_all, function(x) { abs(x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 }, FUN.VALUE = double(1)) ## ----polycent, fig.cap="Illustration of iterative centroid algorithm with triangles. The X represents the area-weighted centroid in iterations 2 and 3.", fig.scap="Illustration of iterative centroid algorithm with triangles.", echo=FALSE, fig.asp=0.3---- # idea: show animated version on web version source("code/11-polycent.R") ## ----10-algorithms-8-------------------------------------------------------------------------------- source("code/11-centroid-alg.R") ## ----10-algorithms-9-------------------------------------------------------------------------------- t_centroid = function(x) { (x[1, ] + x[2, ] + x[3, ]) / 3 } ## ----10-algorithms-10, eval=FALSE, echo=FALSE------------------------------------------------------- ## body(t_centroid) ## formals(t_centroid) ## environment(t_centroid) ## ----10-algorithms-11------------------------------------------------------------------------------- t_centroid(T1) ## ----10-algorithms-12------------------------------------------------------------------------------- t_area = function(x) { abs( x[1, 1] * (x[2, 2] - x[3, 2]) + x[2, 1] * (x[3, 2] - x[1, 2]) + x[3, 1] * (x[1, 2] - x[2, 2]) ) / 2 } ## ----10-algorithms-13------------------------------------------------------------------------------- t_area(T1) ## ----10-algorithms-14------------------------------------------------------------------------------- t_new = cbind(x = c(0, 3, 3, 0), y = c(0, 0, 1, 0)) t_area(t_new) ## ----10-algorithms-15------------------------------------------------------------------------------- poly_centroid = function(poly_mat) { Origin = poly_mat[1, ] # create a point representing the origin i = 2:(nrow(poly_mat) - 2) T_all = lapply(i, function(x) { rbind(Origin, poly_mat[x:(x + 1), ], Origin) }) C_list = lapply(T_all, t_centroid) C = do.call(rbind, C_list) A = vapply(T_all, t_area, FUN.VALUE = double(1)) c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) } ## ----10-algorithms-16, echo=FALSE, eval=FALSE------------------------------------------------------- ## # a slightly more complex version of the function with output set ## poly_centroid = function(poly_mat, output = "matrix") { ## Origin = poly_mat[1, ] # create a point representing the origin ## i = 2:(nrow(poly_mat) - 2) ## T_all = T_all = lapply(i, function(x) { ## rbind(Origin, poly_mat[x:(x + 1), ], Origin) ## }) ## C_list = lapply(T_all, t_centroid) ## C = do.call(rbind, C_list) ## A = vapply(T_all, t_area, FUN.VALUE = double(1)) ## centroid_coords = c(weighted.mean(C[, 1], A), weighted.mean(C[, 2], A)) ## if (output == "matrix") { ## return(centroid_coords) ## } else if (output == "area") ## return(sum(A)) ## } ## ----10-algorithms-17------------------------------------------------------------------------------- poly_centroid(poly_mat) ## ----10-algorithms-18------------------------------------------------------------------------------- poly_centroid_sfg = function(x) { centroid_coords = poly_centroid(x) sf::st_point(centroid_coords) } ## ----10-algorithms-19------------------------------------------------------------------------------- poly_sfc = sf::st_polygon(list(poly_mat)) identical(poly_centroid_sfg(poly_mat), sf::st_centroid(poly_sfc)) ## ----10-algorithms-20, eval=FALSE, echo=FALSE------------------------------------------------------- ## poly_sfc = sf::st_polygon(list(poly_mat)) ## sf::st_area(poly_sfc) ## sf::st_centroid(poly_sfc) ## ----10-algorithms-21, eval=FALSE, echo=FALSE------------------------------------------------------- ## poly_centroid_sf = function(x) { ## stopifnot(is(x, "sf")) ## xcoords = sf::st_coordinates(x) ## centroid_coords = poly_centroid(xcoords) ## centroid_sf = sf::st_sf(geometry = sf::st_sfc(sf::st_point(centroid_coords))) ## centroid_sf ## } ## poly_centroid_sf(sf::st_sf(sf::st_sfc(poly_sfc))) ## poly_centroid_sf(poly_sfc) ## poly_centroid_sf(poly_mat) ================================================ FILE: code/chapters/12-spatial-cv.R ================================================ ## ----12-spatial-cv-1, message=FALSE----------------------------------------------------------------- library(dplyr) library(future) library(lgr) library(mlr3) library(mlr3learners) library(mlr3extralearners) library(mlr3spatiotempcv) library(mlr3tuning) library(mlr3viz) library(progressr) library(sf) library(terra) ## ----12-spatial-cv-2-------------------------------------------------------------------------------- data("lsl", "study_mask", package = "spDataLarge") ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) ## ----lsl-map, echo=FALSE, out.width="70%", fig.cap="Landslide initiation points (red) and points unaffected by landsliding (blue) in Southern Ecuador.", fig.scap="Landslide initiation points."---- # library(tmap) # data("lsl", package = "spDataLarge") # ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = "EPSG:32717") # hs = terra::shade(slope = ta$slope * pi / 180, # terra::terrain(ta$elev, v = "aspect", unit = "radians")) # # so far tmaptools does not support terra objects # # bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.0001, 1), # ylim = c(-0.0001, 1), relative = TRUE) # # map = tm_shape(hs, bbox = bbx) + # tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, # labels.rot = c(0, 90), lines = FALSE) + # tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + # tm_shape(ta$elev) + # tm_raster(alpha = 0.5, palette = terrain.colors(10), legend.show = FALSE) + # tm_shape(lsl_sf) + # tm_bubbles("lslpts", size = 0.2, palette = "-RdYlBu", # title.col = "Landslide: ") + # tm_layout(inner.margins = 0) + # tm_legend(bg.color = "white") # tmap::tmap_save(map, filename = "images/lsl-map-1.png", width = 11, # height = 11, units = "cm") knitr::include_graphics("images/lsl-map-1.png") ## ----lslsummary, echo=FALSE, warning=FALSE---------------------------------------------------------- lsl_table = lsl |> mutate(across(.cols = -any_of(c("x", "y", "lslpts")), ~signif(., 2))) knitr::kable(lsl_table[c(1, 2, 350), ], caption = "Structure of the lsl dataset.", caption.short = "`lsl` dataset.", booktabs = TRUE) |> kableExtra::kable_styling(latex_options = "scale_down") ## ----12-spatial-cv-6-------------------------------------------------------------------------------- fit = glm(lslpts ~ slope + cplan + cprof + elev + log10_carea, family = binomial(), data = lsl) ## ----12-spatial-cv-7-------------------------------------------------------------------------------- class(fit) fit ## ----12-spatial-cv-8-------------------------------------------------------------------------------- pred_glm = predict(object = fit, type = "response") head(pred_glm) ## ----12-spatial-cv-9, eval=FALSE-------------------------------------------------------------------- ## # making the prediction ## pred = terra::predict(ta, model = fit, type = "response") ## ----lsl-susc, echo=FALSE, out.width="70%",fig.cap="Spatial prediction of landslide susceptibility using a GLM.", fig.scap = "Spatial prediction of landslide susceptibility.", warning=FALSE---- # # attach study mask for the natural part of the study area # data("lsl", "study_mask", package = "spDataLarge") # ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) # study_mask = terra::vect(study_mask) # lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) # hs = terra::shade(ta$slope * pi / 180, # terra::terrain(ta$elev, v = "aspect", unit = "radians")) # bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.0001, 1), # ylim = c(-0.0001, 1), relative = TRUE) # # map = tm_shape(hs, bbox = bbx) + # tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, # labels.rot = c(0, 90), lines = FALSE) + # tm_raster(palette = "white", legend.show = FALSE) + # # hillshade # tm_shape(terra::mask(hs, study_mask), bbox = bbx) + # tm_raster(palette = gray(0:100 / 100), n = 100, # legend.show = FALSE) + # # prediction raster # tm_shape(terra::mask(pred, study_mask)) + # tm_raster(alpha = 0.5, palette = "Reds", n = 6, legend.show = TRUE, # title = "Susceptibility") + # tm_layout(legend.position = c("left", "bottom"), # legend.title.size = 0.9, # inner.margins = 0) # tmap::tmap_save(map, filename = "images/lsl-susc-1.png", width = 11, # height = 11, units = "cm") knitr::include_graphics("images/lsl-susc-1.png") ## ----12-spatial-cv-10, message=FALSE, eval=FALSE---------------------------------------------------- ## pROC::auc(pROC::roc(lsl$lslpts, fitted(fit))) ## #> Area under the curve: 0.8216 ## ----partitioning, fig.cap="Spatial visualization of selected test and training observations for cross-validation of one repetition. Random (upper row) and spatial partitioning (lower row).", echo=FALSE, fig.scap="Spatial visualization of selected test and training observations."---- knitr::include_graphics("images/13_partitioning.png") ## ----building-blocks, echo=FALSE, fig.height=4, fig.width=4, fig.cap="Basic building blocks of the mlr3 package. Source: @bischl_applied_2024. (Permission to reuse this figure was kindly granted.)", fig.scap="Basic building blocks of the mlr3 package."---- knitr::include_graphics("images/13_ml_abstraction_crop.png") ## ----12-spatial-cv-11, eval=FALSE------------------------------------------------------------------- ## # create task ## task = mlr3spatiotempcv::TaskClassifST$new( ## id = "ecuador_lsl", ## backend = mlr3::as_data_backend(lsl), ## target = "lslpts", ## positive = "TRUE", ## extra_args = list( ## coordinate_names = c("x", "y"), ## coords_as_features = FALSE, ## crs = "EPSG:32717") ## ) ## ----autoplot, eval=FALSE--------------------------------------------------------------------------- ## # plot response against each predictor ## mlr3viz::autoplot(task, type = "duo") ## # plot all variables against each other ## mlr3viz::autoplot(task, type = "pairs") ## ----12-spatial-cv-12, eval=FALSE------------------------------------------------------------------- ## mlr3extralearners::list_mlr3learners( ## filter = list(class = "classif", properties = "twoclass"), ## select = c("id", "mlr3_package", "required_packages")) |> ## head() ## ----lrns, echo=FALSE------------------------------------------------------------------------------- lrns_df = mlr3extralearners::list_mlr3learners( filter = list(class = "classif", properties = "twoclass"), select = c("id", "mlr3_package", "required_packages")) |> head() # dput(lrns_df) # lrns_df = structure(list(Class = c("classif.adaboostm1", "classif.binomial", # "classif.featureless", "classif.fnn", "classif.gausspr", "classif.IBk" # ), Name = c("ada Boosting M1", "Binomial Regression", "Featureless classifier", # "Fast k-Nearest Neighbour", "Gaussian Processes", "k-Nearest Neighbours" # ), `Short name` = c("adaboostm1", "binomial", "featureless", # "fnn", "gausspr", "ibk"), Package = c("RWeka", "stats", "mlr", # "FNN", "kernlab", "RWeka")), row.names = c(NA, 6L), class = "data.frame") knitr::kable(lrns_df, caption = paste("Sample of available learners for binomial", "tasks in the mlr3 package."), caption.short = "Sample of available learners.", booktabs = TRUE) ## ----12-spatial-cv-13, eval=FALSE------------------------------------------------------------------- ## learner = mlr3::lrn("classif.log_reg", predict_type = "prob") ## ----12-spatial-cv-14, eval=FALSE------------------------------------------------------------------- ## learner$help() ## ----12-spatial-cv-15, eval=FALSE------------------------------------------------------------------- ## learner$train(task) ## learner$model ## ----12-spatial-cv-16, eval=FALSE, echo=FALSE------------------------------------------------------- ## learner$model$formula ## task$data() ## learner$model ## ----12-spatial-cv-17, eval=FALSE------------------------------------------------------------------- ## fit = glm(lslpts ~ ., family = binomial(link = "logit"), ## data = select(lsl, -x, -y)) ## identical(fit$coefficients, learner$model$coefficients) ## ----12-spatial-cv-18, eval=FALSE------------------------------------------------------------------- ## resampling = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) ## ----12-spatial-cv-19, eval=FALSE------------------------------------------------------------------- ## # reduce verbosity ## lgr::get_logger("mlr3")$set_threshold("warn") ## # run spatial cross-validation and save it to resample result glm (rr_glm) ## rr_spcv_glm = mlr3::resample(task = task, ## learner = learner, ## resampling = resampling) ## # compute the AUROC as a data.table ## score_spcv_glm = rr_spcv_glm$score(measure = mlr3::msr("classif.auc")) ## # keep only the columns you need ## score_spcv_glm = score_spcv_glm[, .(task_id, learner_id, resampling_id, classif.auc)] ## ----12-spatial-cv-21------------------------------------------------------------------------------- score = readRDS("extdata/12-bmr_score.rds") score_spcv_glm = score[learner_id == "classif.log_reg" & resampling_id == "repeated_spcv_coords"] ## ----12-spatial-cv-22------------------------------------------------------------------------------- mean(score_spcv_glm$classif.auc) |> round(2) ## ----boxplot-cv, echo=FALSE, out.width="75%", fig.cap="Boxplot showing the difference in GLM AUROC values on spatial and conventional 100-repeated 5-fold cross-validation.", fig.scap="Boxplot showing AUROC values."---- library(ggplot2) # rename the levels of resampling_id score[, resampling_id := as.factor(resampling_id) |> forcats::fct_recode("conventional CV" = "repeated_cv", "spatial CV" = "repeated_spcv_coords") |> forcats::fct_rev()] # create the boxplot ggplot2::ggplot(data = score[learner_id == "classif.log_reg"], mapping = ggplot2::aes(x = resampling_id, y = classif.auc)) + ggplot2::geom_boxplot(fill = c("lightblue2", "mistyrose2")) + ggplot2::theme_bw() + ggplot2::labs(y = "AUROC", x = "") ## ----12-spatial-cv-23------------------------------------------------------------------------------- mlr3_learners = list_mlr3learners() mlr3_learners[class == "classif" & grepl("svm", id), .(id, class, mlr3_package, required_packages)] ## ----12-spatial-cv-24------------------------------------------------------------------------------- lrn_ksvm = mlr3::lrn("classif.ksvm", predict_type = "prob", kernel = "rbfdot") lrn_ksvm$fallback = lrn("classif.featureless", predict_type = "prob") ## ----12-spatial-cv-25------------------------------------------------------------------------------- # performance estimation level perf_level = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) ## ----inner-outer, echo=FALSE, fig.cap="Schematic of hyperparameter tuning and performance estimation levels in CV. (Figure was taken from Schratz et al. (2019). Permission to reuse it was kindly granted.)", fig.scap="Schematic of hyperparameter tuning."---- knitr::include_graphics("images/13_cv.png") ## ----12-spatial-cv-26, eval=FALSE------------------------------------------------------------------- ## # five spatially disjoint partitions ## tune_level = mlr3::rsmp("spcv_coords", folds = 5) ## # use 50 randomly selected hyperparameters ## terminator = mlr3tuning::trm("evals", n_evals = 50) ## tuner = mlr3tuning::tnr("random_search") ## # define the outer limits of the randomly selected hyperparameters ## search_space = paradox::ps( ## C = paradox::p_dbl(lower = -12, upper = 15, trafo = function(x) 2^x), ## sigma = paradox::p_dbl(lower = -15, upper = 6, trafo = function(x) 2^x) ## ) ## ----12-spatial-cv-27, eval=FALSE------------------------------------------------------------------- ## at_ksvm = mlr3tuning::AutoTuner$new( ## learner = lrn_ksvm, ## resampling = tune_level, ## measure = mlr3::msr("classif.auc"), ## search_space = search_space, ## terminator = terminator, ## tuner = tuner ## ) ## ----future, eval=FALSE----------------------------------------------------------------------------- ## library(future) ## # execute the outer loop sequentially and parallelize the inner loop ## future::plan(list("sequential", "multisession"), ## workers = floor(availableCores() / 2)) ## ----12-spatial-cv-30, eval=FALSE------------------------------------------------------------------- ## progressr::with_progress(expr = { ## rr_spcv_svm = mlr3::resample(task = task, ## learner = at_ksvm, ## # outer resampling (performance level) ## resampling = perf_level, ## store_models = FALSE, ## encapsulate = "evaluate") ## }) ## ## # stop parallelization ## future:::ClusterRegistry("stop") ## # compute the AUROC values ## score_spcv_svm = rr_spcv_svm$score(measure = mlr3::msr("classif.auc")) ## # keep only the columns you need ## score_spcv_svm = score_spcv_svm[, .(task_id, learner_id, resampling_id, classif.auc)] ## ----12-spatial-cv-31------------------------------------------------------------------------------- score = readRDS("extdata/12-bmr_score.rds") score_spcv_svm = score[learner_id == "classif.ksvm.tuned" & resampling_id == "repeated_spcv_coords"] ## ----12-spatial-cv-33------------------------------------------------------------------------------- # final mean AUROC round(mean(score_spcv_svm$classif.auc), 2) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_12-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/13-transport.R ================================================ ## ---- echo=FALSE------------------------------------------------------------------------------------ knitr::opts_chunk$set(warning = FALSE) ## ----13-transport-1, message=FALSE, results='hide'-------------------------------------------------- library(sf) library(dplyr) library(spDataLarge) library(stplanr) # for processing geographic transport data library(tmap) # map-making (see Chapter 9) library(ggplot2) # data visualization package library(sfnetworks) # spatial network classes and functions ## ----13-transport-2, echo=FALSE, eval=FALSE--------------------------------------------------------- ## # code that generated the input data - see also ?bristol_ways ## # source("https://github.com/Robinlovelace/geocompr/raw/main/code/13-transport-data-gen.R") ## # view input data ## summary(bristol_ways) ## summary(bristol_ttwa) ## summary(bristol_region) ## ## region_all = rbind(bristol_region, bristol_ttwa) ## tmap_mode("view") ## tm_shape(region_all[1, ], bbox = region_all) + ## tm_fill("yellow", alpha = 0.5) + ## tm_shape(bristol_ways) + ## tm_lines(col = "highway", lwd = 2.1, palette = "-Set1" ## ) + ## tm_scale_bar() + ## tm_shape(region_all) + ## tm_borders(col = "black") + ## tm_basemap(server = leaflet::providers$Esri.WorldTopoMap) ## ----bristol, echo=FALSE, fig.cap="Bristol's transport network represented by colored lines for active (green), public (railways, black) and private motor (red) modes of travel. Black border lines represent the inner city boundary (highlighted in yellow) and the larger Travel To Work Area (TTWA).", fig.scap="Bristol's transport network."---- knitr::include_graphics("images/13_bristol.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/34452756-985267de-ed3e-11e7-9f59-fda1f3852253.png") ## ----13-transport-3, eval=FALSE, echo=FALSE--------------------------------------------------------- ## if (!require(readODS)) { ## install.packages("readODS") ## } ## u = "https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/536823/local-area-walking-and-cycling-in-england-2015.zip" ## download.file(u, "local-area-walking-and-cycling-in-england-2015.zip") ## unzip("local-area-walking-and-cycling-in-england-2015.zip") ## View(readODS::read_ods("Table index.ods")) ## cw0103 = readODS::read_ods("cw0103.ods") ## View(cw0103) ## Another issue with small zones is related to anonymity rules. ## To make it impossible to infer the identity of individuals in zones, detailed socio-demographic variables are often only available at a low geographic resolution. ## Breakdowns of travel mode by age and sex, for example, are available at the Local Authority level in the UK, but not at the much higher Output Area level, each of which contains around 100 households. ## For further details, see www.ons.gov.uk/methodology/geography. ## ----13-transport-5--------------------------------------------------------------------------------- names(bristol_zones) ## ----13-transport-6--------------------------------------------------------------------------------- nrow(bristol_od) nrow(bristol_zones) ## ----13-transport-7--------------------------------------------------------------------------------- zones_attr = bristol_od |> group_by(o) |> summarize(across(where(is.numeric), sum)) |> dplyr::rename(geo_code = o) ## ----13-transport-8--------------------------------------------------------------------------------- summary(zones_attr$geo_code %in% bristol_zones$geo_code) ## ----13-transport-9--------------------------------------------------------------------------------- zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") sum(zones_joined$all) names(zones_joined) ## ----13-transport-10-------------------------------------------------------------------------------- zones_destinations = bristol_od |> group_by(d) |> summarize(across(where(is.numeric), sum)) |> select(geo_code = d, all_dest = all) zones_od = inner_join(zones_joined, zones_destinations, by = "geo_code") |> st_as_sf() ## ----13-transport-11, eval=FALSE-------------------------------------------------------------------- ## qtm(zones_od, c("all", "all_dest")) + ## tm_layout(panel.labels = c("Origin", "Destination")) ## ----zones, echo=FALSE, fig.cap="Number of trips (commuters) living and working in the region. The left map shows zone of origin of commute trips; the right map shows zone of destination (generated by the script 13-zones.R).", message=FALSE, fig.scap="Number of trips (commuters) living and working in the region."---- # file.edit("code/13-zones.R") source("https://github.com/Robinlovelace/geocompr/raw/main/code/13-zones.R", print.eval = TRUE) ## ----13-transport-12-------------------------------------------------------------------------------- od_top5 = bristol_od |> arrange(desc(all)) |> top_n(5, wt = all) ## ----od, echo=FALSE--------------------------------------------------------------------------------- od_top5 |> knitr::kable( caption = paste("Sample of the top 5 origin-destination pairs in the", "Bristol OD data frame, representing travel desire", "lines between zones in the study area."), caption.short = "Sample of the origin-destination data.", booktabs = TRUE) ## ----13-transport-13-------------------------------------------------------------------------------- bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 ## ----13-transport-14-------------------------------------------------------------------------------- od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) ## ----13-transport-15, warning=FALSE----------------------------------------------------------------- desire_lines = od2line(od_inter, zones_od) ## ----13-transport-16, eval=FALSE-------------------------------------------------------------------- ## qtm(desire_lines, lines.lwd = "all") ## ----desire, echo=FALSE, warning=FALSE, message=FALSE, fig.cap="Desire lines representing trip patterns in Bristol, with width representing number of trips and color representing the percentage of trips made by active modes (walking and cycling). The four black lines represent the interzonal OD pairs in Table 7.1.", fig.asp=0.8, fig.scap="Desire lines representing trip patterns in Bristol."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/13-desire.R", print.eval = TRUE) ## ----13-transport-20-------------------------------------------------------------------------------- desire_rail = top_n(desire_lines, n = 3, wt = train) ## ----13-transport-21-------------------------------------------------------------------------------- ncol(desire_rail) desire_rail = line_via(desire_rail, bristol_stations) ncol(desire_rail) ## ----stations, echo=FALSE, message=FALSE, warning=FALSE, fig.cap="Station nodes (red dots) used as intermediary points that convert straight desire lines with high rail usage (thin green lines) into three legs: to the origin station (orange) via public transport (blue) and to the destination (pink, not visible because it is so short).", fig.scap="Station nodes."---- # zone_cents = st_centroid(zones_od) # Temporary hack: zone_cents = st_centroid(zones_od) |> st_set_crs(st_crs(desire_rail)) zone_cents_rail = zone_cents[desire_rail, ] bb = tmaptools::bb(desire_rail, ext = 1.1) desire_rail_plot = rbind( st_sf(data.frame(Geometry = "Desire line (original)"), geometry = desire_rail$geometry), st_sf(data.frame(Geometry = "Leg 1 (origin to station)"), geometry = desire_rail$leg_orig), st_sf(data.frame(Geometry = "Leg 2 (station to station)"), geometry = desire_rail$leg_via), st_sf(data.frame(Geometry = "Leg 3 (station to destination)"), geometry = desire_rail$leg_dest) ) desire_rail_plot = desire_rail_plot |> mutate(lty = case_when(Geometry == "Desire line (original)" ~ 2, TRUE ~ 1)) |> mutate(size = case_when(Geometry == "Desire line (original)" ~ 1, TRUE ~ 2)) bristol_rail_points = rbind( st_sf(data.frame( Node = "Origin and destination locations", col = "black" ), geometry = zone_cents_rail$geometry), st_sf(data.frame( Node = "Public transport node", col = "red" ), geometry = bristol_stations$geometry) ) # tmaptools::palette_explorer() tm_shape(desire_rail_plot, bbox = bb) + # legend incorrect in tmap v3.0.0: https://github.com/r-tmap/tmap/issues/672 # tm_lines(col = "Geometry", palette = "Set2", lty = desire_rail_plot$lty) + tm_lines(col = "Geometry", palette = "Set2", lwd = "size", scale = 3, legend.lwd.show = FALSE) + tm_shape(bristol_rail_points) + # Try with different alpha values: # tm_dots(col = "col", size = 0.3, alpha = 0.2) tm_dots(col = "col", size = 0.3) + tm_scale_bar() # tmap_mode("plot") # qtm(bristol_stations, basemaps = "https://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=feae177da543411c9efa64160305212d", dots.col = "red", symbols.size = 2) + # tm_shape(desire_rail) + # tm_lines(col = "black", lwd = 4) + # tm_shape(legs) + # tm_lines() ## ----13-transport-17, message=FALSE----------------------------------------------------------------- desire_lines$distance_km = as.numeric(st_length(desire_lines)) / 1000 desire_lines_short = desire_lines |> filter(car_driver >= 100, distance_km <= 5, distance_km >= 2.5) ## ----13-transport-18-------------------------------------------------------------------------------- routes_short = route(l = desire_lines_short, route_fun = route_osrm, osrm.profile = "bike") ## ----routes, warning=FALSE, fig.cap="Routes along which many (100+) short (<5km Euclidean distance) car journeys are made (red) overlaying desire lines representing the same trips (black) and zone centroids (dots).", fig.scap="Routes along which many car journeys are made.", echo=FALSE---- # TODO (low priority, RL): add a facetted plot showing network cleaning/overline functions # waldo::compare( # sf::st_crs(desire_lines_short), # sf::st_crs(routes_short) # ) routes_plot_data = rbind( desire_lines_short |> transmute(Entity = "Desire lines") |> sf::st_set_crs("EPSG:4326"), routes_short |> transmute(Entity = "Routes") |> sf::st_set_crs("EPSG:4326") ) zone_cents_routes = zone_cents[desire_lines_short, ] routes_plot_data |> ggplot() + geom_sf(aes(color = Entity, linetype = Entity)) + scale_color_manual(values = c("black", "red")) + scale_linetype_manual(values = c(2, 1)) + geom_sf(data = zone_cents_routes) + theme_void() # tm_shape(desire_lines_short) + tm_lines() + # tm_shape(routes_short) + tm_lines(col = "red") ## ----uptakefun-------------------------------------------------------------------------------------- uptake = function(x) { case_when( x <= 3 ~ 0.5, x >= 8 ~ 0, TRUE ~ (8 - x) / (8 - 3) * 0.5 ) } routes_short_scenario = routes_short |> mutate(uptake = uptake(distance / 1000)) |> mutate(bicycle = bicycle + car_driver * uptake, car_driver = car_driver * (1 -uptake)) sum(routes_short_scenario$bicycle) - sum(routes_short$bicycle) ## ----rnet1------------------------------------------------------------------------------------------ route_network_scenario = overline(routes_short_scenario, attrib = "bicycle") ## ----rnetvis, out.width="49%", fig.show='hold', fig.cap="The % of car trips switching to cycling as a function of distance (left) and route network level results of this function (right).", echo=FALSE---- routes_short_scenario |> ggplot() + geom_line(aes(distance / 1000, uptake)) + xlab("Route distance (km)") + ylab("Percent trips switching from driving to cycling") + scale_y_continuous(labels = scales::percent) tm_shape(route_network_scenario) + tm_lines(lwd = "bicycle", scale = 9, title.lwd = "No. bike trips (modeled, one direction)") ## ----13-transport-22-------------------------------------------------------------------------------- summary(bristol_ways) ## ----13-transport-23-------------------------------------------------------------------------------- bristol_ways$lengths = st_length(bristol_ways) ways_sfn = as_sfnetwork(bristol_ways) class(ways_sfn) ## ----13-transport-23-2, eval=FALSE------------------------------------------------------------------ ## ways_sfn ## #> # A sfnetwork with 5728 nodes and 4915 edges ## #> # A directed multigraph with 1013 components with spatially explicit edges ## #> # Node Data: 5,728 × 1 (active) ## #> # Edge Data: 4,915 × 7 ## #> from to highway maxspeed ref geometry lengths ## #> [m] ## #> 1 1 2 road B3130 (-2.61 51.4, -2.61 51.4, -2.61 51.… 218. ## #> # … ## ----wayssln, fig.cap="Illustration of a small route network, with segment thickness proportional to its betweenness, generated using the igraph package and described in the text.", fig.asp=0.8, out.width="60%", fig.scap="Illustration of a small route network."---- ways_centrality = ways_sfn |> activate("edges") |> mutate(betweenness = tidygraph::centrality_edge_betweenness(lengths)) tm_shape(ways_centrality |> st_as_sf()) + tm_lines(lwd = "betweenness", scale = 9, title.lwd = "Betweenness") + tm_shape(route_network_scenario) + tm_lines(lwd = "bicycle", scale = 9, title.lwd = "N0. bike trips (modeled, one direction)", col = "green") ## ----13-transport-24, eval=FALSE, echo=FALSE-------------------------------------------------------- ## # not producing groups of routes so removing for now... ## # m = igraph::clusters(ways_sfn@g) ## # igraph::V(ways_sfn@g)$m = m$membership ## # gdf = igraph::as_long_data_frame(ways_sfn@g) ## ----13-transport-25-------------------------------------------------------------------------------- existing_cycleways_buffer = bristol_ways |> filter(highway == "cycleway") |> # 1) filter out cycleways st_union() |> # 2) unite geometries st_buffer(dist = 100) # 3) create buffer ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## waldo::compare( ## sf::st_crs(route_network_scenario), ## sf::st_crs(existing_cycleways_buffer) ## ) ## ----13-transport-26, eval=FALSE-------------------------------------------------------------------- ## route_network_no_infra = st_difference( ## route_network_scenario, ## route_network_scenario |> st_set_crs(st_crs(existing_cycleways_buffer)), ## existing_cycleways_buffer ## ) ## ----13-transport-26-workaround, echo=FALSE--------------------------------------------------------- # TODO: remove this hidden chunk when rocker project updates PROJ version route_network_no_infra = st_difference( # route_network_scenario, # Temporary workaround, see https://github.com/Robinlovelace/geocompr/issues/863: route_network_scenario |> st_set_crs(st_crs(existing_cycleways_buffer)), existing_cycleways_buffer ) ## ----13-transport-28, eval=FALSE-------------------------------------------------------------------- ## tmap_mode("view") ## qtm(route_network_no_infra, basemaps = leaflet::providers$Esri.WorldTopoMap, ## lines.lwd = 5) ## ----cycleways, echo=FALSE, message=FALSE, fig.cap="Potential routes along which to prioritize cycle infrastructure in Bristol to reduce car-dependency. The static map provides an overview of the overlay between existing infrastructure and routes with high car-bike switching potential (left). The screenshot the interactive map generated from the `qtm()` function highlights Whiteladies Road as somewhere that would benefit from a new cycleway (right).", out.width="50%", fig.show='hold', fig.scap="Routes along which to prioritize cycle infrastructure."---- # Previous verson: # source("https://github.com/Robinlovelace/geocompr/raw/main/code/13-cycleways.R") # m_leaflet # tmap_leaflet(m_leaflet) # not working # online figure - backup # u = "https://user-images.githubusercontent.com/1825120/39901156-a8ec9ef6-54be-11e8-94fb-0b5f6b48775e.png" # knitr::include_graphics(u) tm_shape(existing_cycleways_buffer, bbox = bristol_region) + tm_polygons(col = "lightgreen") + tm_shape(route_network_scenario) + tm_lines(lwd = "bicycle", scale = 9, title.lwd = "No. bike trips (modeled, one direction)") knitr::include_graphics("images/bristol_cycleways_zoomed.png") ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_13-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/14-location.R ================================================ ## ----14-location-1, message=FALSE------------------------------------- library(sf) library(dplyr) library(purrr) library(terra) library(osmdata) library(spDataLarge) ## ----14-location-2, eval=FALSE---------------------------------------- ## download.file("https://tinyurl.com/ybtpkwxz", ## destfile = "census.zip", mode = "wb") ## unzip("census.zip") # unzip the files ## census_de = readr::read_csv2(list.files(pattern = "Gitter.csv")) ## ----attach-census---------------------------------------------------- data("census_de", package = "spDataLarge") ## ----14-location-4---------------------------------------------------- # pop = population, hh_size = household size input = select(census_de, x = x_mp_1km, y = y_mp_1km, pop = Einwohner, women = Frauen_A, mean_age = Alter_D, hh_size = HHGroesse_D) # set -1 and -9 to NA input_tidy = dplyr::mutate( input, dplyr::across(.fns = ~ifelse(.x %in% c(-1, -9), NA, .x))) ## ----census-desc, echo=FALSE------------------------------------------ tab = dplyr::tribble( ~"class", ~"pop", ~"women", ~"age", ~"hh", 1, "3-250", "0-40", "0-40", "1-2", 2, "250-500", "40-47", "40-42", "2-2.5", 3, "500-2000", "47-53", "42-44", "2.5-3", 4, "2000-4000", "53-60", "44-47", "3-3.5", 5, "4000-8000", ">60", ">47", ">3.5", 6, ">8000", "", "", "" ) # commented code to show the input data frame with factors (RL): # summary(input_tidy) # all integers # fct_pop = factor(input_tidy$pop, labels = tab$pop) # summary(fct_pop) # sum(is.na(input_tidy$pop)) # fct_women = factor(input_tidy$women, labels = tab$women[1:5]) # summary(fct_women) # sum(is.na(input_tidy$women)) # fct_mean_age = factor(input_tidy$mean_age, labels = tab$age[1:5]) # summary(fct_mean_age) # sum(is.na(input_tidy$mean_age)) # fct_hh_size = factor(input_tidy$hh_size, labels = tab$hh[1:5]) # summary(fct_hh_size) # sum(is.na(input_tidy$hh_size)) # input_factor = bind_cols( # select(input_tidy, 1:2), # pop = fct_pop, # women = fct_women, # mean_age = fct_mean_age, # hh_size = fct_hh_size, # ) # summary(input_factor) cap = paste("Categories for each variable in census data from", "Datensatzbeschreibung...xlsx", "located in the downloaded file census.zip (see Figure", "\\@ref(fig:census-stack) for their spatial distribution).") knitr::kable(tab, col.names = c("class", "Population", "% female", "Mean age", "Household size"), caption = cap, caption.short = "Categories for each variable in census data.", align = "c", booktabs = TRUE) ## ----14-location-5---------------------------------------------------- input_ras = terra::rast(input_tidy, type = "xyz", crs = "EPSG:3035") ## ----14-location-6---------------------------------------------------- input_ras ## Note that we are using an equal-area projection (EPSG:3035; Lambert Equal Area Europe), i.e., a projected CRS\index{CRS!projected} where each grid cell has the same area, here 1000 x 1000 square meters. ## Since we are using mainly densities such as the number of inhabitants or the portion of women per grid cell, it is of utmost importance that the area of each grid cell is the same to avoid 'comparing apples and oranges'. ## Be careful with geographic CRS\index{CRS!geographic} where grid cell areas constantly decrease in poleward directions (see also Section \@ref(crs-intro) and Chapter \@ref(reproj-geo-data)). ## ----census-stack, echo=FALSE, fig.cap="Gridded German census data of 2011 (see Table \\@ref(tab:census-desc) for a description of the classes).", fig.scap="Gridded German census data."---- knitr::include_graphics("images/08_census_stack.png") ## ----14-location-8---------------------------------------------------- rcl_pop = matrix(c(1, 1, 127, 2, 2, 375, 3, 3, 1250, 4, 4, 3000, 5, 5, 6000, 6, 6, 8000), ncol = 3, byrow = TRUE) rcl_women = matrix(c(1, 1, 3, 2, 2, 2, 3, 3, 1, 4, 5, 0), ncol = 3, byrow = TRUE) rcl_age = matrix(c(1, 1, 3, 2, 2, 0, 3, 5, 0), ncol = 3, byrow = TRUE) rcl_hh = rcl_women rcl = list(rcl_pop, rcl_women, rcl_age, rcl_hh) ## ----14-location-9---------------------------------------------------- reclass = input_ras for (i in seq_len(terra::nlyr(reclass))) { reclass[[i]] = terra::classify(x = reclass[[i]], rcl = rcl[[i]], right = NA) } names(reclass) = names(input_ras) ## ----14-location-10, eval=FALSE--------------------------------------- ## reclass ## #> ... (full output not shown) ## #> names : pop, women, mean_age, hh_size ## #> min values : 127, 0, 0, 0 ## #> max values : 8000, 3, 3, 3 ## ----14-location-11, warning=FALSE, cache=TRUE, cache.lazy=FALSE------ pop_agg = terra::aggregate(reclass$pop, fact = 20, fun = sum, na.rm = TRUE) summary(pop_agg) ## ----14-location-12, warning=FALSE, cache.lazy=FALSE, cache=TRUE------ pop_agg = pop_agg[pop_agg > 500000, drop = FALSE] ## ----14-location-13, warning=FALSE, message=FALSE--------------------- polys = pop_agg |> terra::patches(directions = 8) |> terra::as.polygons() |> sf::st_as_sf() ## ----14-location-14--------------------------------------------------- metros = polys |> dplyr::group_by(patches) |> dplyr::summarize() ## ----metro-areas, echo=FALSE, out.width= "70%", fig.cap="The aggregated population raster (resolution: 20 km) with the identified metropolitan areas (golden polygons) and the corresponding names.", fig.scap="The aggregated population raster."---- knitr::include_graphics("images/08_metro_areas.png") ## ----14-location-17, warning=FALSE, eval=FALSE------------------------ ## metro_names = sf::st_centroid(metros, of_largest_polygon = TRUE) |> ## tmaptools::rev_geocode_OSM(as.data.frame = TRUE) |> ## select(city, town, state) ## # smaller cities are returned in column town. To have all names in one column, ## # we move the town name to the city column in case it is NA ## metro_names = dplyr::mutate(metro_names, city = ifelse(is.na(city), town, city)) ## ----metro-names, echo=FALSE------------------------------------------ data("metro_names", package = "spDataLarge") knitr::kable(select(metro_names, city, state), caption = "Result of the reverse geocoding.", caption.short = "Result of the reverse geocoding.", booktabs = TRUE) ## ----14-location-19--------------------------------------------------- metro_names = metro_names$city |> as.character() |> {\(x) ifelse(x == "Velbert", "Düsseldorf", x)}() |> {\(x) gsub("ü", "ue", x)}() ## ----14-location-20, eval=FALSE, message=FALSE------------------------ ## shops = purrr::map(metro_names, function(x) { ## message("Downloading shops of: ", x, "\n") ## # give the server a bit time ## Sys.sleep(sample(seq(5, 10, 0.1), 1)) ## query = osmdata::opq(x) |> ## osmdata::add_osm_feature(key = "shop") ## points = osmdata::osmdata_sf(query) ## # request the same data again if nothing has been downloaded ## iter = 2 ## while (nrow(points$osm_points) == 0 & iter > 0) { ## points = osmdata_sf(query) ## iter = iter - 1 ## } ## # return only the point features ## points$osm_points ## }) ## ----14-location-21, eval=FALSE--------------------------------------- ## # checking if we have downloaded shops for each metropolitan area ## ind = purrr::map_dbl(shops, nrow) == 0 ## if (any(ind)) { ## message("There are/is still (a) metropolitan area/s without any features:\n", ## paste(metro_names[ind], collapse = ", "), "\nPlease fix it!") ## } ## ----14-location-22, eval=FALSE--------------------------------------- ## # select only specific columns ## shops = purrr::map_dfr(shops, select, osm_id, shop) ## ----attach-shops----------------------------------------------------- data("shops", package = "spDataLarge") ## If the `shop` column were used instead of the `osm_id` column, we would have retrieved fewer shops per grid cell. ## This is because the `shop` column contains `NA` values, which the `count()` function omits when rasterizing vector objects. ## ----14-location-25, message=FALSE, warning=FALSE--------------------- shops = sf::st_transform(shops, st_crs(reclass)) # create poi raster poi = terra::rasterize(x = terra::vect(shops), y = reclass, field = "osm_id", fun = "length") ## ----14-location-26, message=FALSE, warning=FALSE--------------------- # construct reclassification matrix int = classInt::classIntervals(terra::values(poi), n = 4, style = "fisher") int = round(int$brks) rcl_poi = matrix(c(int[1], rep(int[-c(1, length(int))], each = 2), int[length(int)] + 1), ncol = 2, byrow = TRUE) rcl_poi = cbind(rcl_poi, 0:3) # reclassify poi = terra::classify(poi, rcl = rcl_poi, right = NA) names(poi) = "poi" ## ----14-location-27--------------------------------------------------- # remove population raster and add poi raster reclass = reclass[[names(reclass) != "pop"]] |> c(poi) ## ----14-location-28--------------------------------------------------- # calculate the total score result = sum(reclass) ## ----bikeshop-berlin, echo=FALSE, eval=TRUE, fig.cap="Suitable areas (i.e., raster cells with a score > 9) in accordance with our hypothetical survey for bike stores in Berlin.", fig.scap="Suitable areas for bike stores.", warning=FALSE---- if (knitr::is_latex_output()) { knitr::include_graphics("images/bikeshop-berlin-1.png") } else if (knitr::is_html_output()) { library(leaflet) # have a look at suitable bike shop locations in Berlin berlin = metros[metro_names == "Berlin", ] berlin_raster = terra::crop(result, terra::vect(berlin)) # summary(berlin_raster) # berlin_raster berlin_raster = berlin_raster[berlin_raster > 9, drop = FALSE] leaflet::leaflet() |> leaflet::addTiles() |> # addRasterImage so far only supports raster objects leaflet::addRasterImage(raster::raster(berlin_raster), colors = "darkgreen", opacity = 0.8) |> leaflet::addLegend("bottomright", colors = c("darkgreen"), labels = c("potential locations"), title = "Legend") } ## ---- echo=FALSE, results='asis'-------------------------------------- res = knitr::knit_child('_14-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/15-eco.R ================================================ ## ----15-eco-1, message=FALSE------------------------------------------------------------------------ library(data.table) library(dplyr) library(mlr3) library(mlr3spatiotempcv) library(mlr3tuning) library(mlr3learners) library(qgisprocess) library(paradox) library(ranger) library(tree) library(sf) library(terra) library(tree) library(vegan) ## ----study-area-mongon, echo=FALSE, fig.cap="The Mt. Mongón study area, from Muenchow, Schratz, and Brenning (2017).", out.width="60%", fig.scap="The Mt. Mongón study area."---- knitr::include_graphics("images/15_study_area_mongon.png") # knitr::include_graphics("https://user-images.githubusercontent.com/1825120/38989956-6eae7c9a-43d0-11e8-8f25-3dd3594f7e74.png") ## ----15-eco-2--------------------------------------------------------------------------------------- data("study_area", "random_points", "comm", package = "spDataLarge") dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) ## ----15-eco-3, eval=FALSE--------------------------------------------------------------------------- ## # sites 35 to 40 and corresponding occurrences of the first five species in the ## # community matrix ## comm[35:40, 1:5] ## #> Alon_meri Alst_line Alte_hali Alte_porr Anth_eccr ## #> 35 0 0 0 0.0 1.000 ## #> 36 0 0 1 0.0 0.500 ## #> 37 0 0 0 0.0 0.125 ## #> 38 0 0 0 0.0 3.000 ## #> 39 0 0 0 0.0 2.000 ## #> 40 0 0 0 0.2 0.125 ## ----sa-mongon, echo=FALSE, message=FALSE, fig.cap="Study mask (polygon), location of the sampling sites (black points) and DEM in the background.", fig.scap="Study mask, location of the sampling sites."---- # hs = terra::shade(terra::terrain(dem, v = "slope", unit = "radians"), # terra::terrain(dem, v = "aspect", unit = "radians"), # 10, 200) # library(tmap) # tm = tm_shape(hs) + # tm_grid(n.x = 3, n.y = 3) + # tm_raster(style = "cont", palette = rev(hcl.colors(99, "Grays")), # legend.show = FALSE) + # tm_shape(dem) + # tm_raster(alpha = 0.5, # style = "cont", # title = "m asl", # legend.reverse = TRUE, # n = 11, # palette = terrain.colors(50)) + # tm_shape(study_area) + # tm_borders() + # tm_shape(random_points) + # tm_dots() + # tm_layout(inner.margins = 0, legend.outside = TRUE) # tmap_save(tm, "images/15_sa_mongon_sampling.png", # width = 12, height = 7, units = "cm") knitr::include_graphics("images/15_sa_mongon_sampling.png") ## ----15-eco-5, eval=FALSE--------------------------------------------------------------------------- ## qgisprocess::qgis_show_help("saga:sagawetnessindex") ## #> Saga wetness index (saga:sagawetnessindex) ## #> ... ## #> ---------------- ## #> Arguments ## #> ---------------- ## #> ## #> DEM: Elevation ## #> Argument type: raster ## #> Acceptable values: ## #> - Path to a raster layer ## #> ... ## #> SLOPE_TYPE: Type of Slope ## #> Argument type: enum ## #> Available values: ## #> - 0: [0] local slope ## #> - 1: [1] catchment slope ## #> ... ## #> AREA: Catchment area ## #> Argument type: rasterDestination ## #> Acceptable values: ## #> - Path for new raster layer ## #>... ## #> ---------------- ## #> Outputs ## #> ---------------- ## #> ## #> AREA: ## #> Catchment area ## #> SLOPE: ## #> Catchment slope ## #> ... ## ----15-eco-6, eval=FALSE--------------------------------------------------------------------------- ## # environmental predictors: catchment slope and catchment area ## ep = qgisprocess::qgis_run_algorithm( ## alg = "saga:sagawetnessindex", ## DEM = dem, ## SLOPE_TYPE = 1, ## SLOPE = tempfile(fileext = ".sdat"), ## AREA = tempfile(fileext = ".sdat"), ## .quiet = TRUE) ## ----15-eco-7, eval=FALSE--------------------------------------------------------------------------- ## # read in catchment area and catchment slope ## ep = ep[c("AREA", "SLOPE")] |> ## unlist() |> ## terra::rast() ## names(ep) = c("carea", "cslope") # assign proper names ## terra::origin(ep) = terra::origin(dem) # make sure rasters have the same origin ## ep = c(dem, ndvi, ep) # add dem and ndvi to the multi-layer SpatRaster object ## ----15-eco-8, eval=FALSE--------------------------------------------------------------------------- ## ep$carea = log10(ep$carea) ## ----15-eco-9, cache.lazy=FALSE--------------------------------------------------------------------- ep = terra::rast(system.file("raster/ep.tif", package = "spDataLarge")) ## ----15-eco-10, cache=TRUE, cache.lazy=FALSE, message=FALSE, warning=FALSE-------------------------- # terra::extract adds automatically a for our purposes unnecessary ID column ep_rp = terra::extract(ep, random_points) |> select(-ID) random_points = cbind(random_points, ep_rp) ## ----15-eco-11-------------------------------------------------------------------------------------- # presence-absence matrix pa = vegan::decostand(comm, "pa") # 100 rows (sites), 69 columns (species) # keep only sites in which at least one species was found pa = pa[rowSums(pa) != 0, ] # 84 rows, 69 columns ## ----15-eco-12, eval=FALSE, message=FALSE----------------------------------------------------------- ## set.seed(25072018) ## nmds = vegan::metaMDS(comm = pa, k = 4, try = 500) ## nmds$stress ## #> ... ## #> Run 498 stress 0.08834745 ## #> ... Procrustes: rmse 0.004100446 max resid 0.03041186 ## #> Run 499 stress 0.08874805 ## #> ... Procrustes: rmse 0.01822361 max resid 0.08054538 ## #> Run 500 stress 0.08863627 ## #> ... Procrustes: rmse 0.01421176 max resid 0.04985418 ## #> *** Solution reached ## #> 0.08831395 ## ----15-eco-13, eval=FALSE, echo=FALSE-------------------------------------------------------------- ## saveRDS(nmds, "extdata/15-nmds.rds") ## ----15-eco-14, include=FALSE----------------------------------------------------------------------- nmds = readRDS("extdata/15-nmds.rds") ## ----xy-nmds-code, fig.cap="Plotting the first NMDS axis against altitude.", fig.scap = "First NMDS axis against altitude plot.", fig.asp=1, out.width="60%", eval=FALSE---- ## elev = dplyr::filter(random_points, id %in% rownames(pa)) |> ## dplyr::pull(dem) ## # rotating NMDS in accordance with altitude (proxy for humidity) ## rotnmds = vegan::MDSrotate(nmds, elev) ## # extracting the first two axes ## sc = vegan::scores(rotnmds, choices = 1:2) ## # plotting the first axis against altitude ## plot(y = sc[, 1], x = elev, xlab = "elevation in m", ## ylab = "First NMDS axis", cex.lab = 0.8, cex.axis = 0.8) ## ----xy-nmds, fig.cap="Plotting the first NMDS axis against altitude.", fig.scap = "First NMDS axis against altitude plot.", fig.asp=1, out.width="60%", message=FALSE, echo=FALSE---- elev = dplyr::filter(random_points, id %in% rownames(pa)) |> dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = vegan::MDSrotate(nmds, elev) # extracting the first two axes sc = vegan::scores(rotnmds, choices = 1:2) knitr::include_graphics("images/15_xy_nmds.png") ## ----15-eco-15, eval=FALSE, echo=FALSE-------------------------------------------------------------- ## # scores and rotated scores in one figure ## p1 = xyplot(scores(rotnmds)[, 2] ~ scores(rotnmds)[, 1], pch = 16, ## col = "lightblue", xlim = c(-3, 2), ylim = c(-2, 2), ## xlab = list("Dimension 1", cex = 0.8), ## ylab = list("Dimension 2", cex = 0.8), ## scales = list(x = list(relation = "same", cex = 0.8), ## y = list(relation = "same", cex = 0.8), ## # ticks on top are suppressed ## tck = c(1, 0), ## # plots axes labels only in row and column 1 and 4 ## alternating = c(1, 0, 0, 1), ## draw = TRUE), ## # we have to use the same colors in the legend as used for the plot ## # points ## par.settings = simpleTheme(col = c("lightblue", "salmon"), ## pch = 16, cex = 0.9), ## # also the legend point size should be somewhat smaller ## auto.key = list(x = 0.7, y = 0.9, text = c("unrotated", "rotated"), ## between = 0.5, cex = 0.9), ## panel = function(x, y, ...) { ## # Plot the points ## panel.points(x, y, cex = 0.6, ...) ## panel.points(x = scores(nmds)[, 1], ## y = scores(nmds)[, 2], ## col = "salmon", pch = 16, cex = 0.6) ## panel.arrows(x0 = scores(nmds)[, 1], ## y0 = scores(nmds)[, 2], ## x1 = x, ## y1 = y, ## length = 0.04, ## lwd = 0.4) ## }) ## ## plot(scores(nmds, choices = 1:2)) ## points(scores(rotnmds, choices = 1:2), col = "lightblue", pch = 16) ## ## ## sc = scores(nmds, choices = 1:2) |> as.data.frame() ## sc$id = rownames(sc) |> as.numeric() ## rp = inner_join(select(sc, id), st_drop_geometry(random_points)) ## fit_1 = envfit(nmds, select(rp, dem)) ## fit_2 = envfit(rotnmds, select(rp, dem)) ## par(mfrow = c(1, 2)) ## plot(nmds, display = "sites") ## plot(fit_1) ## plot(rotnmds, display = "sites") ## plot(fit_2) ## ----15-eco-16, message=FALSE, eval=FALSE----------------------------------------------------------- ## # construct response-predictor matrix ## # id- and response variable ## rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) ## # join the predictors (dem, ndvi and terrain attributes) ## rp = inner_join(random_points, rp, by = "id") ## ----attach-rp, echo=FALSE-------------------------------------------------------------------------- # rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) # rp = inner_join(random_points, rp, by = "id") # saveRDS(rp, "extdata/15-rp.rds") rp = readRDS("extdata/15-rp.rds") ## ----15-eco-17, eval=FALSE-------------------------------------------------------------------------- ## tree_mo = tree::tree(sc ~ dem, data = rp) ## plot(tree_mo) ## text(tree_mo, pretty = 0) ## ----tree, echo=FALSE, fig.cap="Simple example of a decision tree with three internal nodes and four terminal nodes.", out.width="60%", fig.scap="Simple example of a decision tree."---- # tree_mo = tree::tree(sc ~ dem, data = rp) # png("images/15_tree.png", width = 1100, height = 700, units = "px", res = 300) # par(mar = rep(1, 4)) # plot(tree_mo) # text(tree_mo, pretty = 0) # dev.off() knitr::include_graphics("images/15_tree.png") ## --------------------------------------------------------------------------------------------------- knitr::opts_chunk$set(eval = FALSE) ## ----15-eco-20-------------------------------------------------------------------------------------- # create task task = mlr3spatiotempcv::as_task_regr_st(select(rp, -id, -spri), id = "mongon", target = "sc") ## ----15-eco-21-------------------------------------------------------------------------------------- lrn_rf = lrn("regr.ranger", predict_type = "response") ## ----15-eco-22-------------------------------------------------------------------------------------- # specifying the search space search_space = paradox::ps( mtry = paradox::p_int(lower = 1, upper = ncol(task$data()) - 1), sample.fraction = paradox::p_dbl(lower = 0.2, upper = 0.9), min.node.size = paradox::p_int(lower = 1, upper = 10) ) ## ----15-eco-23-------------------------------------------------------------------------------------- autotuner_rf = mlr3tuning::AutoTuner$new( learner = lrn_rf, resampling = mlr3::rsmp("spcv_coords", folds = 5), # spatial partitioning measure = mlr3::msr("regr.rmse"), # performance measure terminator = mlr3tuning::trm("evals", n_evals = 50), # specify 50 iterations search_space = search_space, # predefined hyperparameter search space tuner = mlr3tuning::tnr("random_search") # specify random search ) ## ----15-eco-24, eval=FALSE, cache=TRUE, cache.lazy=FALSE-------------------------------------------- ## # hyperparameter tuning ## set.seed(0412022) ## autotuner_rf$train(task) ## ----15-eco-25, cache=TRUE, cache.lazy=FALSE, eval=FALSE, echo=FALSE-------------------------------- ## saveRDS(autotuner_rf, "extdata/15-tune.rds") ## ----15-eco-26, echo=FALSE, cache=TRUE, cache.lazy=FALSE-------------------------------------------- autotuner_rf = readRDS("extdata/15-tune.rds") ## ----tuning-result, cache=TRUE, cache.lazy=FALSE---------------------------------------------------- autotuner_rf$tuning_result ## ----15-eco-27, cache=TRUE, cache.lazy=FALSE-------------------------------------------------------- # predicting using the best hyperparameter combination autotuner_rf$predict(task) ## ----15-eco-28, cache=TRUE, cache.lazy=FALSE, eval=FALSE-------------------------------------------- ## pred = terra::predict(ep, model = autotuner_rf, fun = predict) ## ----rf-pred, echo=FALSE, fig.cap="Predictive mapping of the floristic gradient clearly revealing distinct vegetation belts.", out.width="60%", fig.scap="Predictive mapping of the floristic gradient."---- # hs = terra::shade(terra::terrain(dem, v = "slope", unit = "radians"), # terra::terrain(dem, v = "aspect", unit = "radians"), # 10, 200) |> # terra::mask(terra::vect(study_area)) # pred = terra::mask(pred, terra::vect(study_area)) |> # terra::trim() # library(tmap) # pal = rev(hcl.colors(n = 15, "RdYlBu")) # tm = tm_shape(hs) + # tm_grid(n.x = 3, n.y = 3, lines = FALSE) + # tm_raster(style = "cont", palette = rev(hcl.colors(99, "Grays")), # legend.show = FALSE) + # tm_shape(pred, is.master = TRUE) + # tm_raster(style = "cont", title = "NMDS1", alpha = 0.8, # legend.reverse = TRUE, palette = pal, midpoint = NA) + # tm_shape(study_area) + # tm_borders() + # tm_layout(inner.margins = 0.02, legend.outside = TRUE) # tmap_save(tm, "images/15_rf_pred.png", # width = 12, height = 7, units = "cm") knitr::include_graphics("images/15_rf_pred.png") ## ----15-eco-29, cache=TRUE, cache.lazy=FALSE, eval=FALSE-------------------------------------------- ## newdata = as.data.frame(as.matrix(ep)) ## colSums(is.na(newdata)) # 0 NAs ## # but assuming there were 0s results in a more generic approach ## ind = rowSums(is.na(newdata)) == 0 ## tmp = autotuner_rf$predict_newdata(newdata = newdata[ind, ], task = task) ## newdata[ind, "pred"] = data.table::as.data.table(tmp)[["response"]] ## pred_2 = ep$dem ## # now fill the raster with the predicted values ## pred_2[] = newdata$pred ## # check if terra and our manual prediction is the same ## all(values(pred - pred_2) == 0) ## ---- echo=FALSE, results='asis'-------------------------------------------------------------------- res = knitr::knit_child('_15-ex.Rmd', quiet = TRUE, options = list(include = FALSE, eval = FALSE)) cat(res, sep = '\n') ================================================ FILE: code/chapters/16-synthesis.R ================================================ ## ----15-synthesis-1--------------------------------------------------------------------------------- library(spData) nz_u1 = sf::st_union(nz) nz_u2 = aggregate(nz["Population"], list(rep(1, nrow(nz))), sum) nz_u3 = dplyr::summarise(nz, t = sum(Population)) identical(nz_u1, nz_u2$geometry) identical(nz_u1, nz_u3$geom) ## ----15-synthesis-2, message=FALSE------------------------------------------------------------------ library(dplyr) # attach tidyverse package nz_name1 = nz["Name"] # base R approach nz_name2 = nz |> select(Name) # tidyverse approach identical(nz_name1$Name, nz_name2$Name) # check results ## ----15-synthesis-3, eval=FALSE, echo=FALSE--------------------------------------------------------- ## # aim: find number of packages in the spatial task view ## # how? see: ## # vignette("selectorgadget") ## stv_pkgs = xml2::read_html("https://cran.r-project.org/web/views/Spatial.html") ## pkgs = rvest::html_nodes(stv_pkgs, "ul:nth-child(5) a") ## pkgs_char = rvest::html_text(pkgs) ## length(pkgs_char) ## ----15-synthesis-4, echo=FALSE, eval=FALSE--------------------------------------------------------- ## revdeps_sp = devtools::revdep(pkg = "sp", dependencies = c("Depends", "Imports")) ## revdeps_sf = devtools::revdep(pkg = "sf", dependencies = c("Depends", "Imports")) ## revdeps_spatstat = devtools::revdep(pkg = "spatstat", dependencies = c("Depends", "Imports")) ================================================ FILE: code/chapters/README.R ================================================ ## ---- echo = FALSE---------------------------------------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.path = "images/" ) is_online = curl::has_internet() ## ----contributors, include=FALSE-------------------------------------------------------------------- contributors = source("code/list-contributors.R")[[1]] # save for future reference: readr::write_csv(contributors, "extdata/contributors.csv") # table view: # knitr::kable(contributors, caption = "Contributors to Geocomputation with R") # text view c_txt = contributors$name c_url = contributors$link c_rmd = paste0("[", c_txt, "](", c_url, ")") contributors_text = paste0(c_rmd, collapse = ", ") ## ----dl-unzip, eval=FALSE--------------------------------------------------------------------------- ## u = "https://github.com/Robinlovelace/geocompr/archive/refs/heads/main.zip" ## f = basename(u) ## download.file(u, f) # download the file ## unzip(f) # unzip it ## file.rename(f, "geocompr") # rename the directory ## rstudioapi::openProject("geococompr") # or open the folder in vscode / other IDE ## ----readme-install-github, eval=FALSE-------------------------------------------------------------- ## install.packages("remotes") ## # To reproduce the first Part (chapters 1 to 8): ## install.packages('geocompkg', repos = c('https://geocompr.r-universe.dev', 'https://cloud.r-project.org'), dependencies = TRUE, force = TRUE) ## ----readme-install-github-2, message=FALSE, eval=FALSE, results='hide'----------------------------- ## # To reproduce all chapters (install lots of packages, may take some time!) ## remotes::install_github("geocompx/geocompkg", dependencies = TRUE) ## ----readme-render-book, eval=FALSE----------------------------------------------------------------- ## # Change this depending on where you have the book code stored: ## rstudioapi::openProject("~/Downloads/geocompr") ## # or code /location/of/geocompr in the system terminal ## # or cd /location/of/geocompr then R in the system terminal, then: ## bookdown::render_book("index.Rmd") # to build the book ## browseURL("_book/index.html") # to view it ## # Or, to serve a live preview the book and observe impact of changes: ## bookdown::serve_book() ## ----gen-code, results='hide', echo=FALSE, eval=FALSE----------------------------------------------- ## geocompkg:::generate_chapter_code() ## ----extra-pkgs, message=FALSE, eval=FALSE---------------------------------------------------------- ## source("code/extra-pkgs.R") ## ----source-readme, eval=FALSE---------------------------------------------------------------------- ## source("code/01-cranlogs.R") ## source("code/sf-revdep.R") ## source("code/09-urban-animation.R") ## source("code/09-map-pkgs.R") ## ----render-book, eval=FALSE------------------------------------------------------------------------ ## rmarkdown::render("README.Rmd", output_format = "github_document", output_file = "README.md") ## ----scripts, eval=FALSE, echo=FALSE--------------------------------------------------------------- ## # We aim to make every script in the `code` folder reproducible. ## # To check they can all be reproduced run the following: ## # Aim: test reproducibility of scripts ## script_names = list.files("code", full.names = T) ## avoid = "pkgs|anim|us|saga|sliver|tsp|parti|polycent|cv|svm|data|location|eco|rf|cran|hex" ## dontrun = grepl(avoid, script_names) ## script_names = script_names[!dontrun] ## counter = 0 ## for(i in script_names[45:length(script_names)]) { ## counter = counter + 1 ## print(paste0("Script number ", counter, ": ", i)) ## source(i) ## } ## ----gen-stats, echo=FALSE, message=FALSE, warning=FALSE, eval=FALSE-------------------------------- ## # source("code/generate-chapter-code.R") ## book_stats = readr::read_csv("extdata/word-count-time.csv", ## col_types=('iiDd')) ## ## # to prevent excessive chapter count ## if (Sys.Date() > max(book_stats$date) + 5) { ## book_stats_new = geocompkg:::generate_book_stats() ## book_stats = bind_rows(book_stats, book_stats_new) ## readr::write_csv(book_stats, "extdata/word-count-time.csv") ## } ## book_stats = dplyr::filter(book_stats, chapter <= 15) ## library(ggplot2) ## book_stats$chapter = formatC(book_stats$chapter, width = 2, format = "d", flag = "0") ## book_stats$chapter = fct_rev(as.factor(book_stats$chapter)) ## book_stats$n_pages = book_stats$n_words / 300 ## ----bookstats, warning=FALSE, echo=FALSE, fig.width=8, fig.height=5, eval=FALSE-------------------- ## ggplot(book_stats) + ## geom_area(aes(date, n_pages, fill = chapter), position = "stack") + ## ylab("Estimated number of pages") + ## xlab("Date") + ## scale_x_date(date_breaks = "2 month", ## limits = c(min(book_stats$date), as.Date("2018-10-01")), ## date_labels = "%b %Y") + ## coord_cartesian(ylim = c(0, 350)) ## ----gen-cite, warning=FALSE------------------------------------------------------------------------ # geocompkg:::generate_citations() ## ----pkg_df, message=FALSE-------------------------------------------------------------------------- pkg_df = readr::read_csv("extdata/package_list.csv") ================================================ FILE: code/chapters/_01-ex.R ================================================ ================================================ FILE: code/chapters/_02-ex.R ================================================ ## ----02-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(spData) library(terra) ## ----02-ex-e1--------------------------------------------------------------------------------------- summary(world) # - Its geometry type? # multipolygon # - The number of countries? # 177 # - Its coordinate reference system (CRS)? # epsg:4326 ## ----02-ex-e2--------------------------------------------------------------------------------------- plot(world["continent"], reset = FALSE) cex = sqrt(world$pop) / 10000 world_cents = st_centroid(world, of_largest = TRUE) plot(st_geometry(world_cents), add = TRUE, cex = cex) # - What does the `cex` argument do (see `?plot`)? # It specifies the size of the circles # - Why was `cex` set to the `sqrt(world$pop) / 10000`? # So the circles would be visible for small countries but not too large for large countries, also because area increases as a linear function of the square route of the diameter defined by `cex` # - Bonus: experiment with different ways to visualize the global population. plot(st_geometry(world_cents), cex = world$pop / 1e9) plot(st_geometry(world_cents), cex = world$pop / 1e8) plot(world["pop"]) plot(world["pop"], logz = TRUE) # Similarities: global extent, colorscheme, relative size of circles # # Differences: projection (Antarctica is much smaller for example), graticules, location of points in the countries. # # To understand these differences read-over, run, and experiment with different argument values in this script: https://github.com/Robinlovelace/geocompr/raw/main/code/02-contpop.R # # `cex` refers to the diameter of symbols plotted, as explained by the help page `?graphics::points`. It is an acronym for 'Chacter symbol EXpansion'. # It was set to the square route of the population divided by 10,000 because a) otherwise the symbols would not fit on the map and b) to make circle area proportional to population. ## ----02-ex-e3--------------------------------------------------------------------------------------- nigeria = world[world$name_long == "Nigeria", ] plot(st_geometry(nigeria), expandBB = c(0, 0.2, 0.1, 1), col = "gray", lwd = 3) plot(world[0], add = TRUE) world_coords = st_coordinates(world_cents) text(world_coords, world$iso_a2) # Alternative answer: nigeria = world[world$name_long == "Nigeria", ] africa = world[world$continent == "Africa", ] plot(st_geometry(nigeria), col = "white", lwd = 3, main = "Nigeria in context", border = "lightgray", expandBB = c(0.5, 0.2, 0.5, 0.2)) plot(st_geometry(world), lty = 3, add = TRUE, border = "gray") plot(st_geometry(nigeria), col = "yellow", add = TRUE, border = "darkgray") a = africa[grepl("Niger", africa$name_long), ] ncentre = st_centroid(a) ncentre_num = st_coordinates(ncentre) text(x = ncentre_num[, 1], y = ncentre_num[, 2], labels = a$name_long) ## ----02-ex-e4, message = FALSE---------------------------------------------------------------------- my_raster = rast(ncol = 10, nrow = 10, vals = sample(0:10, size = 10 * 10, replace = TRUE)) plot(my_raster) ## ----02-ex-e5, message = FALSE---------------------------------------------------------------------- nlcd = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) dim(nlcd) # dimensions res(nlcd) # resolution ext(nlcd) # extent nlyr(nlcd) # number of layers cat(crs(nlcd)) # CRS ## ----02-ex-e6, message = FALSE---------------------------------------------------------------------- cat(crs(nlcd)) ## The WKT above describes a two-dimensional projected coordinate reference system. ## It is based on the GRS 1980 ellipsoid with North American Datum 1983 and the Greenwich prime meridian. ## It used the Transverse Mercator projection to transform from geographic to projected CRS (UTM zone 12N). ## Its first axis is related to eastness, while the second one is related to northness, and both axes have units in meters. ## The SRID of the above CRS is "EPSG:26912". ================================================ FILE: code/chapters/_03-ex.R ================================================ ## ----03-ex-e0, include=TRUE, message=FALSE---------------------------------------------------------- library(sf) library(dplyr) library(terra) library(spData) data(us_states) data(us_states_df) ## ----03-ex-e1--------------------------------------------------------------------------------------- us_states_name = us_states["NAME"] class(us_states_name) attributes(us_states_name) attributes(us_states_name$geometry) ## - It is of class `sf` and `data.frame`: it has 2 classes. ## - It is the `sf` class that makes in geographic. ## - More specifically it is the attributes of the object (`sf_column`) and the geometry column (such as `bbox`, `crs`) that make it geographic. ## ----03-ex-e2--------------------------------------------------------------------------------------- us_states |> select(total_pop_10, total_pop_15) # or us_states |> select(starts_with("total_pop")) # or us_states |> select(contains("total_pop")) # or us_states |> select(matches("tal_p")) ## ----03-ex-e3--------------------------------------------------------------------------------------- us_states |> filter(REGION == "Midwest") us_states |> filter(REGION == "West", AREA < units::set_units(250000, km^2), total_pop_15 > 5000000) # or us_states |> filter(REGION == "West", as.numeric(AREA) < 250000, total_pop_15 > 5000000) us_states |> filter(REGION == "South", AREA > units::set_units(150000, km^2), total_pop_15 > 7000000) # or us_states |> filter(REGION == "South", as.numeric(AREA) > 150000, total_pop_15 > 7000000) ## ----03-ex-e4--------------------------------------------------------------------------------------- us_states |> summarize(total_pop = sum(total_pop_15), min_pop = min(total_pop_15), max_pop = max(total_pop_15)) ## ----03-ex-e5--------------------------------------------------------------------------------------- us_states |> group_by(REGION) |> summarize(nr_of_states = n()) ## ----03-ex-e6--------------------------------------------------------------------------------------- us_states |> group_by(REGION) |> summarize(min_pop = min(total_pop_15), max_pop = max(total_pop_15), tot_pop = sum(total_pop_15)) ## ----03-ex-e7--------------------------------------------------------------------------------------- us_states_stats = us_states |> left_join(us_states_df, by = c("NAME" = "state")) class(us_states_stats) ## ----03-ex-e8--------------------------------------------------------------------------------------- us_states_df |> anti_join(st_drop_geometry(us_states), by = c("state" = "NAME")) ## ----03-ex-e9--------------------------------------------------------------------------------------- us_states2 = us_states |> mutate(pop_dens_15 = total_pop_15/AREA, pop_dens_10 = total_pop_10/AREA) ## ----03-ex-e10-------------------------------------------------------------------------------------- us_popdens_change = us_states2 |> mutate(pop_dens_diff_10_15 = pop_dens_15 - pop_dens_10, pop_dens_diff_10_15p = (pop_dens_diff_10_15/pop_dens_15) * 100) plot(us_popdens_change["pop_dens_diff_10_15p"]) ## ----03-ex-e11-------------------------------------------------------------------------------------- us_states %>% setNames(tolower(colnames(.))) ## ----03-ex-e12-------------------------------------------------------------------------------------- us_states_sel = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> select(Income = median_income_15) ## ----03-ex-e13-------------------------------------------------------------------------------------- us_pov_change = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> mutate(pov_change = poverty_level_15 - poverty_level_10) # Bonus us_pov_pct_change = us_states |> left_join(us_states_df, by = c("NAME" = "state")) |> mutate(pov_pct_10 = (poverty_level_10 / total_pop_10) * 100, pov_pct_15 = (poverty_level_15 / total_pop_15) * 100) |> mutate(pov_pct_change = pov_pct_15 - pov_pct_10) ## ----03-ex-e14-------------------------------------------------------------------------------------- us_pov_change_reg = us_pov_change |> group_by(REGION) |> summarize(min_state_pov_15 = min(poverty_level_15), mean_state_pov_15 = mean(poverty_level_15), max_state_pov_15 = max(poverty_level_15)) # Bonus us_pov_change |> group_by(REGION) |> summarize(region_pov_change = sum(pov_change)) |> filter(region_pov_change == max(region_pov_change)) |> pull(REGION) |> as.character() ## ----03-ex-e15-------------------------------------------------------------------------------------- r = rast(nrow = 9, ncol = 9, res = 0.5, xmin = 0, xmax = 4.5, ymin = 0, ymax = 4.5, vals = rnorm(81)) # using cell IDs r[c(1, 9, 81 - 9 + 1, 81)] r[c(1, nrow(r)), c(1, ncol(r))] ## ----03-ex-e16-------------------------------------------------------------------------------------- grain = rast(system.file("raster/grain.tif", package = "spData")) freq(grain) |> arrange(-count )# the most common classes are silt and sand (13 cells) ## ----03-ex-e17-------------------------------------------------------------------------------------- dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) hist(dem) boxplot(dem) # we can also use ggplot2 after converting SpatRaster to a data frame library(ggplot2) ggplot(as.data.frame(dem), aes(dem)) + geom_histogram() ggplot(as.data.frame(dem), aes(dem)) + geom_boxplot() ================================================ FILE: code/chapters/_04-ex.R ================================================ ## ----04-ex-e0, include=TRUE, message=FALSE---------------------------------------------------------- library(sf) library(dplyr) library(spData) ## ----04-ex-e1--------------------------------------------------------------------------------------- library(tmap) # tmap_mode("view") qtm(nz) + qtm(nz_height) canterbury = nz |> filter(Name == "Canterbury") canterbury_height = nz_height[canterbury, ] nz_not_canterbury_height = nz_height[canterbury, , op = st_disjoint] nrow(canterbury_height) # answer: 70 plot(st_geometry(nz)) plot(st_geometry(canterbury), col = "yellow", add = TRUE) plot(nz_not_canterbury_height$geometry, pch = 1, col = "blue", add = TRUE) plot(canterbury_height$geometry, pch = 4, col = "red", add = TRUE) ## ----04-ex-e2--------------------------------------------------------------------------------------- nz_height_count = aggregate(nz_height, nz, length) nz_height_combined = cbind(nz, count = nz_height_count$elevation) nz_height_combined |> st_drop_geometry() |> select(Name, count) |> arrange(desc(count)) |> slice(2) ## ----04-ex-e3--------------------------------------------------------------------------------------- # Base R way: nz_height_count = aggregate(nz_height, nz, length) nz_height_combined = cbind(nz, count = nz_height_count$elevation) plot(nz_height_combined) # Tidyverse way: nz_height_joined = st_join(nz_height, nz |> select(Name)) # Calculate n. points in each region - this contains the result nz_height_counts = nz_height_joined |> group_by(Name) |> summarise(count = n()) # Optionally join results with nz geometries: nz_height_combined = left_join(nz, nz_height_counts |> sf::st_drop_geometry()) # plot(nz_height_combined) # Check: results identical to base R result # Generate a summary table nz_height_combined |> st_drop_geometry() |> select(Name, count) |> arrange(desc(count)) |> na.omit() ## ----04-ex-4-1-------------------------------------------------------------------------------------- colorado = us_states[us_states$NAME == "Colorado", ] plot(us_states$geometry) plot(colorado$geometry, col = "gray", add = TRUE) ## ----04-ex-4-2-------------------------------------------------------------------------------------- intersects_with_colorado = us_states[colorado, , op = st_intersects] plot(us_states$geometry, main = "States that intersect with Colorado") plot(intersects_with_colorado$geometry, col = "gray", add = TRUE) ## ----04-ex-4-3-------------------------------------------------------------------------------------- # Alternative but more verbose solutions # 2: With intermediate object, one list for each state sel_intersects_colorado = st_intersects(us_states, colorado) sel_intersects_colorado_list = lengths(sel_intersects_colorado) > 0 intersects_with_colorado = us_states[sel_intersects_colorado_list, ] # 3: With intermediate object, one index for each state sel_intersects_colorado2 = st_intersects(colorado, us_states) sel_intersects_colorado2 us_states$NAME[unlist(sel_intersects_colorado2)] # 4: With tidyverse us_states |> st_filter(y = colorado, .predicate = st_intersects) ## ----04-ex-4-4-------------------------------------------------------------------------------------- touches_colorado = us_states[colorado, , op = st_touches] plot(us_states$geometry, main = "States that touch Colorado") plot(touches_colorado$geometry, col = "gray", add = TRUE) ## ----04-ex-4-5-------------------------------------------------------------------------------------- washington_to_cali = us_states |> filter(grepl(pattern = "Columbia|Cali", x = NAME)) |> st_centroid() |> st_union() |> st_cast("LINESTRING") states_crossed = us_states[washington_to_cali, , op = st_crosses] states_crossed$NAME plot(us_states$geometry, main = "States crossed by a straight line\n from the District of Columbia to central California") plot(states_crossed$geometry, col = "gray", add = TRUE) plot(washington_to_cali, add = TRUE) ## ----04-ex-e5--------------------------------------------------------------------------------------- library(terra) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) #1 dem_rcl = matrix(c(-Inf, 300, 0, 300, 500, 1, 500, Inf, 2), ncol = 3, byrow = TRUE) dem_reclass = classify(dem, dem_rcl) levels(dem_reclass) = data.frame(id = 0:2, cats = c("low", "medium", "high")) plot(dem_reclass) #2 zonal(c(dem, ndvi), dem_reclass, fun = "mean") ## ----04-ex-e6--------------------------------------------------------------------------------------- # from the focal help page (?terra::focal()): # Laplacian filter: filter=matrix(c(0,1,0,1,-4,1,0,1,0), nrow=3) # Sobel filters (for edge detection): # fx=matrix(c(-1,-2,-1,0,0,0,1,2,1), nrow=3) # fy=matrix(c(1,0,-1,2,0,-2,1,0,-1), nrow=3) # just retrieve the first channel of the R logo r = rast(system.file("ex/logo.tif", package = "terra")) # compute the Sobel filter filter_x = matrix(c(-1, -2, -1, 0, 0, 0, 1, 2, 1), nrow = 3) sobel_x = focal(r, w = filter_x) plot(sobel_x, col = c("white", "black")) filter_y = matrix(c(1, 0, -1, 2, 0, -2, 1, 0, -1), nrow = 3) sobel_y = focal(r, w = filter_y) plot(sobel_y, col = c("black", "white")) ## ----04-ex-e7--------------------------------------------------------------------------------------- file = system.file("raster/landsat.tif", package = "spDataLarge") multi_rast = rast(file) ndvi_fun = function(nir, red){ (nir - red) / (nir + red) } ndvi_rast = lapp(multi_rast[[c(4, 3)]], fun = ndvi_fun) plot(ndvi_rast) ndwi_fun = function(green, nir){ (green - nir) / (green + nir) } ndwi_rast = lapp(multi_rast[[c(2, 4)]], fun = ndwi_fun) plot(ndwi_rast) two_rasts = c(ndvi_rast, ndwi_rast) names(two_rasts) = c("ndvi", "ndwi") # correlation -- option 1 layerCor(two_rasts, fun = cor) # correlation -- option 2 two_rasts_df = as.data.frame(two_rasts) cor(two_rasts_df$ndvi, two_rasts_df$ndwi) ## ----04-ex-e8--------------------------------------------------------------------------------------- # Fetch the DEM data for Spain spain_dem = geodata::elevation_30s(country = "Spain", path = ".", mask = FALSE) # Reduce the resolution by a factor of 20 to speed up calculations spain_dem = aggregate(spain_dem, fact = 20) # According to the documentation, terra::distance() will calculate distance # for all cells that are NA to the nearest cell that are not NA. To calculate # distance to the coast, we need a raster that has NA values over land and any # other value over water water_mask = is.na(spain_dem) water_mask[water_mask == 0] = NA # Use the distance() function on this mask to get distance to the coast distance_to_coast = distance(water_mask) # convert distance into km distance_to_coast_km = distance_to_coast / 1000 # Plot the result plot(distance_to_coast_km, main = "Distance to the coast (km)") ## ----04-ex-e9--------------------------------------------------------------------------------------- # now let's weight each 100 altitudinal meters by an additional distance of 10 km distance_to_coast_km2 = distance_to_coast_km + ((spain_dem / 100) * 10) # plot the result plot(distance_to_coast_km2) # visualize the difference plot(distance_to_coast_km - distance_to_coast_km2) ================================================ FILE: code/chapters/_05-ex.R ================================================ ## ----05-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(terra) library(dplyr) library(spData) library(spDataLarge) ## ----05-ex-e1--------------------------------------------------------------------------------------- plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.5)) plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.05)) # Starts to breakdown here at 0.5% of the points: plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.005)) # At this point no further simplification changes the result plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.0005)) plot(rmapshaper::ms_simplify(st_geometry(nz), keep = 0.00005)) plot(st_simplify(st_geometry(nz), dTolerance = 100)) plot(st_simplify(st_geometry(nz), dTolerance = 1000)) # Starts to breakdown at 10 km: plot(st_simplify(st_geometry(nz), dTolerance = 10000)) plot(st_simplify(st_geometry(nz), dTolerance = 100000)) plot(st_simplify(st_geometry(nz), dTolerance = 100000, preserveTopology = TRUE)) # Problem: st_simplify returns POLYGON and MULTIPOLYGON results, affecting plotting # Cast into a single geometry type to resolve this nz_simple_poly = st_simplify(st_geometry(nz), dTolerance = 10000) |> st_sfc() |> st_cast("POLYGON") nz_simple_multipoly = st_simplify(st_geometry(nz), dTolerance = 10000) |> st_sfc() |> st_cast("MULTIPOLYGON") plot(nz_simple_poly) length(nz_simple_poly) nrow(nz) ## ----05-ex-e2--------------------------------------------------------------------------------------- canterbury = nz[nz$Name == "Canterbury", ] cant_buff = st_buffer(canterbury, 100) nz_height_near_cant = nz_height[cant_buff, ] nrow(nz_height_near_cant) # 75 - 5 more ## ----05-ex-e3--------------------------------------------------------------------------------------- cant_cent = st_centroid(canterbury) nz_centre = st_centroid(st_union(nz)) st_distance(cant_cent, nz_centre) # 234 km ## ----05-ex-e4--------------------------------------------------------------------------------------- world_sfc = st_geometry(world) world_sfc_mirror = world_sfc * c(1, -1) plot(world_sfc) plot(world_sfc_mirror) us_states_sfc = st_geometry(us_states) us_states_sfc_mirror = us_states_sfc * c(1, -1) plot(us_states_sfc) plot(us_states_sfc_mirror) ## nicer plot # library(ggrepel) # us_states_sfc_mirror_labels = st_centroid(us_states_sfc_mirror) |> # st_coordinates() |> # as_data_frame() |> # mutate(name = us_states$NAME) # us_states_sfc_mirror_sf = st_set_geometry(us_states, us_states_sfc_mirror) # ggplot(data = us_states_sfc_mirror_sf) + # geom_sf(color = "white") + # geom_text_repel(data = us_states_sfc_mirror_labels, mapping = aes(X, Y, label = name), size = 3, min.segment.length = 0) + # theme_void() ## ----05-ex-e5a, echo=FALSE-------------------------------------------------------------------------- b = st_sfc(st_point(c(0, 1)), st_point(c(1, 1))) # create 2 points b = st_buffer(b, dist = 1) # convert points to circles x = b[1] y = b[2] bb = st_bbox(st_union(x, y)) box = st_as_sfc(bb) set.seed(2017) p = st_sample(x = box, size = 10) ## ----05-ex-e5--------------------------------------------------------------------------------------- p_in_y = p[y] p_in_xy = p_in_y[x] x_and_y = st_intersection(x, y) p[x_and_y] ## ----05-ex-e6--------------------------------------------------------------------------------------- us_states2163 = st_transform(us_states, "EPSG:2163") us_states_bor = st_cast(us_states2163, "MULTILINESTRING") us_states_bor$borders = st_length(us_states_bor) arrange(us_states_bor, borders) arrange(us_states_bor, -borders) ## ----05-ex-e7--------------------------------------------------------------------------------------- srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) rast_template = rast(ext(srtm), res = 0.01) srtm_resampl1 = resample(srtm, y = rast_template, method = "bilinear") srtm_resampl2 = resample(srtm, y = rast_template, method = "near") srtm_resampl3 = resample(srtm, y = rast_template, method = "cubic") srtm_resampl4 = resample(srtm, y = rast_template, method = "cubicspline") srtm_resampl5 = resample(srtm, y = rast_template, method = "lanczos") srtm_resampl_all = c(srtm_resampl1, srtm_resampl2, srtm_resampl3, srtm_resampl4, srtm_resampl5) plot(srtm_resampl_all) # differences plot(srtm_resampl_all - srtm_resampl1, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl2, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl3, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl4, range = c(-300, 300)) plot(srtm_resampl_all - srtm_resampl5, range = c(-300, 300)) ================================================ FILE: code/chapters/_06-ex.R ================================================ ## ----06-ex-e0, message=FALSE, include=TRUE---------------------------------------------------------- library(sf) library(terra) library(spData) zion_points_path = system.file("vector/zion_points.gpkg", package = "spDataLarge") zion_points = read_sf(zion_points_path) srtm = rast(system.file("raster/srtm.tif", package = "spDataLarge")) ch = st_combine(zion_points) |> st_convex_hull() |> st_as_sf() ## ----06-ex-e1--------------------------------------------------------------------------------------- plot(srtm) plot(st_geometry(zion_points), add = TRUE) plot(ch, add = TRUE) srtm_crop1 = crop(srtm, zion_points) srtm_crop2 = crop(srtm, ch) plot(srtm_crop1) plot(srtm_crop2) srtm_mask1 = mask(srtm, zion_points) srtm_mask2 = mask(srtm, ch) plot(srtm_mask1) plot(srtm_mask2) ## ----06-ex-e2--------------------------------------------------------------------------------------- zion_points_buf = st_buffer(zion_points, dist = 90) plot(srtm) plot(st_geometry(zion_points_buf), add = TRUE) plot(ch, add = TRUE) zion_points_points = extract(srtm, zion_points) zion_points_buffer = extract(srtm, zion_points_buf, fun = "mean") plot(zion_points_points$srtm, zion_points_buffer$srtm) # Bonus # remotes::install_github("isciences/exactextractr") # zion_points_buf_2 = exactextractr::exact_extract(x = srtm, y = zion_points_buf, # fun = "mean") # # plot(zion_points_points$srtm, zion_points_buf_2) # plot(zion_points_buffer$srtm, zion_points_buf_2) ## ----06-ex-e3--------------------------------------------------------------------------------------- nz_height3100 = dplyr::filter(nz_height, elevation > 3100) new_graticule = st_graticule(nz_height3100, datum = "EPSG:2193") plot(st_geometry(nz_height3100), graticule = new_graticule, axes = TRUE) nz_template = rast(ext(nz_height3100), resolution = 3000, crs = crs(nz_height3100)) nz_raster = rasterize(nz_height3100, nz_template, field = "elevation", fun = "length") plot(nz_raster) plot(st_geometry(nz_height3100), add = TRUE) nz_raster2 = rasterize(nz_height3100, nz_template, field = "elevation", fun = max) plot(nz_raster2) plot(st_geometry(nz_height3100), add = TRUE) ## ----06-ex-e4--------------------------------------------------------------------------------------- nz_raster_low = raster::aggregate(nz_raster, fact = 2, fun = sum, na.rm = TRUE) res(nz_raster_low) nz_resample = resample(nz_raster_low, nz_raster) plot(nz_raster_low) plot(nz_resample) # the results are spread over a greater area and there are border issues plot(nz_raster) ## Advantages: ## ## - lower memory use ## - faster processing ## - good for viz in some cases ## ## Disadvantages: ## ## - removes geographic detail ## - adds another processing step ## ----06-ex-e5--------------------------------------------------------------------------------------- grain = rast(system.file("raster/grain.tif", package = "spData")) ## ----06-ex-e5-2------------------------------------------------------------------------------------- grain_poly = as.polygons(grain) |> st_as_sf() levels(grain) clay = dplyr::filter(grain_poly, grain == "clay") plot(clay) ## Advantages: ## ## - can be used to subset other vector objects ## - can do affine transformations and use sf/dplyr verbs ## ## Disadvantages: ## ## - better consistency ## - fast processing on some operations ## - functions developed for some domains ================================================ FILE: code/chapters/_07-ex.R ================================================ ## ----07-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(terra) library(spData) ## ----07-ex-e1--------------------------------------------------------------------------------------- st_crs(nz) nz_wgs = st_transform(nz, "EPSG:4326") nz_crs = st_crs(nz) nz_wgs_crs = st_crs(nz_wgs) nz_crs$epsg nz_wgs_crs$epsg st_bbox(nz) st_bbox(nz_wgs) nz_wgs_NULL_crs = st_set_crs(nz_wgs, NA) nz_27700 = st_transform(nz_wgs, "EPSG:27700") par(mfrow = c(1, 3)) plot(st_geometry(nz)) plot(st_geometry(nz_wgs)) plot(st_geometry(nz_wgs_NULL_crs)) # answer: it is fatter in the East-West direction # because New Zealand is close to the South Pole and meridians converge there plot(st_geometry(nz_27700)) par(mfrow = c(1, 1)) ## ----07-ex-e2--------------------------------------------------------------------------------------- # see https://github.com/r-spatial/sf/issues/509 world_tmerc = st_transform(world, "+proj=tmerc") plot(st_geometry(world_tmerc)) world_4326 = st_transform(world_tmerc, "EPSG:4326") plot(st_geometry(world_4326)) ## ----07-ex-e3--------------------------------------------------------------------------------------- con_raster = rast(system.file("raster/srtm.tif", package = "spDataLarge")) con_raster_utm12n = project(con_raster, "EPSG:32612", method = "near") con_raster_utm12n plot(con_raster) plot(con_raster_utm12n) ## ----07-ex-e4--------------------------------------------------------------------------------------- cat_raster = rast(system.file("raster/nlcd.tif", package = "spDataLarge")) cat_raster_wgs84 = project(cat_raster, "EPSG:4326", method = "bilinear") cat_raster_wgs84 plot(cat_raster) plot(cat_raster_wgs84) ================================================ FILE: code/chapters/_08-ex.R ================================================ ## ----08-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(terra) ## Vector formats: Shapefile (old format supported by many programs), GeoPackage (more recent format with better support of attribute data) and GeoJSON (common format for web mapping). ## ## Raster formats: GeoTiff, Arc ASCII, R-raster (see book for descriptions). ## ## Database formats: PostGIS, SQLite, FileGDB (see book for details). ## `st_read()` prints outputs and keeps strings as text strings (`st_read()` creates factors). This can be seen from the source code of `read_sf()`, which show's it wraps `st_read()`: ## ----08-ex-e2--------------------------------------------------------------------------------------- read_sf ## ----08-ex-e3--------------------------------------------------------------------------------------- c_h = read.csv(system.file("misc/cycle_hire_xy.csv", package = "spData")) |> st_as_sf(coords = c("X", "Y")) c_h ## ----08-ex-e4--------------------------------------------------------------------------------------- library(rnaturalearth) germany_borders = ne_countries(country = "Germany", returnclass = "sf") plot(germany_borders) st_write(germany_borders, "germany_borders.gpkg") ## ----08-ex-e5--------------------------------------------------------------------------------------- library(geodata) gmmt = worldclim_global(var = "tmin", res = 5, path = tempdir()) names(gmmt) plot(gmmt) gmmt_june = terra::subset(gmmt, "wc2.1_5m_tmin_06") plot(gmmt_june) writeRaster(gmmt_june, "tmin_june.tif") ## ----08-ex-e6--------------------------------------------------------------------------------------- png(filename = "germany.png", width = 350, height = 500) plot(st_geometry(germany_borders), axes = TRUE, graticule = TRUE) dev.off() ## ----08-ex-e7, eval=FALSE--------------------------------------------------------------------------- ## library(mapview) ## mapview_obj = mapview(c_h, zcol = "nbikes", legend = TRUE) ## mapshot(mapview_obj, file = "cycle_hire.html") ================================================ FILE: code/chapters/_10-ex.R ================================================ ## ----10-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(terra) ## --------------------------------------------------------------------------------------------------- library(qgisprocess) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) slope = terrain(dem, "slope", unit = "degrees") aspect = terrain(dem, "aspect", unit = "degrees") qgis_algo = qgis_algorithms() grep("r.sun", qgis_algo$algorithm, value = TRUE) alg = "grass7:r.sun.incidout" qgis_show_help(alg) dem_sun = qgis_run_algorithm(alg, elevation = dem, aspect = aspect, slope = slope, day = 80, time = 11) dem_sun # output global (total) irradiance/irradiation [W.m-2] for given time gsi_dem = qgis_as_terra(dem_sun$glob_rad) plot(dem) plot(gsi_dem) ## --------------------------------------------------------------------------------------------------- library(Rsagacmd) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") swi = saga$ta_hydrology$saga_wetness_index tidy(swi) swi_results = swi(dem, area_type = 0, slope_type = 1) swi_results_all = rast(swi_results) plot(swi_results_all[["area"]]) plot(swi_results_all[["slope"]]) ## --------------------------------------------------------------------------------------------------- library(Rsagacmd) saga = saga_gis(raster_backend = "terra", vector_backend = "sf") ndvi = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) sg = saga$imagery_segmentation$seed_generation ndvi_seeds = sg(ndvi, band_width = 2) plot(ndvi_seeds$seed_grid) srg = saga$imagery_segmentation$seeded_region_growing ndvi_srg = srg(ndvi_seeds$seed_grid, ndvi, method = 1) plot(ndvi_srg$segments) ndvi_segments = as.polygons(ndvi_srg$segments) |> st_as_sf() # extract values ndvi_segments_vals = extract(ndvi, ndvi_segments, fun = "mean") ndvi_segments = cbind(ndvi_segments, ndvi_segments_vals) # k-means ks = kmeans(ndvi_segments[["ndvi"]], centers = 6) ndvi_segments$k = ks$cluster # merge polygons library(dplyr) ndvi_segments2 = ndvi_segments |> group_by(k) |> summarise() # visualize results library(tmap) tm1 = tm_shape(ndvi) + tm_raster(style = "cont", palette = "PRGn", title = "NDVI", n = 7) + tm_shape(ndvi_segments2) + tm_borders(col = "red") + tm_layout(legend.outside = TRUE) tm2 = tm_shape(ndvi_segments2) + tm_polygons(col = "k", style = "cat", palette = "Set1") + tm_layout(legend.outside = TRUE) tmap_arrange(tm1, tm2) ## --------------------------------------------------------------------------------------------------- library(rgrass) dem = rast(system.file("raster/dem.tif", package = "spDataLarge")) data(random_points, package = "spDataLarge") random_point = random_points[sample(1:nrow(random_points), 1), ] link2GI::linkGRASS(dem) write_RAST(dem, vname = "dem") execGRASS("r.viewshed", input = "dem", coordinates = sf::st_coordinates(random_point), output = "view", flags = "overwrite") out = read_RAST("view") # simple viz plot(out) # hillshade viz hs = shade(slope = terrain(dem, "slope", unit = "radians"), aspect = terrain(dem, "aspect", unit = "radians")) library(tmap) tm_shape(hs) + tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + tm_shape(dem) + tm_raster(alpha = 0.6, palette = hcl.colors(25, "Geyser"), legend.show = FALSE) + tm_shape(out) + tm_raster(style = "cont", legend.show = FALSE) + tm_shape(random_point) + tm_symbols(col = "black") + tm_layout(frame = FALSE) # mapview viz library(mapview) mapview(out, col = "white", map.type = "Esri.WorldImagery") + mapview(point) ## --------------------------------------------------------------------------------------------------- link2GI::linkGDAL() our_filepath = system.file("raster/elev.tif", package = "spData") cmd = paste("gdalinfo", our_filepath) system(cmd) # Driver, file path, dimensions, CRS, resolution, bounding box, summary statistics ## --------------------------------------------------------------------------------------------------- our_filepath = system.file("raster/elev.tif", package = "spData") cmd2 = paste("gdalwarp", our_filepath, "new_elev.tif", "-tr 1 1", "-r bilinear") system(cmd2) ## --------------------------------------------------------------------------------------------------- library(RPostgreSQL) conn = dbConnect(drv = PostgreSQL(), dbname = "rtafdf_zljbqm", host = "db.qgiscloud.com", port = "5432", user = "rtafdf_zljbqm", password = "d3290ead") query = paste( "SELECT *", "FROM highways", "WHERE state = 'CA';") ca_highways = read_sf(conn, query = query, geom = "wkb_geometry") plot(st_geometry(ca_highways)) ## --------------------------------------------------------------------------------------------------- library(rstac) library(gdalcubes) ?spDataLarge::ndvi.tif ndvi1 = rast(system.file("raster/ndvi.tif", package = "spDataLarge")) bbox1 = as.numeric(st_bbox(project(ndvi1, "EPSG:4326"))) # get data s = stac("https://earth-search.aws.element84.com/v0") items = s |> stac_search(collections = "sentinel-s2-l2a-cogs", bbox = bbox1, datetime = "2020-08-01/2020-10-31") |> post_request() |> items_fetch() collection = stac_image_collection(items$features, property_filter = function(x) {x[["eo:cloud_cover"]] < 10}) v = cube_view(srs = "EPSG:32717", extent = collection, dx = xres(ndvi1), dy = yres(ndvi1), dt = "P1D") # calculate ndvi ndvi2 = raster_cube(collection, v) |> select_bands(c("B04", "B08")) |> apply_pixel("(B08-B04)/(B08+B04)", "NDVI") # write results to file gdalcubes_options(parallel = 2) gdalcubes::write_tif(ndvi2, dir = ".", prefix = "ndvi2") # unify two datasets ndvi2 = rast("ndvi22020-10-10.tif") plot(ndvi2) ndvi2 = resample(ndvi2, ndvi1, method = "bilinear") plot(ndvi2) # vizualize the final results ndvi_all = c(ndvi1, ndvi2) names(ndvi_all) = c("y2000", "y2020") library(tmap) tm_shape(ndvi_all) + tm_raster(style = "cont") ================================================ FILE: code/chapters/_12-ex.R ================================================ ## The solutions assume the following packages are attached (other packages will be attached when needed): ## ----12-ex-e0, message=FALSE, warning=FALSE--------------------------------------------------------- library(dplyr) # library(kernlab) library(mlr3) library(mlr3learners) library(mlr3extralearners) library(mlr3spatiotempcv) library(mlr3tuning) library(qgisprocess) library(terra) # library(rlang) library(sf) library(tmap) ## ----12-ex-e1-1, eval=FALSE------------------------------------------------------------------------- ## # attach data ## dem = terra::rast(system.file("raster/ta.tif", package = "spDataLarge"))$elev ## ## algs = qgisprocess::qgis_algorithms() ## dplyr::filter(algs, grepl("curvature", algorithm)) ## alg = "saga:slopeaspectcurvature" ## qgisprocess::qgis_show_help(alg) ## qgisprocess::qgis_arguments(alg) ## # terrain attributes (ta) ## out_nms = paste0(tempdir(), "/", c("slope", "cplan", "cprof"), ## ".sdat") ## args = rlang::set_names(out_nms, c("SLOPE", "C_PLAN", "C_PROF")) ## out = qgis_run_algorithm(alg, ELEVATION = dem, METHOD = 6, ## UNIT_SLOPE = "[1] degree", ## !!!args, ## .quiet = TRUE ## ) ## ta = out[names(args)] |> unlist() |> terra::rast() ## names(ta) = c("slope", "cplan", "cprof") ## # catchment area ## dplyr::filter(algs, grepl("[Cc]atchment", algorithm)) ## alg = "saga:catchmentarea" ## qgis_show_help(alg) ## qgis_arguments(alg) ## carea = qgis_run_algorithm(alg, ## ELEVATION = dem, ## METHOD = 4, ## FLOW = file.path(tempdir(), "carea.sdat")) ## # transform carea ## carea = terra::rast(carea$FLOW[1]) ## log10_carea = log10(carea) ## names(log10_carea) = "log10_carea" ## # add log_carea and dem to the terrain attributes ## ta = c(ta, dem, log10_carea) ## ----12-ex-e2, eval=FALSE--------------------------------------------------------------------------- ## # attach terrain attribute raster stack (in case you have skipped the previous ## # exercise) ## data("lsl", package = "spDataLarge") ## ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) ## lsl = select(lsl, x, y, lslpts) ## # extract values to points, i.e., create predictors ## lsl[, names(ta)] = terra::extract(ta, lsl[, c("x", "y")]) |> ## select(-ID) ## ----12-ex-e3, eval=FALSE--------------------------------------------------------------------------- ## # attach data (in case you have skipped exercises 1) and 2) ## # landslide points with terrain attributes and terrain attribute raster stack ## data("lsl", "study_mask", package = "spDataLarge") ## ta = terra::rast(system.file("raster/ta.tif", package = "spDataLarge")) ## ## # fit the model ## fit = glm(lslpts ~ slope + cplan + cprof + elev + log10_carea, ## data = lsl, family = binomial()) ## ## # make the prediction ## pred = terra::predict(object = ta, model = fit, type = "response") ## ## # make the map ## lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) ## lsl_sf = sf::st_as_sf(lsl, coords = c("x", "y"), crs = 32717) ## hs = terra::shade(ta$slope * pi / 180, ## terra::terrain(ta$elev, v = "aspect", unit = "radians")) ## rect = tmaptools::bb_poly(raster::raster(hs)) ## bbx = tmaptools::bb(raster::raster(hs), xlim = c(-0.00001, 1), ## ylim = c(-0.00001, 1), relative = TRUE) ## # white raster to only plot the axis ticks, otherwise gridlines would be visible ## tm_shape(hs, bbox = bbx) + ## tm_grid(col = "black", n.x = 1, n.y = 1, labels.inside.frame = FALSE, ## labels.rot = c(0, 90)) + ## tm_raster(palette = "white", legend.show = FALSE) + ## # hillshade ## tm_shape(terra::mask(hs, study_mask), bbox = bbx) + ## tm_raster(palette = gray(0:100 / 100), n = 100, ## legend.show = FALSE) + ## # prediction raster ## tm_shape(terra::mask(pred, study_mask)) + ## tm_raster(alpha = 0.5, palette = "Reds", n = 6, legend.show = TRUE, ## title = "Susceptibility") + ## # rectangle and outer margins ## qtm(rect, fill = NULL) + ## tm_layout(outer.margins = c(0.04, 0.04, 0.02, 0.02), frame = FALSE, ## legend.position = c("left", "bottom"), ## legend.title.size = 0.9) ## ----12-ex-e4, eval=FALSE--------------------------------------------------------------------------- ## # attach data (in case you have skipped exercises 1) and 2) ## data("lsl", package = "spDataLarge") # landslide points with terrain attributes ## ## # create task ## task = TaskClassifST$new( ## id = "lsl_ecuador", ## backend = mlr3::as_data_backend(lsl), target = "lslpts", positive = "TRUE", ## extra_args = list( ## coordinate_names = c("x", "y"), ## coords_as_features = FALSE, ## crs = 32717) ## ) ## ## # construct learners (for all subsequent exercises) ## # GLM ## lrn_glm = lrn("classif.log_reg", predict_type = "prob") ## lrn_glm$fallback = lrn("classif.featureless", predict_type = "prob") ## ## # SVM ## # construct SVM learner (using ksvm function from the kernlab package) ## lrn_ksvm = lrn("classif.ksvm", predict_type = "prob", kernel = "rbfdot", ## type = "C-svc") ## lrn_ksvm$fallback = lrn("classif.featureless", predict_type = "prob") ## ## # specify nested resampling and adjust learner accordingly ## # five spatially disjoint partitions ## tune_level = rsmp("spcv_coords", folds = 5) ## # use 50 randomly selected hyperparameters ## terminator = trm("evals", n_evals = 50) ## tuner = tnr("random_search") ## # define the outer limits of the randomly selected hyperparameters ## ps = ps( ## C = p_dbl(lower = -12, upper = 15, trafo = function(x) 2^x), ## sigma = p_dbl(lower = -15, upper = 6, trafo = function(x) 2^x) ## ) ## at_ksvm = AutoTuner$new( ## learner = lrn_ksvm, ## resampling = tune_level, ## measure = msr("classif.auc"), ## search_space = ps, ## terminator = terminator, ## tuner = tuner ## ) ## ## # QDA ## lrn_qda = lrn("classif.qda", predict_type = "prob") ## lrn_qda$fallback = lrn("classif.featureless", predict_type = "prob") ## ## # SVM without tuning hyperparameters ## vals = lrn_ksvm$param_set$values ## lrn_ksvm_notune = lrn_ksvm$clone() ## lrn_ksvm_notune$param_set$values = c(vals, C = 1, sigma = 1) ## ## # define resampling strategies ## # specify the reampling method, i.e. spatial CV with 100 repetitions and 5 folds ## # -> in each repetition dataset will be splitted into five folds ## # method: repeated_spcv_coords -> spatial partioning ## rsmp_sp = rsmp("repeated_spcv_coords", folds = 5, repeats = 100) ## # method: repeated_cv -> non-spatial partitioning ## rsmp_nsp = rsmp("repeated_cv", folds = 5, repeats = 100) ## ## # (spatial) cross-validataion ## #**************************** ## # create your design ## grid = benchmark_grid(tasks = task, ## learners = list(lrn_glm, at_ksvm, lrn_qda, ## lrn_ksvm_notune), ## resamplings = list(rsmp_sp, rsmp_nsp)) ## # execute the cross-validation ## library(future) ## # execute the outer loop sequentially and parallelize the inner loop ## future::plan(list("sequential", "multisession"), ## workers = floor(availableCores() / 2)) ## set.seed(021522) ## bmr = benchmark(grid, ## store_backends = FALSE, ## store_models = FALSE, ## encapsulate = "evaluate") ## # stop parallelization ## future:::ClusterRegistry("stop") ## # save your result, e.g. to ## # saveRDS(bmr, file = "extdata/12-bmr.rds") ## ## # plot your result ## autoplot(bmr, measure = msr("classif.auc")) ## ----12-ex-e5, eval=FALSE--------------------------------------------------------------------------- ## # attach data (in case you have skipped exercise 4) ## bmr = readRDS("extdata/12-bmr.rds") ## ## # plot your result ## autoplot(bmr, measure = msr("classif.auc")) ## # QDA has higher AUROC values on average than GLM which indicates moderately ## # non-linear boundaries ## ----12-ex-e6, eval=FALSE--------------------------------------------------------------------------- ## # attach data (in case you have skipped exercise 4) ## bmr = readRDS("extdata/12-bmr.rds") ## # plot your result ## autoplot(bmr, measure = msr("classif.auc")) ================================================ FILE: code/chapters/_13-ex.R ================================================ ## ----13-ex-e0, message=FALSE------------------------------------------------------------------------ library(sf) library(spDataLarge) ## ----13-e1, eval=FALSE, echo=FALSE------------------------------------------------------------------ ## sum(desire_lines$car_driver) / sum(desire_lines$all) ## # 57% ## desire_lines_5km_plus = desire_lines |> ## filter(distance_km > 5) ## # Just over are half ar 5km+, 54%: ## nrow(desire_lines_5km_plus) / nrow(desire_lines) ## # 71 of 5km+ trips are made by car ## sum(desire_lines_5km_plus$car_driver) / sum(desire_lines_5km_plus$all) ## ## desire_lines_driving = desire_lines |> ## mutate(`Proportion driving` = car_driver / all) |> ## filter(`Proportion driving` > 0.5) ## nrow(desire_lines_5km_plus_driving) / nrow(desire_lines) ## ## desire_lines_5km_less_50_pct_driving = desire_lines |> ## filter(distance_km <= 5) |> ## mutate(`Proportion driving` = car_driver / all) |> ## filter(`Proportion driving` > 0.5) ## desire_lines_5km_less_50_pct_driving |> ## tm_shape() + ## tm_lines("Proportion driving") ## ----13-transport-29, eval=FALSE, echo=FALSE-------------------------------------------------------- ## sum(st_length(route_network_no_infra)) ## # 104193.6 [m] ## # Just over 100 km ## ----13-transport-30, echo=FALSE, eval=FALSE-------------------------------------------------------- ## sum(routes_short_scenario$all) / sum(desire_lines$all) # 13% ## d_intersect = desire_lines[routes_short_scenario, , op = st_crosses] ## sum(d_intersect$all) / sum(desire_lines$all) # 88% ================================================ FILE: code/chapters/_15-ex.R ================================================ ## ----15-ex-e0, message=FALSE, warning=FALSE---------------------------------------------------------------------- library(sf) library(terra) library(data.table) library(dplyr) library(future) library(ggplot2) library(lgr) library(mlr3) library(mlr3learners) library(mlr3spatiotempcv) library(mlr3tuning) library(mlr3viz) library(progressr) library(qgisprocess) library(tictoc) library(vegan) ## ----15-ex-e1, message=FALSE------------------------------------------------------------------------------------- data("comm", package = "spDataLarge") pa = vegan::decostand(comm, "pa") pa = pa[rowSums(pa) != 0, ] comm = comm[rowSums(comm) != 0, ] set.seed(25072018) nmds_pa = vegan::metaMDS(comm = pa, k = 4, try = 500) nmds_per = vegan::metaMDS(comm = comm, k = 4, try = 500) nmds_pa$stress nmds_per$stress ## The NMDS using the presence-absence values yields a better result (`nmds_pa$stress`) than the one using percentage data (`nmds_per$stress`). ## This might seem surprising at first sight. ## On the other hand, the percentage matrix contains both more information and more noise. ## Another aspect is how the data was collected. ## Imagine a botanist in the field. ## It might seem feasible to differentiate between a plant which has a cover of 5% and another species that covers 10%. ## However, what about a herbal species that was only detected three times and consequently has a very tiny cover, e.g., 0.0001%. ## Maybe another herbal species was detected 6 times, is its cover then 0.0002%? ## The point here is that percentage data as specified during a field campaign might reflect a precision that the data does not have. ## This again introduces noise which in turn will worsen the ordination result. ## Still, it is a valuable information if one species had a higher frequency or coverage in one plot than another compared to just presence-absence data. ## One compromise would be to use a categorical scale such as the Londo scale. ## ----15-ex-e2---------------------------------------------------------------------------------------------------- # first compute the terrain attributes we have also used in the chapter library(dplyr) library(terra) library(qgisprocess) library(vegan) data("comm", "random_points", package = "spDataLarge") dem = terra::rast(system.file("raster/dem.tif", package = "spDataLarge")) ndvi = terra::rast(system.file("raster/ndvi.tif", package = "spDataLarge")) # use presence-absence matrix and get rid of empty pa = vegan::decostand(comm, "pa") pa = pa[rowSums(pa) != 0, ] # compute environmental predictors (ep) catchment slope and catchment area ep = qgisprocess::qgis_run_algorithm( alg = "saga:sagawetnessindex", DEM = dem, SLOPE_TYPE = 1, SLOPE = tempfile(fileext = ".sdat"), AREA = tempfile(fileext = ".sdat"), .quiet = TRUE) # read in catchment area and catchment slope ep = ep[c("AREA", "SLOPE")] |> unlist() |> terra::rast() # assign proper names names(ep) = c("carea", "cslope") # make sure all rasters share the same origin origin(ep) = origin(dem) # add dem and ndvi to the multi-layer SpatRaster object ep = c(dem, ndvi, ep) ep$carea = log10(ep$carea) # compute the curvatures qgis_show_help("grass7:r.slope.aspect") curvs = qgis_run_algorithm( "grass7:r.slope.aspect", elevation = dem, .quiet = TRUE) # adding curvatures to ep curv_nms = c("pcurvature", "tcurvature") curvs = curvs[curv_nms] |> unlist() |> terra::rast() curvs = terra::app(curvs, as.numeric) names(curvs) = curv_nms ep = c(ep, curvs) random_points[, names(ep)] = # terra::extract adds an ID column, we don't need terra::extract(ep, random_points) |> select(-ID) elev = dplyr::filter(random_points, id %in% rownames(pa)) %>% dplyr::pull(dem) # rotating NMDS in accordance with altitude (proxy for humidity) rotnmds = MDSrotate(nmds_pa, elev) # extracting the first two axes sc = vegan::scores(rotnmds, choices = 1:2, display = "sites") rp = data.frame(id = as.numeric(rownames(sc)), sc = sc[, 1]) # join the predictors (dem, ndvi and terrain attributes) rp = dplyr::inner_join(random_points, rp, by = "id") # saveRDS(rp, "extdata/15-rp_exercises.rds") ## ----15-ex-e3, message=FALSE------------------------------------------------------------------------------------- library(dplyr) library(future) library(mlr3) library(mlr3spatiotempcv) library(mlr3learners) library(mlr3viz) library(paradox) library(ranger) # in case you have not run the previous exercises, run: # rp = readRDS("extdata/15-rp_exercises.rds") # define the task task = mlr3spatiotempcv::as_task_regr_st( select(rp, -id, -spri), target = "sc", id = "mongon") # define the learners mlr3::mlr_learners # linear model lrn_lm = mlr3::lrn("regr.lm", predict_type = "response") # random forest lrn_rf = mlr3::lrn("regr.ranger", predict_type = "response") # now define the AutoTuner of the random forest search_space = paradox::ps( mtry = paradox::p_int(lower = 1, upper = ncol(task$data()) - 1), sample.fraction = paradox::p_dbl(lower = 0.2, upper = 0.9), min.node.size = paradox::p_int(lower = 1, upper = 10) ) at_rf = mlr3tuning::AutoTuner$new( learner = lrn_rf, # spatial partitioning resampling = mlr3::rsmp("spcv_coords", folds = 5), # performance measure measure = mlr3::msr("regr.rmse"), search_space = search_space, # random search with 50 iterations terminator = mlr3tuning::trm("evals", n_evals = 50), tuner = mlr3tuning::tnr("random_search") ) # define the resampling strategy rsmp_sp = mlr3::rsmp("repeated_spcv_coords", folds = 5, repeats = 100) # create the benchmark design design_grid = mlr3::benchmark_grid( tasks = task, learners = list(lrn_lm, at_rf), resamplings = rsmp_sp) print(design_grid) # execute the outer loop sequentially and parallelize the inner loop future::plan(list("sequential", "multisession"), workers = floor(future::availableCores() / 2)) set.seed(10112022) # reduce verbosity lgr::get_logger("mlr3")$set_threshold("warn") lgr::get_logger("bbotk")$set_threshold("info") # BE CAREFUL: Running the benchmark might take quite some time # therefore, we have stored the result of the benchmarking in # extdata/15-bmr-exercises.rds (see below) tictoc::tic() progressr::with_progress(expr = { bmr = mlr3::benchmark( design = design_grid, # New argument `encapsulate` for `resample()` and `benchmark()` to # conveniently enable encapsulation and also set the fallback learner to the # respective featureless learner. This is simply for convenience, configuring # each learner individually is still possible and allows a more fine-grained # control encapsulate = "evaluate", store_backends = FALSE, store_models = FALSE) }) tictoc::toc() # stop parallelization future:::ClusterRegistry("stop") # we have saved the result as follows # saveRDS(bmr, file = "extdata/15-bmr_exercises.rds") # READ IT IN in in case you don't want to run the spatial CV yourself as it is computationally # demanding # bmr = readRDS("extdata/15-bmr_exercises.rds") # mean RMSE bmr$aggregate(measures = msr("regr.rmse")) # or computed manually agg = bmr$aggregate(measures = msr("regr.rmse")) # lm performs slightly better when considering the mean rmse purrr::map(agg$resample_result, ~ mean(.$score(msr("regr.rmse"))$regr.rmse)) # however, looking at the median, the random forest performs slightly better purrr::map(agg$resample_result, ~ median(.$score(msr("regr.rmse"))$regr.rmse)) # make a boxplot (when using autoplot, mlr3viz needs to be attached!) library(mlr3viz) autoplot(bmr, measure = msr("regr.rmse")) # or doing it "manually" # extract the AUROC values and put them into one data.table d = purrr::map_dfr(agg$resample_result, ~ .$score(msr("regr.rmse"))) # create the boxplots library(ggplot2) ggplot(data = d, mapping = aes(x = learner_id, y = regr.rmse)) + geom_boxplot(fill = c("lightblue2", "mistyrose2")) + theme_bw() + labs(y = "RMSE", x = "model") ## In fact, `lm` performs at least as good the random forest model, and thus should be preferred since it is much easier to understand and computationally much less demanding (no need for fitting hyperparameters). ## But keep in mind that the used dataset is small in terms of observations and predictors and that the response-predictor relationships are also relatively linear. ================================================ FILE: code/chapters/_404.R ================================================ ## ----c404, echo=FALSE, message=FALSE, fig.asp=1----------------------------------------------------- library(tmap) library(sf) null_island = st_point(c(0, 0)) null_island = st_sfc(null_island, crs = 4326) null_island = st_sf(name = "Null Island", geom = null_island) tm_shape(null_island) + tm_graticules(labels.col = "gray40") + tm_text("name", size = 5, fontface = "italic") + tm_layout(bg.color = "lightblue", main.title = "You are here:", main.title.color = "gray40") ================================================ FILE: code/chapters/index.R ================================================ ## ----index-1, echo=FALSE---------------------------------------------------------------------------- is_on_ghactions = identical(Sys.getenv("GITHUB_ACTIONS"), "true") is_online = curl::has_internet() is_html = knitr::is_html_output() ## ---- echo = FALSE---------------------------------------------------------------------------------- # google scholar metadata library(metathis) if (is_html) { meta() |> meta_google_scholar( title = "Geocomputation with R", author = c("Robin Lovelace", "Jakub Nowosad", "Jannes Muenchow"), publication_date = "2019", isbn = "9780203730058" ) } ## # Welcome {-} ## ## This is the online home of *Geocomputation with R*, a book on geographic data analysis, visualization and modeling. ## ## The geocompr book cover ## ## **Note**: The first edition of the book has been published by CRC Press in the [R Series](https://www.routledge.com/Chapman--HallCRC-The-R-Series/book-series/CRCTHERSER). ## You can buy the book from [CRC Press](https://www.routledge.com/9781138304512), or [Amazon](https://www.amazon.com/Geocomputation-R-Robin-Lovelace-dp-0367670577/dp/0367670577/), and see the archived **First Edition** hosted on [bookdown.org](https://bookdown.org/robinlovelace/geocompr/). ## ## Inspired by the Free and Open Source Software for Geospatial ([FOSS4G](https://foss4g.org/)) movement, the code and prose underlying this book are open, ensuring that the content is reproducible, transparent, and accessible. ## Hosting the source code on [GitHub](https://github.com/Robinlovelace/geocompr/) allows anyone to interact with the project by opening issues or contributing new content and typo fixes for the benefit of everyone. ## ## [![](https://img.shields.io/github/stars/robinlovelace/geocompr?style=for-the-badge)](https://github.com/robinlovelace/geocompr) ## [![](https://img.shields.io/github/contributors/robinlovelace/geocompr?style=for-the-badge)](https://github.com/Robinlovelace/geocompr/graphs/contributors) ## ## The online version of the book is hosted at [geocompr.robinlovelace.net](https://geocompr.robinlovelace.net) and kept up-to-date by [GitHub Actions](https://github.com/Robinlovelace/geocompr/actions). ## Its current 'build status' as follows: ## ## [![Actions](https://github.com/Robinlovelace/geocompr/workflows/Render/badge.svg)](https://github.com/Robinlovelace/geocompr/actions) ## Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. ## ## ## How to contribute? {-} ## ## **bookdown** makes editing a book as easy as editing a wiki, provided you have a GitHub account ([sign-up at github.com](https://github.com/join)). ## Once logged-in to GitHub, click on the 'Edit this page' icon in the right panel of the book website. ## This will take you to an editable version of the the source [R Markdown](http://rmarkdown.rstudio.com/) file that generated the page you're on. ## ## ## ## To raise an issue about the book's content (e.g. code not running) or make a feature request, check-out the [issue tracker](https://github.com/Robinlovelace/geocompr/issues). ## ## Maintainers and contributors must follow this repository’s [CODE OF CONDUCT](https://github.com/Robinlovelace/geocompr/blob/main/CODE_OF_CONDUCT.md). ## ## ## Reproducibility {-} ## ## The quickest way to reproduce the contents of the book if you're new to geographic data in R may be in the web browser, thanks to [Binder](https://mybinder.org/). ## Clicking on the link below should open a new window containing RStudio Server in your web browser, enabling you to open chapter files and running code chunks to test that the code is reproducible. ## ## [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/robinlovelace/geocompr/main?urlpath=rstudio) ## ## If you see something like the image below, congratulations, it worked! ## You can start exploring Geocomputation with R in a cloud-based environment, noting [mybinder.org user guidelines](https://mybinder.readthedocs.io/en/latest/about/user-guidelines.html)): ## ## ## To reproduce the code in the book on your own computer, you need a recent version of [R](https://cran.r-project.org/) and up-to-date packages. ## These can be installed using the [**remotes**](https://github.com/r-lib/remotes) package. ## ----index-3, message=FALSE, eval=FALSE, echo=is_html, results='hide'------------------------------- ## install.packages("remotes") ## install.packages('geocompkg', repos = c('https://geocompr.r-universe.dev', 'https://cloud.r-project.org'), dependencies = TRUE, force = TRUE) ## ---- echo=FALSE, eval=FALSE------------------------------------------------------------------------ ## remotes::install_github("nowosad/spData") ## remotes::install_github("nowosad/spDataLarge") ## ## # During development work on the 2nd edition you may also need dev versions of ## # other packages to build the book, e.g.,: ## remotes::install_github("rspatial/terra") ## remotes::install_github("mtennekes/tmap") ## After installing the book's dependencies, you can rebuild the book for testing and educational purposes. ## To do this [download](https://github.com/Robinlovelace/geocompr/archive/refs/heads/main.zip) and unzip or [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) the book's source code. ## After opening the `geocompr.Rproj` project in [RStudio](https://www.rstudio.com/products/rstudio/download/#download) (or opening the folder in another IDE such as [VS Code](https://github.com/REditorSupport/vscode-R)), you should be able to reproduce the contents with the following command: ## ----index-3-1, eval=FALSE, echo=is_html------------------------------------------------------------ ## bookdown::serve_book(".") ## ----index-3-2, echo=FALSE, include=FALSE----------------------------------------------------------- # is geocompkg installed? geocompkg_is_installed = "geocompkg" %in% installed.packages() if (!geocompkg_is_installed){ message( 'geocompkg not installed, run\nremotes::install_github("geocompx/geocompkg") # to install it' ) } ## See the project's [GitHub repo](https://github.com/robinlovelace/geocompr#reproducing-the-book) for full details on reproducing the book. ## ## Getting involved {-} ## ## If you find the project of use and interest, you can get involved in many ways, by: ## ## - Telling people about it ## - [Buying](https://www.amazon.com/Geocomputation-R-Robin-Lovelace-dp-0367670577/dp/0367670577) a copy ## - Helping people get started with open source software for reproducible research in general, and working with geographic data in R in particular (this can be an excellent way to consolidate and build your own skills) ## - Communicating about the book online, via the [#geocompr hashtag](https://twitter.com/hashtag/geocompr) on Twitter (see our [Guestbook at geocompr.github.io](https://geocompr.github.io/guestbook/)) or by letting us know of [courses](https://github.com/geocompr/geocompr.github.io/edit/source/content/guestbook/index.md) using the book ## - [Citing](https://github.com/Robinlovelace/geocompr/raw/main/CITATION.bib) and [linking-to](https://geocompr.robinlovelace.net/) it ## - '[Starring](https://help.github.com/articles/about-stars/)' the [geocompr GitHub repository](https://github.com/robinlovelace/geocompr) ## - Reviewing it, on [Amazon](https://www.amazon.com/Geocomputation-Chapman-Hall-Robin-Lovelace/dp/1138304514/), [Goodreads](https://www.goodreads.com/book/show/42780859-geocomputation-with-r) or elsewhere ## - Asking questions about the content or making suggestion on [GitHub](https://github.com/Robinlovelace/geocompr/issues), [Twitter](https://twitter.com/hashtag/geocompr?src=hashtag_click) or [Discord](https://discord.gg/PMztXYgNxp) ## - Answering questions, or at least responding to people asking for clarification or reproducible examples to demonstrate their question ## - Supporting community translations ## - The Spanish version: https://geocompr.github.io/es/ ## - The French version: https://geocompr.github.io/fr/ ## - The Japanese version: http://babayoshihiko.ddns.net/geo/ ## ## Further details can be found at [github.com/Robinlovelace/geocompr](https://github.com/Robinlovelace/geocompr#geocomputation-with-r). ## ##
## ## The globe icon used in this book was created by [Jean-Marc Viglino](https://github.com/Viglino) and is licensed under [CC-BY 4.0 International](https://github.com/Viglino/font-gis/blob/main/LICENSE-CC-BY.md). ## ## ## \newpage ## ## \vspace*{5cm} ## ## \thispagestyle{empty} ## ## \begin{center} \Large \emph{For Katy } \end{center} ## ## \vspace*{2cm} ## \begin{center} \Large \emph{Dla Jagody} \end{center} ## ## \vspace*{2cm} ## \begin{center} \Large \emph{F{\"u}r meine Katharina und alle unsere Kinder } \end{center} ## ----contrib-preface, include=FALSE----------------------------------------------------------------- contributors = readr::read_csv("extdata/contributors.csv") c_txt = contributors$name c_url = contributors$link c_rmd = paste0("[", c_txt, "](", c_url, ")") contributors_text = paste0(c_txt, collapse = ", ") ================================================ FILE: code/chapters/references.R ================================================ ================================================ FILE: code/de_9im.R ================================================ #' This function visualises sf objects and returns info on the #' types of spatial relationship there is between them #' #' Context: [robinlovelace/geocompr#677](https://github.com/Robinlovelace/geocompr/issues/677) #' #' @examples #' library(sf) #' x = st_sfc(st_polygon(list(rbind(c(0, 0), c(1, 0), c(1, 1), c(0, 0))))) #' y = st_sfc(st_polygon(list(rbind(c(0, 0), c(0, 1), c(1, 1), c(0, 0))))) #' de_9im(x, y) #' p3 = st_sfc(st_polygon(list(rbind(c(0.7, 0.3), c(0.7, 0.95), c(0.9, 0.95), c(0.7, 0.3))))) #' p4 = st_sfc(st_polygon(list(rbind(c(0.6, 0.1), c(0.7, 0.5), c(0.9, 0.5), c(0.6, 0.1))))) #' p5 = st_sfc(st_polygon(list(rbind(c(0, 0.2), c(0, 1), c(0.9, 1), c(0, 0.2))))) #' de_9im(x, p3) de_9im = function(x, y, object_names = c("x", "y"), plot = TRUE, funs = list( "st_intersects", "st_disjoint", "st_touches", "st_crosses", "st_within", "st_contains", "st_contains_properly", "st_overlaps", "st_equals", "st_covers", "st_covered_by" # , # "st_equals_exact" # requuires par argument ), include_relate = TRUE, sparse = FALSE, output = "character", collapse = " ✓\n", tmap = TRUE ) { require("sf") if (is(x, "sfc") && is(y, "sfc")) { x = st_sf(data.frame(Object = object_names[1]), geometry = x) y = st_sf(data.frame(Object = object_names[2]), geometry = y) } xy = rbind(x, y) funs_matched = lapply(funs, match.fun) res = lapply(seq(length(funs)), function(i) { funs_matched[[i]](x, y, sparse = sparse) }) res = unlist(res) if (output == "character") { res = unlist(funs)[res] } res_text2 = "" if (include_relate) { relation = sf::st_relate(x, y) res_text2 = paste0(" \nDE-9IM string: \n", relation) } if (plot) { res_text1 = paste(res, collapse = collapse) collapse_no_break = gsub(pattern = "\\n", replacement = "", x = collapse) res_text1 = paste0(res_text1, collapse_no_break) message("Object x has the following spatial relations to y: ", res_text1, res_text2) if (tmap){ res = de_9im_plot2(xy, label1 = res_text1, label2 = res_text2) } else { res = de_9im_plot(xy, label1 = res_text1, label2 = res_text2) } } res } de_9im_plot = function(xy, label1 = "test", label2 = "", alpha = 0.5, show.legend = FALSE, x = 0.1, y = 0.95, theme = ggplot2::theme_void()) { require("ggplot2", quietly = TRUE) # browser() ggplot(xy) + geom_sf(aes(fill = Object), alpha = alpha, show.legend = show.legend) + annotate("text", x = 0.1, y = 0.95, label = label1, hjust = "left", vjust = "top") + annotate("text", x = 0.1, y = 0.1, label = label2, hjust = "left", vjust = "bottom", fontface = "italic") + theme } de_9im_plot2 = function(xy, label1 = "test", label2 = "", alpha = 0.5, show.legend = FALSE, x = 0.1, y = 0.95, theme = ggplot2::theme_void()) { require("tmap", quietly = TRUE) # browser() # toDo: does not work yet st_crs(xy) = "EPSG:2180" tm_shape(xy) + tm_polygons("Object", fill.legend = tm_legend_hide(), fill_alpha = alpha, fill.scale = tm_scale(values = c("#E36939", "#6673E3"))) + tm_credits(label1, position = c(0.07, 0.62), just = "top") + tm_credits(label2, position = c(0.07, 0.32), fontface = "italic", just = "bottom") + tm_layout(frame = FALSE) } 9 # # Test code to functionalize: # theme_set(new = theme_void()) # g1 = ggplot(ps1) + geom_sf(aes(fill = Object), alpha = 0.5, show.legend = FALSE) # # g1 + annotate("text", x = 0.3, y = 0.9, label = "st_intersects(Polygon1, Polygon2)") # g1 + annotate("text", x = 0.1, y = 0.95, label = "intersects TRUE\ndisjoint FALSE\ntouches TRUE\n", hjust = "left", vjust = "top") # # Try annotating only which type of relations apply # # g1 + annotate("text", x = 0.1, y = 0.95, label = "Relations: intersects, touches", hjust = "left", vjust = "top") # g1an = g1 + # ================================================ FILE: code/extra-pkgs.R ================================================ pkgs = c( "cranlogs", # automated cran-logs "USAboundaries", # for plots of the globe "tidytext" # for word count ) to_install = !pkgs %in% installed.packages() if (any(to_install)) { install.packages(pkgs[to_install]) } gh_pkgs = c( "ropenscilabs/packagemetrics", "ropensci/USAboundariesData" ) remotes::install_github(gh_pkgs) ================================================ FILE: code/front_cover2.R ================================================ remotes::install_github("BlakeRMills/MoMAColors") pkgs = c( "camcorder", "cartogram", "glue", "ggtext", "ggforce", "MoMAColors", # dev version available on github: https://github.com/BlakeRMills/MoMAColors "rnaturalearth", "rnaturalearthdata", "scico", "sf", "showtext", "terra", "tidyterra", "tidyverse", "od" ) pkgs_to_install = pkgs[!pkgs %in% installed.packages()] remotes::install_cran(pkgs_to_install) sapply(pkgs, require, character.only = TRUE) # Set fonts font_add_google("Raleway", "ral") showtext_auto() # Set size gg_record( dir = file.path(tempdir(), "recording"), device = "png", width = 15.6, # I chose the size of the 1st edition book, so the image created here may cover all of the first page height = 23.4, units = "cm", # may also be exported as svg dpi = 600 ) # Cover is made of two different type of maps (1 "buffer" map and 1 "dorling" cartogram) that will be created one after another ############################# # Creation of the first map # ############################# # Let"s start with the 1st map: buffer area around capitals in Africa # Loading data ############## # Vector files of world borders loaded from {rnaturalearth} mp_with_countries = ne_countries(scale = 50, type = "countries", returnclass = "sf") |> st_transform(crs = "+proj=robin") # Populated places layer has been downloaded from Natural Earth: # https://www.naturalearthdata.com/downloads/10m-cultural-vectors/ # For script reproducibility purposes, I uploaded a subset on my github: cp = read_sf("https://github.com/BjnNowak/geocomputation/raw/main/data/ne_10m_populated_places.gpkg") |> # Select only administrative capitals filter(ADM0CAP == 1) |> # Reproject to Robinson st_transform(crs = "+proj=robin") |> # Intersection to keep only capitals in Africa or (West) Asia st_intersection(mp_with_countries |> filter(continent %in% c("Africa"))) # Clean data ############ # Dissolve countries for world basemap mp = mp_with_countries |> mutate(entity = "world", ct = 1) |> group_by(entity) |> summarize(sm = sum(ct)) |> ungroup() mp_africa = mp_with_countries |> filter(continent == "Africa") # Prepare labels and extract coordinates (to place later with {ggtext}) of capitals main_cp = cp |> arrange(-POP_MAX) |> mutate(pop = round(POP_MAX / 1000000, 2)) |> mutate(label = glue::glue("{NAME}
{pop} M")) |> mutate(lon = sf::st_coordinates(geom)[, 1], lat = sf::st_coordinates(geom)[, 2]) # Subset of capitals to be placed on final map sel = c("Cairo", "Algiers", "Tripoli", "Baghdad", "Khartoum", "Addis Ababa", "Abidjan", "Dakar", "Bangui", "Harare", "Brazzaville", "Luanda", "Nairobi", "N'Djamena") # Function to create sucessive buffers around capitals fun_buff = function(cp, buff) { # Create empty list buff_list = list() # Fill list with 5 successive buffers for (i in 1:5) { buff_list[[i]] = cp |> st_buffer(buff * i) |> st_intersection(mp_africa) } return(buff_list) } # Apply function to create list with buffers around points buff_list = fun_buff(cp = cp, buff = 100000) ############################## # Creation of the second map # ############################## # The second map is a customized dorling cartogram showing the quantity and origin of energy production in Europe. # I made it on a different continent to avoid overlapping. # I've written an online tutorial detailing my method for creating "customized" Dorling cartograms, displaying the value of sub-variables: # https://r-graph-gallery.com/web-dorling-cartogram-with-R.html # For this map, the sub-variables will represent the origin of the energy (fossil, renewable or nuclear) # Load data ############# # Data is from a TidyTuesday dataset (so already available online): # https://github.com/rfordatascience/tidytuesday/blob/master/data/2023/2023-06-06/readme.md owid_energy = readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2023/2023-06-06/owid-energy.csv") # Data cleaning ############### # Compute the origin of the energy produced per country since 2010 clean = owid_energy |> filter(year > 2010) |> mutate(iso_code = case_when(country == "Kosovo" ~ "KOS", TRUE ~ iso_code)) |> drop_na(iso_code) |> group_by(country) |> summarize( iso_code = iso_code[1], biofuel = mean(na.omit(biofuel_elec_per_capita)), coal = mean(na.omit(coal_elec_per_capita)), gas = mean(na.omit(gas_elec_per_capita)), hydro = mean(na.omit(hydro_elec_per_capita)), nuke = mean(na.omit(nuclear_elec_per_capita)), oil = mean(na.omit(oil_elec_per_capita)), solar = mean(na.omit(solar_elec_per_capita)), wind = mean(na.omit(wind_elec_per_capita)), total = biofuel + coal + gas + hydro + nuke + oil + solar + wind ) |> ungroup() # Make cartogram ################ # Join data and map # (we use here the basemap already downloaded for 1st map) world = mp_with_countries |> left_join(clean, by = c("iso_a3" = "iso_code")) |> # Keeping only European countries filter(continent == "Europe") |> drop_na(total) # Making Dorling cartogram based on total quantity of energy produced dorl = cartogram_dorling(world, weight = "total", k = 2.5, m_weight = 0, itermax = 1000) # Compute area and radius for each circle of the cartogram d2 = dorl |> mutate(ar = st_area(dorl), rad = sqrt(ar / pi)) # Extract centroids for each circle centr = dorl |> st_centroid() |> st_coordinates() # Merge area and centroids # and compute proportional radius for different sources of energy d3 = tibble(d2, X = centr[, 1], Y = centr[, 2]) |> mutate(rad = as.numeric(rad)) |> mutate( # Renewable ratio_renew = (biofuel + hydro + solar + wind) / total, # Fossil ratio_fossil = (oil + gas + coal) / total, # Nuclear ratio_nuke = nuke / total ) |> mutate( rad_renew = sqrt(rad * rad * ratio_renew), rad_fossil = sqrt(rad * rad * ratio_fossil), rad_nuke = sqrt(rad * rad * ratio_nuke) ) # Create custom function to draw circles based on centroid and radius circleFun = function(center = c(0, 0), diameter = 1, npoints = 100, start = 0, end = 2) { tt = seq(start * pi, end * pi, length.out = npoints) df = data.frame(x = center[1] + diameter / 2 * cos(tt), y = center[2] + diameter / 2 * sin(tt)) df2 = bind_rows(df, tibble(x = center[1], y = center[2])) return(df2) } # Sort by country code dord = d3 |> arrange(iso_a3) # Limits of the three "slices" (renewable, fossil or nuclear) for each circle c_renew = c(0, 2 * 1 / 3) c_fossil = c(2 * 1 / 3, 2 * 2 / 3) c_nuke = c(2 * 2 / 3, 2) # Empty object to be filled with values later renew = tibble(iso = c(), x = c(), y = c()) nuke = tibble(iso = c(), x = c(), y = c()) fossil = tibble(iso = c(), x = c(), y = c()) # Apply function for each country and type of energy for (i in 1:dim(dord)[1]) { # compared to the version of the tutorial created for R Graph Gallery, # the only difference here is that I need to add a point for the center # (required coz we do not draw half circles here). t1 = tibble(iso = dord$iso_a3[i], x = dord$X[i], y = dord$Y[i]) temp_renew = tibble(iso = dord$iso_a3[i], circleFun(c(dord$X[i], dord$Y[i]), diameter = dord$rad_renew[i] * 2, start = c_renew[1], end = c_renew[2])) |> bind_rows(t1) # Add center point temp_nuke = tibble(iso = dord$iso_a3[i], circleFun(c(dord$X[i], dord$Y[i]), diameter = dord$rad_nuke[i] * 2, start = c_nuke[1], end = c_nuke[2])) |> bind_rows(t1) temp_fossil = tibble(iso = dord$iso_a3[i], circleFun(c(dord$X[i], dord$Y[i]), diameter = dord$rad_fossil[i] * 2, start = c_fossil[1], end = c_fossil[2])) |> bind_rows(t1) renew = renew |> bind_rows(temp_renew) nuke = nuke |> bind_rows(temp_nuke) fossil = fossil |> bind_rows(temp_fossil) } ##################### # Generate OD data # ##################### eu_flows_clean = read_csv("https://raw.githubusercontent.com/BjnNowak/geocomputation/main/data/eu_flows_clean.csv") eu_flows_sf = od::od_to_sf(eu_flows_clean, mp_with_countries |> transmute(tolower(iso_a3))) eu_flows_top_500 = eu_flows_sf |> arrange(desc(trade_value_usd_exp)) |> slice(1:500) # without russia eu_flows_no_russia = eu_flows_top_500 |> filter(reporter_iso != "rus", partner_iso != "rus") ############################################### # Third map : NDVI raster for Asia and Russia # ############################################### asia1 = mp_with_countries |> filter(continent == "Asia") asia2 = mp_with_countries |> filter(name == "Russia") |> st_cast("POLYGON") |> mutate(area = st_area(geometry)) |> top_n(1) asia2$area = NULL asia = rbind(asia1, asia2) # Load raster url = "https://zenodo.org/records/10476056/files/smoothed_NDVI_May_2020.tif?download=1" ndvi = terra::rast(url) |> # reproject to Robinson project(method = "near", "+proj=robin", mask = TRUE) |> # crop to Asia borders crop(asia, mask = TRUE) # Set band name to NDVI names(ndvi) = "NDVI" ################## # Make final map # ################## # Color palette for buffers pal = moma.colors("Flash" , n = 22, type = "continuous") alp = 1 # optional parameter to add transparencies to buffer col_lv1 = alpha(pal[1], alp) col_lv2 = alpha(pal[6], alp) col_lv3 = alpha(pal[9], alp) col_lv4 = alpha(pal[12], alp) col_lv5 = alpha(pal[15], alp) col_world = "#073B4C" col_borders = "gray80" col_back = "#1D201F" pal_dis = moma.colors("ustwo" , n = 5, type = "discrete") alp_dorl = 0.05 # optional parameter to add transparencies to buffer col_fossil = alpha(pal_dis[1], alp_dorl) col_nuke = alpha(pal_dis[3], alp_dorl) col_renew = alpha(pal_dis[5], alp_dorl) # color for Europe flows col_trade = "white" #col_lv3 # NDVI color palette set from {scico} pal_ndvi = scico(17, palette = "tokyo") # Robinson bounding box xlims = c(-2200000, 4500000) ylims = c(-2000000, 8000000) grat = st_graticule(lat = c(-89.9, seq(-80, 80, 20), 89.9)) g = ggplot() + # Add basemap geom_sf(mp, mapping = aes(geometry = geometry), fill = "#151529", color = alpha("white", 0.15), lwd = 0.1) + # Third map ########### tidyterra::geom_spatraster(data = ndvi, aes(fill = NDVI), na.rm = TRUE, maxcell = 20e+05) + # Low res for tests #maxcell = 300e+05 # First map ########### # Add successive buffers # Add countries borders above buffers geom_sf(buff_list[[5]], mapping = aes(geometry = geom), fill = col_lv5, color = alpha("white", 0)) + geom_sf(buff_list[[4]], mapping = aes(geometry = geom), fill = col_lv4, color = alpha("white", 0)) + geom_sf(buff_list[[3]], mapping = aes(geometry = geom), fill = col_lv3, color = alpha("white", 0)) + geom_sf(buff_list[[2]], mapping = aes(geometry = geom), fill = col_lv2, color = alpha("white", 0)) + geom_sf(buff_list[[1]], mapping = aes(geometry = geom), fill = col_lv1, color = alpha("white", 0)) + # Add countries borders above buffers geom_sf(mp_with_countries, mapping = aes(geometry = geometry), fill = NA, color = alpha("white", 0.05), lwd = 0.15) + # Add capitals labels geom_richtext(main_cp |> filter(NAME %in% sel), #main_cp|>head(100), mapping = aes(x = lon, y = lat, label = label), color = alpha("white", 0.5), size = 15, hjust = 1, lineheight = 0.15, family = "ral", fill = NA, label.color = NA, # remove background and outline label.padding = grid::unit(rep(0, 4), "pt") # remove padding ) + # Second map ############ # # Add main circles for Dorling cartogram geom_circle(data = d3, #d3|>filter(adm0_a3%in%kp_dorl), aes(x0 = X, y0 = Y, r = rad), color = alpha("white", 0.25), fill = "#6C809A", alpha = 0.25, linewidth = 0.05) + # # Add slices for Dorling cartogram # geom_polygon(renew, # #renew|>filter(iso%in%kp_dorl), # mapping = aes(x, y, group = iso), fill = col_renew, color = NA) + # geom_polygon(nuke, # #nuke|>filter(iso%in%kp_dorl), # mapping = aes(x, y, group = iso), fill = col_nuke, color = NA) + # geom_polygon(fossil, # #fossil|>filter(iso%in%kp_dorl), # mapping = aes(x, y, group = iso), fill = col_fossil, color = NA) + # Add flows geom_sf(eu_flows_no_russia, mapping = aes(size = trade_value_usd_exp, alpha = trade_value_usd_exp, geometry = geometry), color = col_trade#, linewidth = 1, ) + # Add graticule geom_sf(grat, mapping = aes(geometry = geometry), color = alpha("white", 0.15)) + guides(fill = "none", size = "none", alpha = "none") + scale_alpha(range = c(0.05, 0.35)) + # Color gradient for NDVI # scale_fill_gradientn(colors = rev(pal[3:19]), na.value = NA, limits = c(0, 1), breaks = seq(0.1, 0.9, 0.1)) + scale_fill_gradientn(colors = pal_ndvi, na.value = NA, limits = c(0, 1), breaks = seq(0.1, 0.9, 0.1)) + # Center map coord_sf(xlims, ylims) + # Custom theme (color background can be changed here) theme_void() + theme(plot.background = element_rect(fill = "#191930", color = "#191930")) #g ggsave("/tmp/test-map.png", g, width = 15.6, height = 23.4, unit = "cm", dpi = 600) browseURL("/tmp/test-map.png") ================================================ FILE: code/frontcover.R ================================================ # load packages ---- library(raster) library(sf) library(spData) library(ggplot2) library(hexSticker) library(raster) library(rcartocolor) library(ggimage) library(tidyverse) library(gridExtra) library(spex) library(spDataLarge) library(stplanr) library(cartogram) library(rasterVis) # NZ map plot ------------------------------------------------------------- data("world", "nz", package = "spData") dem = getData("alt", country = "NZL", mask = FALSE, path = tempdir()) dem = dem[[1]] dem_2 = aggregate(dem, 50) nz = st_transform(nz, proj4string(dem)) south_nz = crop(dem_2, as(nz[10:16,], "Spatial")) south_nz = as.data.frame(south_nz, xy = TRUE) p1 = ggplot(nz) + geom_sf(data = st_geometry(nz[1:9,])) + coord_sf(crs = st_crs(st_geometry(nz[1:9,])), datum = NA) + geom_tile(data = south_nz, aes(x, y, fill = NZL1_alt)) + scale_fill_carto_c(type = "diverging", palette = "Fall", na.value = NA) + theme_void() + guides(fill = FALSE) p1 s_x = 1.05 s_y = 1.05 s_width = 1.5 s_height = 1.5 h1 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p1, x = s_x, y = s_y, width = s_width, height = s_height ) h1 # console logo plot ------------------------------------------------------- p2 = grid::textGrob("> geo::", gp = grid::gpar( col = "blue", cex = 3 )) s_x = 1.0 s_y = 1.0 s_width = 3.5 s_height = 3.5 h2 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p2, x = s_x, y = s_y, width = s_width, height = s_height ) h2 # geomcompr logo plot ---------------------------------------------------- r = raster("images/r_logo.tif") r = raster::aggregate(r, 4) r = setExtent(r, extent(c(-49, 49,-50, 50))) crs(r) = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" v = spex::polygonize(r) %>% mutate(id = ifelse(r_logo == 255, 0, 1)) %>% group_by(id) %>% summarise() %>% filter(id == 1) gr = st_graticule(ndiscr = 1000) %>% st_transform("+proj=laea +y_0=0 +lon_0=0 +lat_0=0 +ellps=WGS84 +no_defs") p3 = ggplot(fill = "transparent") + geom_sf(data = gr, size = 0.0001) + geom_sf(data = v, fill = "#2066b9") + coord_sf(crs = st_crs(gr)) + theme( panel.background = element_rect(fill = "transparent") # bg of the panel , plot.background = element_rect(fill = "transparent", color = NA) # bg of the plot , panel.grid.major = element_blank() # get rid of major grid , panel.grid.minor = element_blank() # get rid of minor grid , legend.background = element_rect(fill = "transparent") # get rid of legend bg , legend.box.background = element_rect(fill = "transparent") # get rid of legend panel bg ) s_x = 1 s_y = 1 s_width = 1.4 s_height = 1.4 h3 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p3, x = s_x, y = s_y, width = s_width, height = s_height ) h3 # network plot ------------------------------------------------------------ zones_attr = bristol_od %>% group_by(o) %>% summarize_if(is.numeric, sum) %>% dplyr::rename(geo_code = o) zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") zones_od = bristol_od %>% group_by(d) %>% summarize_if(is.numeric, sum) %>% select(geo_code = d, all_dest = all) %>% inner_join(zones_joined, ., by = "geo_code") od_top5 = bristol_od %>% arrange(desc(all)) %>% top_n(5, wt = all) bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) desire_lines = od2line(od_inter, zones_od) desire_lines$Active_breaks = cut( desire_lines$Active, breaks = c(0, 5, 10, 20, 40, 100), include.lowest = TRUE ) p4 = ggplot(desire_lines) + geom_sf(aes(colour = Active_breaks), lwd = 0.1, alpha = 0.01) + theme_void() + scale_colour_viridis_d(breaks = c(0, 5, 10, 20, 40, 100), option = "C") + theme(panel.grid.major = element_line(colour = 'transparent')) + guides(colour = FALSE) s_x = 0.95 s_y = 0.9 s_width = 1.55 s_height = 1.55 h4 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p4, x = s_x, y = s_y, width = s_width, height = s_height ) h4 # geocompr map 1 plot ----------------------------------------------------- us_states2163 = st_transform(us_states, 2163) us_states2163_ncont = cartogram_ncont(us_states2163, "total_pop_15") p5 = ggplot(us_states2163_ncont) + geom_sf(aes(fill = total_pop_15), lwd = 0.25) + coord_sf(crs = st_crs(us_states2163_ncont), datum = NA) + scale_fill_viridis_c(option = "D") + theme(panel.grid.major = element_line(colour = 'transparent')) + guides(fill = FALSE) + theme_void() s_x = 1 s_y = 0.95 s_width = 1.5 s_height = 1.5 h5 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p5, x = s_x, y = s_y, width = s_width, height = s_height ) h5 # geocompr map 2 plot ----------------------------------------------------- srtm = raster(system.file("raster/srtm.tif", package = "spDataLarge")) zion = read_sf(system.file("vector/zion.gpkg", package = "spDataLarge")) zion = st_transform(zion, projection(srtm)) srtm_masked = mask(srtm, as(zion, "Spatial")) p6 = gplot(srtm_masked) + geom_raster(aes(fill = value)) + scale_fill_carto_c(palette = 'TealRose', na.value = NA) + geom_sf(data = st_cast(zion, "LINESTRING"), inherit.aes = FALSE) + theme_void() + guides(fill = FALSE) + theme(panel.grid.major = element_line(colour = 'transparent')) s_x = 1 s_y = 1 s_width = 1.4 s_height = 1.4 h6 = hexagon(fill = NA, color = "#000000") + geom_subview( subview = p6, x = s_x, y = s_y, width = s_width, height = s_height ) h6 # final plot arrangement ------------------------------------------------- final_plot = function(hex1, hex2, hex3, hex4, hex5, hex6){ p = ggplot() + coord_equal(xlim = c(0, 30), ylim = c(0, 30), expand = c(0, 0)) + annotation_custom( ggplotGrob(hex1), xmin = 0.5, xmax = 8.5, ymin = 21, ymax = 29 ) + annotation_custom( ggplotGrob(hex3), xmin = 3.8, xmax = 11.8, ymin = 15.2, ymax = 23.2 ) + annotation_custom( ggplotGrob(hex2), xmin = 7.2, xmax = 15.2, ymin = 21, ymax = 29 ) + annotation_custom( ggplotGrob(hex4), xmin = 10.5, xmax = 18.5, ymin = 15.2, ymax = 23.2 ) + annotation_custom( ggplotGrob(hex5), xmin = 0.5, xmax = 8.5, ymin = 9.4, ymax = 17.4 ) + annotation_custom( ggplotGrob(hex6), xmin = 7.2, xmax = 15.2, ymin = 9.4, ymax = 17.4 ) + labs(x = NULL, y = NULL) + theme_void() print(p) } final_plot(h6, h4, h2, h3, h5, h1) ggsave("geocompr_cover.pdf", width = 12, height = 18) browseURL("geocompr_cover.pdf") ================================================ FILE: code/generate-chapter-code.R ================================================ #' Extracts R code from each chapter and dumps it in the code folder generate_chapter_code = function(dir = ".", out_dir = "code/chapters/") { rmd_files = list.files(path = dir, pattern = ".Rmd") r_files = paste0(out_dir, rmd_files) r_files = gsub(pattern = "Rmd", replacement = "R", r_files) for(i in seq_along(rmd_files)) { knitr::purl(input = rmd_files[i], output = r_files[i]) } } generate_chapter_code() #' Generate a data frame of book statistics per chapter generate_book_stats = function(dir = ".") { library(tidytext) library(dplyr) rmd_files = list.files(path = dir, pattern = ".Rmd") chapters = lapply(rmd_files, readLines) chapters = lapply(chapters, function(x) data_frame(line = 1:length(x), text = x)) chapters[[1]] %>% unnest_tokens(words, text) n_words = sapply(chapters, function(x) nrow(unnest_tokens(x, words, text))) chapter = 1:length(n_words) date = Sys.Date() data_frame(n_words, chapter, date) } ================================================ FILE: code/hex_sticker.R ================================================ library(sf) library(raster) library(dplyr) library(ggplot2) library(hexSticker) library(showtext) # read the logo file and change its extent -------------------------------- r = raster("images/r_logo.tif") r = setExtent(r, extent(c(-49, 49, -50, 50))) crs(r) = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" # polygonize the logo (time-consuming)------------------------------------- v = spex::polygonize(r) %>% mutate(id = ifelse(r_logo==255, 0, 1)) %>% group_by(id) %>% summarise() %>% filter(id == 1) # saveRDS(v, "v.rds") # v = readRDS("v.rds") # create a graticule ------------------------------------------------------ gr = st_graticule(ndiscr = 1000) %>% st_transform("+proj=laea +y_0=0 +lon_0=0 +lat_0=0 +ellps=WGS84 +no_defs") # create a map ------------------------------------------------------------ p = ggplot(fill = "transparent") + geom_sf(data = gr, size = 0.0001) + geom_sf(data = v, fill = "#2066b9") + coord_sf(crs = st_crs(gr)) + theme( panel.background = element_rect(fill = "transparent") # bg of the panel , plot.background = element_rect(fill = "transparent", color = NA) # bg of the plot , panel.grid.major = element_blank() # get rid of major grid , panel.grid.minor = element_blank() # get rid of minor grid , legend.background = element_rect(fill = "transparent") # get rid of legend bg , legend.box.background = element_rect(fill = "transparent") # get rid of legend panel bg ) # add a new font ---------------------------------------------------------- font_add_google("Fjalla One", "fs") # create a sticker -------------------------------------------------------- sticker( subplot = p, s_x = 1, s_y = 1.1, s_width = 1.7, s_height = 1.7, h_size = 2, package = "geocompr", p_family = "fs", p_size = 12, p_color = "#2066b9", p_x = 1, p_y = 0.33, h_fill = "white", h_color = "#2066b9", filename = "images/geocompr_hex.png" ) # create an animation ------------------------------------------------------ # library(magick) # plotter2 = function(lon_0, y){ # x = st_graticule(ndiscr = 10000) %>% # st_transform(paste0("+proj=laea +y_0=0 +lon_0=", lon_0, " +lat_0=0 +ellps=WGS84 +no_defs")) # y = st_geometry(y) %>% # st_transform(st_crs("+proj=laea +y_0=0 +lon_0=0 +lat_0=0 +ellps=WGS84 +no_defs")) # y = y + c(lon_0, 0) # plot(st_geometry(x), col = "gray") # plot(st_geometry(y), add = TRUE, col = "blue") # } # # img = image_graph(600, 600, res = 96) # seq(21, 1, by = -3) %>% map(plotter2, v) # dev.off() # animation = image_animate(img, fps = 10) # print(animation) # ================================================ FILE: code/list-contributors.R ================================================ # Filename: list_contributors.R (2018-03-16) # # TO DO: List all geocompr contributors # # Author(s): Jannes Muenchow & Robin Lovelace # #********************************************************** # CONTENTS------------------------------------------------- #********************************************************** # # 1. ATTACH PACKAGES AND DATA # 2. CONTRIBUTOR LIST # #********************************************************** # 1 ATTACH PACKAGES AND DATA------------------------------- #********************************************************** # attach packages library(gh) library(tidyverse) #********************************************************** # 2 CONTRIBUTOR LIST--------------------------------------- #********************************************************** # git has to be in PATH out_json = gh::gh(endpoint = "/repos/geocompx/geocompr/contributors", .limit = Inf) link = vapply(out_json, "[[", FUN.VALUE = "", "html_url") name = gsub(pattern = "https://github.com/", "", link) commits = paste0("https://github.com/geocompx/geocompr/commits?author=", name) out_df = tibble(name, link) # remove book authors filter(out_df, !grepl("robin|jannes|jn|jakub|nowosad|Nowosad|Robinlovelace|jannes-m", name, TRUE)) ================================================ FILE: code/old-to-future-remove/06_raster_reprojection_tests.R ================================================ library(ggplot2) library(visualraster) theme_set(theme_fullframe()) set.seed(2017-11-05) small_ras_val = raster(matrix(sample.int(12, 16, replace = TRUE), 4, 4, byrow =TRUE), crs = "+proj=longlat") small_ras_val[c(7, 9)] = NA small_ras_val2 = projectRaster(small_ras_val, crs = "+proj=utm +zone=30 +ellps=WGS72 +datum=WGS84 +units=m +no_defs ") small_ras_val3 = projectRaster(small_ras_val, crs = "+proj=utm +zone=30 +ellps=WGS72 +datum=WGS84 +units=m +no_defs ", method = "ngb") ggplot() + vr_geom_raster_seq(small_ras_val) + vr_geom_text(small_ras_val) + scale_fill_gradientn(colors = c("white")) ggplot() + vr_geom_raster_seq(small_ras_val2) + vr_geom_text(small_ras_val2) + scale_fill_gradientn(colors = c("white")) ggplot() + vr_geom_raster_seq(small_ras_val3) + vr_geom_text(small_ras_val3) + scale_fill_gradientn(colors = c("white")) ================================================ FILE: code/old-to-future-remove/08-uscolonize.R ================================================ library(tmap) library(dplyr) library(tidyr) library(sf) statepop = historydata::us_state_populations %>% select(-GISJOIN) %>% rename(NAME = state) statepop_wide = spread(statepop, year, population, sep = "_") statepop_sf = left_join(spData::us_states, statepop_wide, by = "NAME") %>% st_transform(2163) year_vars = names(statepop_sf)[grepl("year", names(statepop_sf))] facet_anim = tm_shape(statepop_sf) + tm_fill(year_vars) + tm_facets(free.scales.fill = FALSE, ncol = 1, nrow = 1) tmap_animation(tm = facet_anim, filename = "images/09-us_pop.gif") ================================================ FILE: code/old-to-future-remove/10-centroid.R ================================================ #' Find the centre-point of a polygon represented by a matrix #' #' Calculates the centroid and (optionally) area of a polygon. #' #' @examples #' poly_csv = "0,5,10,15,20,25,30,40,45,50,40,30,25,20,15,10,8,4,0 #' 10,0,10,0,10,0,20,20,0,50,40,50,20,50,10,50,8,50,10" #' poly_df = read.csv(text = poly_csv, header = FALSE) #' poly_mat = t(poly_df) #' centroid(poly_mat) #' centroid(poly_mat, return_area = TRUE) #' square_csv = "0,0,10,10,0 #' 0,10,10,0,0" #' poly_df = read.csv(text = square_csv, header = FALSE) #' poly_mat = t(poly_df) #' centroid(poly_mat) #' centroid(poly_mat, return_area = TRUE) centroid = function(poly_mat, return_area = FALSE) { stopifnot(identical(poly_mat, poly_mat)) num_points = nrow(poly_mat) A = xmean = ymean = 0 # set initial params to zero # i = 2 # enter this for testing for(i in 1:(num_points - 1)) { p1 = poly_mat[i, ] p2 = poly_mat[i + 1, ] ai = p1[1] * p2[2] - p2[1] * p1[2] A = A + ai xmean = xmean + (p2[1] + p1[1]) * ai ymean = ymean + (p2[2] + p1[2]) * ai } A = A / 2 C = c(xmean / (6 * A), ymean / (6 * A)) if (return_area) return(abs(A)) else return(C) } ================================================ FILE: code/old-to-future-remove/10-earthquakes.R ================================================ # Aim: create up-to-date map of Earthquakes in previous week # setup ------------------------------------------------------------------ library(sf) library(spData) # download data ----------------------------------------------------------- u = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_month.geojson" earthquakes = read_sf(u) # summary(earthquakes) # summarise # text results ------------------------------------------------------------ print(paste0(nrow(earthquakes), " significant earthquakes happened last month")) # map --------------------------------------------------------------------- plot(st_geometry(world), border = "gray") plot(st_geometry(earthquakes), cex = earthquakes$mag, add = TRUE) title(paste0( "Location of significant (mag > 5) Earthquakes in the month to ", Sys.Date(), "\n(circle diameter is proportional to magnitude)" )) ================================================ FILE: code/old-to-future-remove/12-code-extension.R ================================================ # Aim: build on code in Chapter 12 of Geocomputation with R to demosntrate geographic levels ## ----12-transport-1, message=FALSE, results='hide'----------------------- library(sf) library(dplyr) library(spDataLarge) library(stplanr) # geographic transport data package library(tmap) # visualization package (see Chapter 8) ## ----12-transport-2, echo=FALSE, eval=FALSE------------------------------ ## # code that generated the input data - see also ?bristol_ways ## # source("https://github.com/Robinlovelace/geocompr/raw/main/code/12-transport-data-gen.R") ## # view input data ## summary(bristol_ways) ## summary(bristol_ttwa) ## summary(bristol_region) ## region_all = rbind(bristol_region, bristol_ttwa) library(tmap) tmap_mode("view") qtm(bristol_ways, lines.col = "highway", lines.lwd = 3, lines.palette = c("green", "black", "red")) + tm_scale_bar() + tm_shape(region_all) + tm_borders(lwd = c(5, 7), col = "darkblue") ## ----bristol, echo=FALSE, fig.cap="Bristol's transport network represented by colored lines for active (green), public (railways, black) and private motor (red) modes of travel. Blue border lines represent the inner city boundary and the larger Travel To Work Area (TTWA).", fig.scap="Bristol's transport network."---- knitr::include_graphics("https://user-images.githubusercontent.com/1825120/34452756-985267de-ed3e-11e7-9f59-fda1f3852253.png") ## ----12-transport-3, eval=FALSE, echo=FALSE------------------------------ ## if (!require(readODS)) { ## install.packages("readODS") ## } ## u = "https://www.gov.uk/government/uploads/system/uploads/attachment_data/file/536823/local-area-walking-and-cycling-in-england-2015.zip" ## download.file(u, "local-area-walking-and-cycling-in-england-2015.zip") ## unzip("local-area-walking-and-cycling-in-england-2015.zip") ## View(readODS::read_ods("Table index.ods")) ## cw0103 = readODS::read_ods("cw0103.ods") ## View(cw0103) ## Another issue with small zones is related to anonymity rules. ## ----12-transport-5------------------------------------------------------ names(bristol_zones) ## ----12-transport-6------------------------------------------------------ nrow(bristol_od) nrow(bristol_zones) ## ----12-transport-7------------------------------------------------------ zones_attr = bristol_od %>% group_by(o) %>% summarize_if(is.numeric, sum) %>% dplyr::rename(geo_code = o) ## ----12-transport-8------------------------------------------------------ summary(zones_attr$geo_code %in% bristol_zones$geo_code) ## ----12-transport-9------------------------------------------------------ zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") sum(zones_joined$all) names(zones_joined) ## ----12-transport-10----------------------------------------------------- zones_od = bristol_od %>% group_by(d) %>% summarize_if(is.numeric, sum) %>% select(geo_code = d, all_dest = all) %>% inner_join(zones_joined, ., by = "geo_code") ## ----12-transport-11, eval=FALSE----------------------------------------- ## qtm(zones_od, c("all", "all_dest")) + ## tm_layout(panel.labels = c("Origin", "Destination")) ## ----zones, echo=FALSE, fig.cap="Number of trips (commuters) living and working in the region. The left map shows zone of origin of commute trips; the right map shows zone of destination (generated by the script 12-zones.R).", message=FALSE, fig.scap="Number of trips (commuters) living and working in the region."---- source("https://github.com/Robinlovelace/geocompr/raw/main/code/12-zones.R", print.eval = TRUE) ## ----12-transport-12----------------------------------------------------- od_top5 = bristol_od %>% arrange(desc(all)) %>% top_n(5, wt = all) bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) ## ----12-transport-15, warning=FALSE-------------------------------------- desire_lines = od2line(od_inter, zones_od) desire_lines$distance = as.numeric(st_length(desire_lines)) desire_carshort = dplyr::filter(desire_lines, car_driver > 300 & distance < 5000) desire_lines$id = stplanr::od_id_character(desire_lines$o, desire_lines$d) desire_lines_100 = desire_lines %>% top_n(n = 100, wt = all) qtm(desire_lines_100, lines.lwd = "all") # cycle_routes = line2route(desire_lines[1:9, ], route_fun = cyclestreets::journey) # routing cycle_routes_original = pct::get_pct_routes_fast(region = "avon") cycle_routes = cycle_routes_original %>% select(id, geo_code1, geo_code2, dutch_slc) summary(cycle_routes$geo_code1 %in% desire_lines_100$o) summary(cycle_routes$geo_code2 %in% desire_lines_100$d) summary(cycle_routes$id %in% desire_lines_100$id) head(cycle_routes$id) names(desire_lines) cycle_routes_100 = cycle_routes %>% filter(id %in% desire_lines$id) desire_lines_df = sf::st_drop_geometry(desire_lines_100) desire_lines_cycleways = inner_join(cycle_routes_100, desire_lines_df, by = "id") plot(desire_lines_cycleways) rnet = overline(desire_lines_cycleways, "dutch_slc") plot(rnet) # getting geographic data library(geofabrik) bristol_railways = get_geofabrik(name = "Bristol", key = "railway", value = "rail") bristol_cycleways = get_geofabrik(name = "Bristol", key = "highway", value = "cycleway") tm_shape(rnet) + tm_lines() + tm_shape(bristol_railways) + tm_lines(col = "red") tmap_mode("view") tm_shape(rnet) + tm_lines(lwd = "dutch_slc", scale = 9) + tm_shape(bristol_railways) + tm_lines(col = "red") + tm_shape(bristol_cycleways) + tm_lines(col = "surface", lwd = 2, colorNA = "green") ================================================ FILE: code/old-to-future-remove/12-desire-front.R ================================================ # Aim: generate tmap figure representing desire lines # load data if not already loaded: if (!exists("desire_lines")) { library(sf) library(tidyverse) library(spDataLarge) library(stplanr) library(tmap) zones_attr = bristol_od %>% group_by(o) %>% summarize_if(is.numeric, sum) %>% dplyr::rename(geo_code = o) zones_joined = left_join(bristol_zones, zones_attr, by = "geo_code") zones_od = bristol_od %>% group_by(d) %>% summarize_if(is.numeric, sum) %>% select(geo_code = d, all_dest = all) %>% inner_join(zones_joined, ., by = "geo_code") od_top5 = bristol_od %>% arrange(desc(all)) %>% top_n(5, wt = all) bristol_od$Active = (bristol_od$bicycle + bristol_od$foot) / bristol_od$all * 100 od_intra = filter(bristol_od, o == d) od_inter = filter(bristol_od, o != d) desire_lines = od2line(od_inter, zones_od) } tmap_mode("plot") desire_lines_top5 = od2line(od_top5, zones_od) # tmaptools::palette_explorer() tm_shape(desire_lines) + tm_lines(palette = "plasma", breaks = c(0, 5, 10, 20, 40, 100), lwd = "all", scale = 9, title.lwd = "Number of trips", alpha = 0.6, col = "Active", title = "Active travel (%)" ) + tm_legend(show = FALSE) + tm_layout(frame = FALSE) ================================================ FILE: code/old-to-future-remove/globe.R ================================================ # Aim: plot globe if (!require(globe)) { install.packages("globe") } library(sf) center = st_sf(st_sfc(st_point(c(0, 0)))) buff_equator = st_buffer(x = center, dist = 20) coords = st_coordinates(buff_equator)[, 1:2] png(filename = "images/globe-r.png", width = 500, height = 500) globeearth(eye = c(0, 0)) globelines(loc = coords) dev.off() ================================================ FILE: code/old-to-future-remove/sfr-class-diagram-gen.R ================================================ # Aim: create diagram representing sf classes library(sf) library(diagram) sf_classes = getClasses(where = asNamespace("sf")) n = sf_classes[grepl(pattern = "sfc_", sf_classes)] n = gsub(pattern = "sfc_", replacement = "", n) n = gsub(pattern = "GEOMETRY", replacement = "GEOMETRY COLLECTION", n) n = sort(n)[c(1, 4, 3, 5, 6, 2, 7)] # see https://davetang.org/muse/2017/03/31/creating-flowchart-using-r/ png(filename = "images/sf-classes.png", width = 600, height = 500) # openplotmat() pos = coordinates(c(1, 3, 3)) plot(pos, type = 'n') text(pos) par(mar = rep(1, 4)) openplotmat() straightarrow(from = pos[5, ], to = pos[2, ]) straightarrow(from = pos[5, ], to = pos[1, ]) straightarrow(from = pos[6, ], to = pos[3, ]) straightarrow(from = pos[7, ], to = pos[4, ]) straightarrow(from = pos[7, ], to = pos[1, ]) straightarrow(from = pos[2, ], to = pos[1, ]) straightarrow(from = pos[3, ], to = pos[1, ]) straightarrow(from = pos[4, ], to = pos[1, ]) for(i in seq_along(n)) textrect(mid = pos[i,], radx = 0.14, rady = 0.05, lab = n[i]) i = 1 textrect(mid = pos[i,], radx = 0.18, rady = 0.05, lab = n[i]) dev.off() ## attempt with DiagrammR ----- # nodes = create_node_df(n = length(n), label = n, shape = "rectangle", width = 3, ) # graph_attrs = c("layout = circo", # "overlap = false", # "fixedsize = true", # "ranksep = 3", # "outputorder = edgesfirst") # edges = create_edge_df(from = c(1, 2, 3, 4, 5, 6), # to = c(4, 5, 6, 7, 7, 7)) # g = create_graph(nodes_df = nodes, edges_df = edges) # # render_graph(g, layout = "forceDirected") # export_graph(g,file_name = "f.gexf", file_type = "gexf") ================================================ FILE: code/old-to-future-remove/spData.R ================================================ # Aim load data from spDataLarge if you cannot install the package if (!require(spDataLarge)) { download.file("https://github.com/Nowosad/spDataLarge/archive/master.zip", "spDataLarge.zip") unzip("spDataLarge.zip") files_rda = list.files("spDataLarge-master/data/", full.names = TRUE) sapply(files_rda, load, envir = .GlobalEnv) } ================================================ FILE: code/sf-classes.R ================================================ # https://stackoverflow.com/questions/35631889/ggplot2-align-multiple-plots-with-varying-spacings-and-add-arrows-between-them library(sf) library(ggplot2) library(lattice) library(grid) library(gridExtra) ## sfc objects creation --------- point_sfc = st_sfc(st_point(c(1, 1)), crs = 4326) linestring_sfc = st_sfc(st_linestring(rbind(c(0.8, 1), c(0.8, 1.2), c(1, 1.2))), crs = 4326) polygon_sfc = st_sfc(st_polygon(list(rbind( c(1.2, 0.6), c(1.4, 0.6), c(1.4, 0.8), c(1.2, 0.8), c(1.2, 0.6) ))), crs = 4326) multipoint_sfc = st_sfc(st_multipoint(rbind(c(1, 0.6), c(1.4, 1.1))), crs = 4326) multilinestring_sfc = st_sfc(st_multilinestring(list(rbind( c(1.2, 1), c(1.2, 1.4) ), rbind( c(1.4, 0.4), c(1.6, 0.6), c(1.6, 0.8) ))), crs = 4326) multipolygon_sfc = st_sfc(st_multipolygon(list(list(rbind( c(1.4, 1.2), c(1.6, 1.4), c(1.4, 1.4), c(1.4, 1.2) )), st_polygon( list(rbind( c(0.6, 0.6), c(0.9, 0.6), c(0.9, 0.9), c(0.6, 0.9), c(0.6, 0.6) ), rbind( c(0.7, 0.7), c(0.8, 0.8), c(0.8, 0.7), c(0.7, 0.7) )) ))), crs = 4326) ## sf objects creation --------- point_sf = st_sf(geometry = point_sfc) linestring_sf = st_sf(geometry = linestring_sfc) polygon_sf = st_sf(geometry = polygon_sfc) multipoint_sf = st_sf(geometry = multipoint_sfc) multilinestring_sf = st_sf(geometry = multilinestring_sfc) multipolygon_sf = st_sf(geometry = multipolygon_sfc) geometrycollection_sf = st_cast( c( point_sfc, linestring_sfc, polygon_sfc, multipoint_sfc, multilinestring_sfc, multipolygon_sfc ), "GEOMETRYCOLLECTION" ) ## single plots ---------- p_point_sf = ggplot() + geom_sf(data = point_sf) + labs(title = "POINT") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_linestring_sf = ggplot() + geom_sf(data = linestring_sf) + labs(title = "LINESTRING") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_polygon_sf = ggplot() + geom_sf(data = polygon_sf, fill = "gray") + labs(title = "POLYGON") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_multipoint_sf = ggplot() + geom_sf(data = multipoint_sf) + labs(title = "MULTIPOINT") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_multilinestring_sf = ggplot() + geom_sf(data = multilinestring_sf) + labs(title = "MULTILINESTRING") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_multipolygon_sf = ggplot() + geom_sf(data = multipolygon_sf, fill = "gray") + labs(title = "MULTIPOLYGON") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme(line = element_blank(), axis.text = element_blank(), axis.title = element_blank()) p_geometrycollection_sf = ggplot() + geom_sf(data = point_sf) + geom_sf(data = linestring_sf) + geom_sf(data = polygon_sf, fill = "gray") + geom_sf(data = multipoint_sf) + geom_sf(data = multilinestring_sf) + geom_sf(data = multipolygon_sf, fill = "gray") + labs(title = "GEOMETRYCOLLECTION") + coord_sf(xlim = c(0.6, 1.6), ylim = c(0.4, 1.4)) + theme( line = element_blank(), axis.text = element_blank(), axis.title = element_blank(), title = element_text(size = 8) ) ## combine plot ------------ png("images/sf-classes.png", height = 600, width = 600) # Empty grob for spacing b = nullGrob() # per @baptiste's comment, use nullGrob() instead of rectGrob() # grid.bezier with a few hard-coded settings mygb = function(x, y) { grid.bezier( x = x, y = y, gp = gpar(col = "gray", fill = "gray"), arrow = arrow(type = "closed", length = unit(2, "mm")) ) } r1 = arrangeGrob( b, p_multilinestring_sf, b, p_multipoint_sf, b, layout_matrix = rbind(c(1, 2, 3, 4, 5)), widths = c(13.3, 30, 13.3, 30, 13.3) ) r2 = arrangeGrob(b) r3 = arrangeGrob( p_multipolygon_sf, b, p_geometrycollection_sf, b, p_polygon_sf, layout_matrix = rbind(c(1, 2, 3, 4, 5)), widths = c(30, 5, 30, 5, 30) ) r4 = arrangeGrob(b) r5 = arrangeGrob( b, p_point_sf, b, p_linestring_sf, b, layout_matrix = rbind(c(1, 2, 3, 4, 5)), widths = c(13.3, 30, 13.3, 30, 13.3) ) grid.arrange(r1, r2, r3, r4, r5, ncol = 1, heights = c(30, 5, 30, 5, 30)) # topleft arrow vp = viewport( x = 0.35, y = 0.66, width = 0.08, height = 0.1 ) pushViewport(vp) # grid.rect(gp=gpar(fill="black", alpha=0.1)) # Use this to see where your viewport is located on the full graph layout mygb(x = c(0, 0.4, 0.4, 1), y = c(1, 0.4, 0.4, 0)) # left arrow popViewport() vp = viewport( x = 0.33, y = 0.5, width = 0.12, height = 0.1 ) pushViewport(vp) mygb(x = c(0, 0.33, 0.66, 1), y = c(0.5, 0.5, 0.5, 0.5)) # bottom left arrow popViewport() vp = viewport( x = 0.35, y = 0.31, width = 0.08, height = 0.1 ) pushViewport(vp) mygb(x = c(0, 0.6, 0.6, 1), y = c(0, 0.4, 0.4, 1)) # bottom right arrow popViewport() vp = viewport( x = 0.65, y = 0.31, width = 0.08, height = 0.1 ) pushViewport(vp) mygb(x = c(1, 0.4, 0.4, 0), y = c(0, 0.4, 0.4, 1)) # right arrow popViewport() vp = viewport( x = 0.68, y = 0.5, width = 0.12, height = 0.1 ) pushViewport(vp) mygb(x = c(1, 0.66, 0.33, 0), y = c(0.5, 0.5, 0.5, 0.5)) # top right arrow popViewport() vp = viewport( x = 0.65, y = 0.66, width = 0.08, height = 0.1 ) pushViewport(vp) mygb(x = c(1, 0.6, 0.6, 0), y = c(1, 0.4, 0.4, 0)) dev.off() ================================================ FILE: code/sfheaders.Rmd ================================================ --- title: "sfheaders" output: html_document editor_options: chunk_output_type: console --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) ``` ```{r sfheaers-setup} ## Detatch {sf} to remove 'print' methods ## because I want to show the underlying structure ## ## library(sf) will be called later pkgload::unload("sf") ``` The design philosophy of `{sfheaders}` is to 1. **build** `{sf}` objects from vectors, matrices and data.frames - without depending on the `{sf}` library 2. **expose** all C++ code through header files (hence the name, `sfheaders`) It's intended that every operation you can do in R, you can also do in C++. I anticipate 99% of users will only need the R functions. But the C++ header files are there if you are building your own package with C++ code and would like to build `{sf}` objects directly in C++. This package is not directly affiliated with the `{sf}` library, but every effort is made to keep it aligned and create valid `sf` objects. ## Building sf objects in R The simplest use case for `{sfheaders}` is best demonstrated with examples of building `sfg`, `sfc` and `sf` objects. ### Simple Feature Geometry - sfg Here are examples showing - a vector converted to `sfg_POINT` - a matrix converted to `sfg_LINESTRING` - a data.frame converted to `sfg_POLYGON` ```{r sfheaders-sfg_point} v = c(1,1) sfheaders::sfg_point(obj = v) ``` ```{r sfheaders-sfg_linestring} n = 8 m = matrix(1:n, ncol = 2) sfheaders::sfg_linestring(obj = m) ``` ```{r sfheaders-sfg_polygon} n = 4 df = data.frame( x = 1:n , y = n:1 ) sfheaders::sfg_polygon(obj = df) ``` In these examples I have deliberately not used the print methods that come with the `{sf}` library, to show you the underlying structures of the objects. If I now load `{sf}` ```{r sfheaders-library-sf} library(sf) ``` and create the `sfg_POLYGON` again, you'll see it's printed to the console as a regular `{sf}` object ```{r sfheaders-sfg_polygon_print} sfheaders::sfg_polygon(obj = df) ``` ### Simple Feature Collections - sfc Reusing the objects `v`, `m`, and `df` we can also build `sfc` objects ```{r sfheaders-sfc_point} sfheaders::sfc_point(obj = v) ``` ```{r sfheaders-sfc_linestring} sfheaders::sfc_linestring(obj = m) ``` ```{r sfheaders-sfc_polygon} sfheaders::sfc_polygon(obj = df) ``` ### Simple Features - sf And similarly, `sf` objects ```{r sfheaders-sfc_point} sfheaders::sf_point(obj = v) ``` ```{r sfheaders-sfc_linestring} sfheaders::sf_linestring(obj = m) ``` ```{r sfheaders-sfc_polygon} sfheaders::sf_polygon(obj = df) ``` In each of these examples you'll notices the CRS (coordinate reference system) is not defined. If you plan on doing any calculations or geometric operations using `{sf}` functions you'll need to set this manually, for example ```{r sfheaders-crs} sf <- sfheaders::sfg_polygon(obj = df) sf::st_crs(sf) <- 4326 ## Web Mecarter (long/lat) ``` ## Building sf objects in C++ To use the C++ API you need to link to both the `{sfheaders}` and `{geometries}` libraries. If you're building a package you define these links in the `LinkingTo` section of the `DESCRIPTION` file. For example, this is the `LinkingTo` section of the [`{geojsonsf}`](https://github.com/SymbolixAU/geojsonsf/blob/master/DESCRIPTION) library. ``` LinkingTo: geometries, jsonify (>= 1.1.1), rapidjsonr (>= 1.2.0), Rcpp, sfheaders (>= 0.2.2) ``` Now all the C++ headers in `{sfheaders}` will be available in your package, just define them as `#include` statements at the top of your code and away you go. Here are the same examples as before, showing - a vector converted to `sfg_POINT` - a matrix converted to `sfg_LINESTRING` - a data.frame converted to `sfg_POLYGON` but written as C++ (through Rcpp) ```c++ #include "sfheaders/sfg/point/sfg_point.hpp" #include "sfheaders/sfg/linestring/sfg_linestring.hpp" #include "sfheaders/sfg/polygon/sfg_polygon.hpp" //' Create Point //' //' builds an sfg_point object from a valid SEXP type //' //' [[Rcpp::export]] SEXP create_point(SEXP x) { return sfheaders::sfg::sfg_point( x ); } //' Create Linestring //' //' builds an sfg_linestring object from a vaild SEXP type //' //' [[Rcpp::export]] SEXP create_linestring(SEXP x) { return sfheaders::sfg::sfg_linestring( x ); } //' Create Polygon //' //' builds an sfg_polygon object from a vaild SEXP type //' //' [[Rcpp::export]] SEXP create_polygon(SEXP x) { return sfheaders::sfg::sfg_polygon( x ); } ``` ================================================ FILE: extdata/.gitignore ================================================ l_all.rds 12-sp_conv_cv_test.rds ================================================ FILE: extdata/coffee-data-messy.csv ================================================ Monthly export statistics - November 2017,,,,,, In thousand 60kg bags,,,,,, ,November 2016,November 2017,% change,October - November,, ,,,,2016/17,2017/18,% change TOTAL,9 930,9 019,-9.2%,19 862,17 615,-11.3% Arabicas,6 410,6 060,-5.5%,12 736,11 836,-7.1% Colombian Milds,1 437,1 272,-11.5%,2 752,2 473,-10.1% Other Milds,1 590,1 734,9.1%,3 158,3 243,2.7% Brazilian Naturals,3 383,3 054,-9.7%,6 826,6 120,-10.3% Robustas,3 520,2 959,-15.9%,7 127,5 779,-18.9% Angola,0,0,,0,0, Bolivia,3,4,42.5%,5,8,48.9% Brazil,3 277,2 786,-15.0%,6 640,5 663,-14.7% Burundi,37,38,3.0%,66,72,8.5% Cameroon,8,6,-21.8%,15,18,16.9% Central African Republic,0,0,,0,0, "Congo, Dem. Rep. of",4,12,240.9%,7,24,267.7% Colombia,1 330,1 169,-12.1%,2 560,2 293,-10.5% Costa Rica,28,32,14.6%,71,46,-34.8% Côte d'Ivoire,114,130,14.4%,165,202,22.1% Cuba,0,0,,0,0, Dominican Republic,1,0,,2,2,49.8% Ecuador,87,62,-28.2%,192,133,-30.8% El Salvador,5,8,54.4%,16,23,41.4% Ethiopia,215,283,32.1%,424,560,31.9% Gabon,0,0,,0,0, Ghana,1,1,17.2%,2,2,17.2% Guatemala,58,70,20.9%,105,130,23.8% Honduras,149,165,11.2%,214,217,1.7% India,453,566,25.0%,887,1 014,14.4% Indonesia,742,360,-51.5%,1 643,960,-41.6% Jamaica,0,1,,1,1,51.9% Kenya,60,50,-16.2%,113,98,-13.4% Liberia,0,0,,0,0, Madagascar,3,5,39.4%,8,9,16.6% Malawi,3,1,-60.2%,3,2,-40.5% Mexico,151,220,45.6%,340,420,23.5% Nepal,0,0,,0,0, Nicaragua,42,45,7.1%,97,99,2.1% Panama,3,3,13.6%,8,6,-27.4% Papua New Guinea,114,74,-35.1%,256,161,-37.2% Paraguay,0,0,,0,0, Peru,585,625,6.8%,1 175,1 228,4.5% Philippines,2,0,,5,0, Rwanda,36,42,16.9%,75,82,9.8% Sierra Leone,1,3,114.4%,3,6,86.8% Tanzania,81,66,-18.1%,150,137,-8.7% Thailand,39,16,-59.5%,80,32,-60.0% Timor-Leste,14,2,-85.3%,25,4,-83.7% Togo,2,3,0.1%,4,5,19.1% Uganda,408,443,8.7%,617,825,33.7% Venezuela,0,0,,0,0, Vietnam,1 844,1 700,-7.8%,3 817,3 075,-19.4% Yemen,4,1,-82.8%,10,2,-84.6% Zambia,3,0,,4,0, Zimbabwe,1,1,-47.8%,2,1,-38.2% Others,23,26,12.7%,55,56,1.2% Note: Group sub-totals take into account the corresponding share of each type of coffee exported by countries that produce and,,,,,, "export both Arabica and Robusta in significant volumes. It should be noted that, where applicable, an Arabica/Robusta ratio of",,,,,, 50/50 has been used to convert processed coffee into Green Bean Equivalent (GBE),,,,,, "A figure of 0 in the table can mean a volume of less than 500 bags, due to rounding. Full data in Excel format are available to paid",,,,,, subscribers,,,,,, Next update: 31 January 2018,,,,,,  International Coffee Organization,,,,,, ================================================ FILE: extdata/coffee-data.csv ================================================ name_long,y16,y17 Colombian Milds,1437,1272 Other Milds,1590,1734 Brazilian Naturals,3383,3054 Robustas,3520,2959 Angola,0,0 Bolivia,3,4 Brazil,3277,2786 Burundi,37,38 Cameroon,8,6 Central African Republic,0,0 "Congo, Dem. Rep. of",4,12 Colombia,1330,1169 Costa Rica,28,32 Côte d'Ivoire,114,130 Cuba,0,0 Dominican Republic,1,0 Ecuador,87,62 El Salvador,5,8 Ethiopia,215,283 Gabon,0,0 Ghana,1,1 Guatemala,58,70 Honduras,149,165 India,453,566 Indonesia,742,360 Jamaica,0,1 Kenya,60,50 Liberia,0,0 Madagascar,3,5 Malawi,3,1 Mexico,151,220 Nepal,0,0 Nicaragua,42,45 Panama,3,3 Papua New Guinea,114,74 Paraguay,0,0 Peru,585,625 Philippines,2,0 Rwanda,36,42 Sierra Leone,1,3 Tanzania,81,66 Thailand,39,16 Timor-Leste,14,2 Togo,2,3 Uganda,408,443 Venezuela,0,0 Vietnam,1844,1700 Yemen,4,1 Zambia,3,0 Zimbabwe,1,1 Others,23,26 Note: Group sub-totals take into account the corresponding share of each type of coffee exported by countries that produce and,NA,NA "export both Arabica and Robusta in significant volumes. It should be noted that, where applicable, an Arabica/Robusta ratio of",NA,NA 50/50 has been used to convert processed coffee into Green Bean Equivalent (GBE),NA,NA "A figure of 0 in the table can mean a volume of less than 500 bags, due to rounding. Full data in Excel format are available to paid",NA,NA subscribers,NA,NA Next update: 31 January 2018,NA,NA  International Coffee Organization,NA,NA ================================================ FILE: extdata/contributors.csv ================================================ name,link prosoitos,https://github.com/prosoitos tibbles-and-tribbles,https://github.com/tibbles-and-tribbles florisvdh,https://github.com/florisvdh babayoshihiko,https://github.com/babayoshihiko katygregg,https://github.com/katygregg Lvulis,https://github.com/Lvulis rsbivand,https://github.com/rsbivand iod-ine,https://github.com/iod-ine KiranmayiV,https://github.com/KiranmayiV cuixueqin,https://github.com/cuixueqin defuneste,https://github.com/defuneste smkerr,https://github.com/smkerr zmbc,https://github.com/zmbc marcosci,https://github.com/marcosci darrellcarvalho,https://github.com/darrellcarvalho dcooley,https://github.com/dcooley FlorentBedecarratsNM,https://github.com/FlorentBedecarratsNM erstearns,https://github.com/erstearns appelmar,https://github.com/appelmar MikeJohnPage,https://github.com/MikeJohnPage eyesofbambi,https://github.com/eyesofbambi krystof236,https://github.com/krystof236 nickbearman,https://github.com/nickbearman tylerlittlefield,https://github.com/tylerlittlefield sdesabbata,https://github.com/sdesabbata howardbaik,https://github.com/howardbaik edzer,https://github.com/edzer pat-s,https://github.com/pat-s giocomai,https://github.com/giocomai KHwong12,https://github.com/KHwong12 LaurieLBaker,https://github.com/LaurieLBaker eblondel,https://github.com/eblondel MarHer90,https://github.com/MarHer90 mdsumner,https://github.com/mdsumner ahmohil,https://github.com/ahmohil richfitz,https://github.com/richfitz VLucet,https://github.com/VLucet wdearden,https://github.com/wdearden yihui,https://github.com/yihui adambhouston,https://github.com/adambhouston chihinl,https://github.com/chihinl cshancock,https://github.com/cshancock e-clin,https://github.com/e-clin ec-nebi,https://github.com/ec-nebi gregor-d,https://github.com/gregor-d jasongrahn,https://github.com/jasongrahn p-kono,https://github.com/p-kono pokyah,https://github.com/pokyah schuetzingit,https://github.com/schuetzingit tim-salabim,https://github.com/tim-salabim tszberkowitz,https://github.com/tszberkowitz vlarmet,https://github.com/vlarmet ateucher,https://github.com/ateucher annakrystalli,https://github.com/annakrystalli andtheWings,https://github.com/andtheWings kant,https://github.com/kant gavinsimpson,https://github.com/gavinsimpson Himanshuteli,https://github.com/Himanshuteli yutannihilation,https://github.com/yutannihilation jimr1603,https://github.com/jimr1603 jbixon13,https://github.com/jbixon13 jkennedyie,https://github.com/jkennedyie olyerickson,https://github.com/olyerickson yvkschaefer,https://github.com/yvkschaefer katiejolly,https://github.com/katiejolly kwhkim,https://github.com/kwhkim layik,https://github.com/layik mpaulacaldas,https://github.com/mpaulacaldas mtennekes,https://github.com/mtennekes mvl22,https://github.com/mvl22 ganes1410,https://github.com/ganes1410 ================================================ FILE: extdata/generic_map_pkgs.csv ================================================ package,published,title,depends_count,suggests_count,tidyverse_happy,has_vignette_build,has_tests,reverse_count,dl_last_month,ci,test_coverage,stars,forks,last_commit,last_issue_closed,contributors ggplot2,2022-11-04,Create Elegant Data Visualisations Using the Grammar of Graphics,1,23,TRUE,TRUE,TRUE,3791,2785489,NONE,CodeCov,5.7k,1.9k,0.03333333333333333,0.03333333333333333,281 googleway,2022-01-24,Accesses Google Maps APIs to Retrieve Data and Plot Maps,1,3,TRUE,TRUE,TRUE,3,3997,Travis,CodeCov,217,43,51.56666666666667,0.26666666666666666,5 ggspatial,2022-11-24,Spatial Data Framework for ggplot2,1,13,TRUE,FALSE,TRUE,5,10446,NONE,CodeCov,332,32,0.6,0.7333333333333333,10 leaflet,2022-03-23,"Create Interactive Web Maps with the JavaScript 'Leaflet' Library",1,10,TRUE,FALSE,TRUE,82,67213,NONE,NONE,739,488,37.2,10.233333333333333,36 mapview,2022-04-16,Interactive Viewing of Spatial Data in R,2,13,FALSE,FALSE,TRUE,10,18315,NONE,CodeCov,447,91,1.2666666666666666,1.5666666666666667,21 plotly,2022-11-07,Create Interactive Web Graphics via 'plotly.js',2,26,TRUE,FALSE,TRUE,307,421790,NONE,NONE,2.3k,607,1.1666666666666667,50.1,47 rasterVis,2022-11-27,Visualization Methods for Raster Data,3,5,FALSE,FALSE,FALSE,12,10496,NONE,NONE,73,21,0.5,0.3333333333333333,4 tmap,2022-03-02,Thematic Maps,2,14,TRUE,TRUE,TRUE,15,24737,Appveyor,NONE,692,110,30.133333333333333,24.933333333333334,26 ================================================ FILE: extdata/gis-vs-gds-table.csv ================================================ "Attribute","Desktop GIS (GUI)","R" "Home disciplines","Geography","Computing, Statistics" "Software focus","Graphical User Interface","Command line" "Reproducibility","Minimal","Maximal" ================================================ FILE: extdata/package_list.csv ================================================ "Name","Title","version" "bookdown","Authoring Books and Technical Documents with R Markdown [@R-bookdown]","0.7" "cartogram","Create Cartograms with R [@R-cartogram]","0.1.0" "dismo","Species Distribution Modeling [@R-dismo]","1.1.4" "geosphere","Spherical Trigonometry [@R-geosphere]","1.5.7" "ggmap","Spatial Visualization with ggplot2 [@R-ggmap]","2.6.1" "ggplot2","Create Elegant Data Visualisations Using the Grammar of Graphics [@R-ggplot2]","3.0.0.9000" "gstat","Spatial and Spatio-Temporal Geostatistical Modelling, Prediction [@R-gstat]","1.1.6" "historydata","Datasets for Historians [@R-historydata]","0.2.9001" "htmlwidgets","HTML Widgets for R [@R-htmlwidgets]","1.2" "kableExtra","Construct Complex Table with 'kable' and Pipe Syntax [@R-kableExtra]","0.9.0" "kernlab","Kernel-Based Machine Learning Lab [@R-kernlab]","0.9.26" "knitr","A General-Purpose Package for Dynamic Report Generation in R [@R-knitr]","1.20" "latticeExtra","Extra Graphical Utilities Based on Lattice [@R-latticeExtra]","0.6.28" "leaflet","Create Interactive Web Maps with the JavaScript 'Leaflet' [@R-leaflet]","2.0.1" "link2GI","Linking Geographic Information Systems, Remote Sensing and Other [@R-link2GI]","0.3.0" "lwgeom","Bindings to Selected 'liblwgeom' Functions for Simple Features [@R-lwgeom]","0.1.4" "mapview","Interactive Viewing of Spatial Data in R [@R-mapview]","2.4.0" "microbenchmark","Accurate Timing Functions [@R-microbenchmark]","1.4.4" "mlr","Machine Learning in R [@R-mlr]","2.12.1" "osmdata","Import 'OpenStreetMap' Data as Simple Features or Spatial [@R-osmdata]","0.0.7" "pROC","Display and Analyze ROC Curves [@R-pROC]","1.12.1" "ranger","A Fast Implementation of Random Forests [@R-ranger]","0.10.1" "raster","Geographic Data Analysis and Modeling [@R-raster]","2.6.7" "rcartocolor","'CARTOColors' Palettes [@R-rcartocolor]","0.0.22" "rgdal","Bindings for the 'Geospatial' Data Abstraction Library [@R-rgdal]","1.3.3" "rgeos","Interface to Geometry Engine - Open Source ('GEOS') [@R-rgeos]","0.3.28" "rgrass7","Interface Between GRASS 7 Geographical Information System and R [@R-rgrass7]","0.1.10" "rmapshaper","Client for 'mapshaper' for 'Geospatial' Operations [@R-rmapshaper]","0.4.0" "rmarkdown","Dynamic Documents for R [@R-rmarkdown]","1.10" "rnaturalearth","World Map Data from Natural Earth [@R-rnaturalearth]","0.2.0" "rnaturalearthdata","World Vector Map Data from Natural Earth Used in 'rnaturalearth' [@R-rnaturalearthdata]","0.1.0" "RPostgreSQL","R Interface to the 'PostgreSQL' Database System [@R-RPostgreSQL]","0.6.2" "RQGIS","Integrating R with QGIS [@R-RQGIS]","1.0.3" "RSAGA","SAGA Geoprocessing and Terrain Analysis [@R-RSAGA]","1.1.0" "sf","Simple Features for R [@R-sf]","0.6.3" "sp","Classes and Methods for Spatial Data [@R-sp]","1.3.1" "spData","Datasets for Spatial Analysis [@R-spData]","0.2.9.0" "spDataLarge","Large datasets for spatial analysis [@R-spDataLarge]","0.2.7.0" "stplanr","Sustainable Transport Planning [@R-stplanr]","0.2.4.9000" "tabularaster","Tidy Tools for 'Raster' Data [@R-tabularaster]","0.5.0" "tidyverse","Easily Install and Load the 'Tidyverse' [@R-tidyverse]","1.2.1" "tmap","Thematic Maps [@R-tmap]","2.0.1" "tmaptools","Thematic Map Tools [@R-tmaptools]","2.0.1" "tree","Classification and Regression Trees [@R-tree]","1.0.39" "vegan","Community Ecology Package [@R-vegan]","2.5.2" ================================================ FILE: extdata/sfs-st-cast.csv ================================================ input_geom,POINT,MULTIPOINT,LINESTRING,MULTILINESTRING,POLYGON,MULTIPOLYGON,GEOMETRYCOLLECTION POINT(1),1,1,1,NA,NA,NA,NA MULTIPOINT(1),4,1,1,1,1,NA,NA LINESTRING(1),5,1,1,1,1,NA,NA MULTILINESTRING(1),7,2,2,1,NA,NA,NA POLYGON(1),5,1,1,1,1,1,NA MULTIPOLYGON(1),10,1,NA,1,2,1,1 GEOMETRYCOLLECTION(1),9,1,NA,NA,NA,NA,1 ================================================ FILE: extdata/specific_map_pkgs.csv ================================================ package,published,title,depends_count,suggests_count,tidyverse_happy,has_vignette_build,has_tests,reverse_count,dl_last_month,ci,test_coverage,stars,forks,last_commit,last_issue_closed,contributors cartogram,2020-08-26,Create Cartograms with R,NA,4,FALSE,FALSE,FALSE,1,3109,NONE,NONE,134,13,42.1,17.6,4 geogrid,2018-12-11,Turn Geospatial Polygons into Regular or Hexagonal Grids,NA,3,FALSE,FALSE,TRUE,1,1955,Travis,CodeCov,378,32,54.2,53.86666666666667,6 geofacet,2020-05-26,'ggplot2' Faceting Utilities for Geographical Data,1,5,TRUE,TRUE,TRUE,1,1042,Travis,CodeCov,318,40,36.36666666666667,14.433333333333334,2 linemap,2021-01-19,Line Maps,1,2,FALSE,FALSE,TRUE,0,228,NONE,CodeCov,110,6,28.433333333333334,13.533333333333333,0 tanaka,2022-07-04,Design Shaded Contour Lines (or Tanaka) Maps,NA,3,FALSE,FALSE,TRUE,1,438,NONE,CodeCov,71,3,10.733333333333333,2.7333333333333334,0 rayshader,2021-04-28,Create Maps and Visualize Data in 2D and 3D,1,9,TRUE,FALSE,FALSE,2,2982,NONE,NA,1.9k,204,0.16666666666666666,0.16666666666666666,8 ================================================ FILE: extdata/top_dls.csv ================================================ package,Downloads,date r5r,4774,2023-11-14 stars,1354,2023-11-14 leafem,1013,2023-11-14 spdep,881,2023-11-14 tmap,873,2023-11-14 lwgeom,843,2023-11-14 mapview,768,2023-11-14 rnaturalearth,593,2023-11-14 tmaptools,575,2023-11-14 gstat,552,2023-11-14 leafpop,474,2023-11-14 sftime,438,2023-11-14 epiR,429,2023-11-14 tigris,364,2023-11-14 ggspatial,354,2023-11-14 tidycensus,324,2023-11-14 osrm,264,2023-11-14 mapiso,263,2023-11-14 geojsonio,255,2023-11-14 spatialreg,244,2023-11-14 exactextractr,236,2023-11-14 tidyterra,199,2023-11-14 rmapshaper,196,2023-11-14 gridpattern,195,2023-11-14 ggVennDiagram,169,2023-11-14 pgirmess,163,2023-11-14 transformr,158,2023-11-14 concaveman,153,2023-11-14 fmesher,147,2023-11-14 metR,143,2023-11-14 automap,119,2023-11-14 elevatr,113,2023-11-14 lidR,111,2023-11-14 cartogram,100,2023-11-14 smoothr,99,2023-11-14 BIEN,88,2023-11-14 giscoR,80,2023-11-14 geobr,77,2023-11-14 rgeoda,74,2023-11-14 cartography,72,2023-11-14 mapedit,71,2023-11-14 BayesX,67,2023-11-14 leafpm,66,2023-11-14 INLAspacetime,64,2023-11-14 gdalUtilities,64,2023-11-14 GWmodel,63,2023-11-14 ctmm,63,2023-11-14 ozmaps,63,2023-11-14 secr,61,2023-11-14 mapsf,58,2023-11-14 cshapes,57,2023-11-14 leafgl,57,2023-11-14 nngeo,57,2023-11-14 stats19,57,2023-11-14 maptiles,53,2023-11-14 inlabru,52,2023-11-14 ncdfgeom,52,2023-11-14 areal,50,2023-11-14 swfscMisc,48,2023-11-14 Morpho,47,2023-11-14 cubble,47,2023-11-14 geoknife,46,2023-11-14 amt,38,2023-11-14 rpostgis,38,2023-11-14 camtrapR,37,2023-11-14 Directional,36,2023-11-14 stplanr,36,2023-11-14 geogrid,35,2023-11-14 spatialsample,35,2023-11-14 CARBayes,34,2023-11-14 FedData,33,2023-11-14 GEOmap,33,2023-11-14 MODIStsp,33,2023-11-14 basemaps,32,2023-11-14 blockCV,32,2023-11-14 epikit,32,2023-11-14 mapboxapi,32,2023-11-14 sen2r,32,2023-11-14 geofacet,31,2023-11-14 mapSpain,31,2023-11-14 ows4R,31,2023-11-14 pliman,31,2023-11-14 prioritizr,31,2023-11-14 sfnetworks,30,2023-11-14 CARBayesST,29,2023-11-14 BIOMASS,28,2023-11-14 link2GI,28,2023-11-14 rcarbon,28,2023-11-14 intamap,27,2023-11-14 pavo,27,2023-11-14 sits,27,2023-11-14 MODISTools,26,2023-11-14 tinytiger,26,2023-11-14 bcdata,25,2023-11-14 daymetr,25,2023-11-14 rtop,25,2023-11-14 CARBayesdata,24,2023-11-14 SUNGEO,24,2023-11-14 ggOceanMaps,24,2023-11-14 gtfstools,24,2023-11-14 iemisc,24,2023-11-14 lgcp,24,2023-11-14 RCzechia,23,2023-11-14 bcmaps,23,2023-11-14 crawl,23,2023-11-14 nhdplusTools,23,2023-11-14 rdhs,23,2023-11-14 simplevis,23,2023-11-14 spsurvey,23,2023-11-14 suntools,23,2023-11-14 wdpar,23,2023-11-14 PReMiuM,22,2023-11-14 emstreeR,22,2023-11-14 nominatimlite,22,2023-11-14 osmextract,22,2023-11-14 raptr,22,2023-11-14 spatsurv,22,2023-11-14 Rsagacmd,21,2023-11-14 SSDM,21,2023-11-14 cleangeo,21,2023-11-14 geouy,21,2023-11-14 mapchina,21,2023-11-14 oceanmap,21,2023-11-14 tidytransit,21,2023-11-14 CCAMLRGIS,20,2023-11-14 clhs,20,2023-11-14 meteo,20,2023-11-14 openairmaps,20,2023-11-14 openeo,20,2023-11-14 MassWateR,19,2023-11-14 ggseg,19,2023-11-14 hydroloom,19,2023-11-14 malariaAtlas,19,2023-11-14 rerddapXtracto,19,2023-11-14 sphet,19,2023-11-14 spmoran,19,2023-11-14 SpatialPosition,18,2023-11-14 cartograflow,18,2023-11-14 grainscape,18,2023-11-14 mapsapi,18,2023-11-14 pct,18,2023-11-14 prioritizrdata,18,2023-11-14 antaresViz,17,2023-11-14 canadianmaps,17,2023-11-14 hereR,17,2023-11-14 spmodel,17,2023-11-14 tidyUSDA,17,2023-11-14 BFS,16,2023-11-14 SDLfilter,16,2023-11-14 TUFLOWR,16,2023-11-14 aopdata,16,2023-11-14 covid19br,16,2023-11-14 crsuggest,16,2023-11-14 ebirdst,16,2023-11-14 flexpolyline,16,2023-11-14 galah,16,2023-11-14 geonetwork,16,2023-11-14 rivnet,16,2023-11-14 sfarrow,16,2023-11-14 sfdep,16,2023-11-14 surveyvoi,16,2023-11-14 terrainr,16,2023-11-14 ursa,16,2023-11-14 waywiser,16,2023-11-14 wildlifeDI,16,2023-11-14 CopernicusDEM,15,2023-11-14 ForestTools,15,2023-11-14 OasisR,15,2023-11-14 RPyGeo,15,2023-11-14 chirps,15,2023-11-14 cmsafvis,15,2023-11-14 csodata,15,2023-11-14 feltr,15,2023-11-14 geostan,15,2023-11-14 graph4lg,15,2023-11-14 gtfs2gps,15,2023-11-14 h3jsr,15,2023-11-14 hemispheR,15,2023-11-14 mlr3spatial,15,2023-11-14 robis,15,2023-11-14 spatsoc,15,2023-11-14 stcos,15,2023-11-14 ARPALData,14,2023-11-14 MazamaSpatialUtils,14,2023-11-14 OSMscale,14,2023-11-14 PL94171,14,2023-11-14 SpatialKDE,14,2023-11-14 arcpullr,14,2023-11-14 bcputility,14,2023-11-14 bigDM,14,2023-11-14 chilemapas,14,2023-11-14 covidcast,14,2023-11-14 cyclestreets,14,2023-11-14 eSDM,14,2023-11-14 hyfo,14,2023-11-14 importinegi,14,2023-11-14 mapping,14,2023-11-14 meteospain,14,2023-11-14 opendatatoronto,14,2023-11-14 opentripplanner,14,2023-11-14 plotdap,14,2023-11-14 MTA,13,2023-11-14 RchivalTag,13,2023-11-14 abmR,13,2023-11-14 anipaths,13,2023-11-14 bbsBayes,13,2023-11-14 dggridR,13,2023-11-14 dynamicSDM,13,2023-11-14 geofi,13,2023-11-14 googletraffic,13,2023-11-14 jpmesh,13,2023-11-14 mapme.biodiversity,13,2023-11-14 meteoland,13,2023-11-14 move2,13,2023-11-14 movecost,13,2023-11-14 rasterDT,13,2023-11-14 rmapzen,13,2023-11-14 rnrfa,13,2023-11-14 secrlinear,13,2023-11-14 spNetwork,13,2023-11-14 spatialrisk,13,2023-11-14 vein,13,2023-11-14 CopernicusMarine,12,2023-11-14 CropScapeR,12,2023-11-14 GWSDAT,12,2023-11-14 LabourMarketAreas,12,2023-11-14 NipponMap,12,2023-11-14 abstr,12,2023-11-14 atakrig,12,2023-11-14 bfsMaps,12,2023-11-14 bioregion,12,2023-11-14 card,12,2023-11-14 crimedata,12,2023-11-14 eixport,12,2023-11-14 enmSdmX,12,2023-11-14 fitbitViz,12,2023-11-14 geodiv,12,2023-11-14 geomerge,12,2023-11-14 happign,12,2023-11-14 himach,12,2023-11-14 intSDM,12,2023-11-14 ipdw,12,2023-11-14 leastcostpath,12,2023-11-14 macleish,12,2023-11-14 mgwrsar,12,2023-11-14 micromap,12,2023-11-14 naijR,12,2023-11-14 pRecipe,12,2023-11-14 rangeMapper,12,2023-11-14 riverdist,12,2023-11-14 soilassessment,12,2023-11-14 starsExtra,12,2023-11-14 stppSim,12,2023-11-14 tanaka,12,2023-11-14 tilemaps,12,2023-11-14 EmissV,11,2023-11-14 MapGAM,11,2023-11-14 VicmapR,11,2023-11-14 ascotraceR,11,2023-11-14 bayesmove,11,2023-11-14 bdc,11,2023-11-14 bdl,11,2023-11-14 btb,11,2023-11-14 cartogramR,11,2023-11-14 cdcfluview,11,2023-11-14 conleyreg,11,2023-11-14 divseg,11,2023-11-14 downscale,11,2023-11-14 eiCompare,11,2023-11-14 eks,11,2023-11-14 epm,11,2023-11-14 gfcanalysis,11,2023-11-14 motif,11,2023-11-14 prevR,11,2023-11-14 rangeBuilder,11,2023-11-14 redist,11,2023-11-14 rgugik,11,2023-11-14 secrdesign,11,2023-11-14 supercells,11,2023-11-14 wdnr.gis,11,2023-11-14 BeeBDC,10,2023-11-14 BoundaryStats,10,2023-11-14 CRTspat,10,2023-11-14 CatastRo,10,2023-11-14 FORTLS,10,2023-11-14 GGoutlieR,10,2023-11-14 GIFT,10,2023-11-14 IceSat2R,10,2023-11-14 LMoFit,10,2023-11-14 PlanetNICFI,10,2023-11-14 PointedSDMs,10,2023-11-14 SDPDmod,10,2023-11-14 SWMPrExtension,10,2023-11-14 SimSurvey,10,2023-11-14 animalEKF,10,2023-11-14 capm,10,2023-11-14 censable,10,2023-11-14 chessboard,10,2023-11-14 cropDemand,10,2023-11-14 cropZoning,10,2023-11-14 datazoom.amazonia,10,2023-11-14 dwp,10,2023-11-14 echor,10,2023-11-14 ecochange,10,2023-11-14 expowo,10,2023-11-14 geocmeans,10,2023-11-14 geogenr,10,2023-11-14 geomultistar,10,2023-11-14 geotopbricks,10,2023-11-14 helsinki,10,2023-11-14 landsepi,10,2023-11-14 lidaRtRee,10,2023-11-14 micromapST,10,2023-11-14 mregions,10,2023-11-14 oceanexplorer,10,2023-11-14 oceanis,10,2023-11-14 rWCVP,10,2023-11-14 rflexscan,10,2023-11-14 spMaps,10,2023-11-14 tilegramsR,10,2023-11-14 track2KBA,10,2023-11-14 weed,10,2023-11-14 DEPONS2R,9,2023-11-14 GPSeqClus,9,2023-11-14 MetricGraph,9,2023-11-14 Momocs,9,2023-11-14 Orcs,9,2023-11-14 appeears,9,2023-11-14 arealDB,9,2023-11-14 atpolR,9,2023-11-14 canadamaps,9,2023-11-14 cdrcR,9,2023-11-14 fgdr,9,2023-11-14 gbm.auto,9,2023-11-14 geoAr,9,2023-11-14 geodimension,9,2023-11-14 geomander,9,2023-11-14 ggmapinset,9,2023-11-14 idbr,9,2023-11-14 intamapInteractive,9,2023-11-14 lakemorpho,9,2023-11-14 mapi,9,2023-11-14 neotoma2,9,2023-11-14 nhdR,9,2023-11-14 nlrx,9,2023-11-14 palaeoverse,9,2023-11-14 paleopop,9,2023-11-14 patternize,9,2023-11-14 pspatreg,9,2023-11-14 raceland,9,2023-11-14 readwritesqlite,9,2023-11-14 redlistr,9,2023-11-14 sabre,9,2023-11-14 safedata,9,2023-11-14 satres,9,2023-11-14 sfhotspot,9,2023-11-14 sftrack,9,2023-11-14 shoredate,9,2023-11-14 trackdf,9,2023-11-14 uavRmp,9,2023-11-14 walkboutr,9,2023-11-14 windAC,9,2023-11-14 CSHShydRology,8,2023-11-14 CoastlineFD,8,2023-11-14 FIESTA,8,2023-11-14 FIESTAutils,8,2023-11-14 GeNetIt,8,2023-11-14 GeoAdjust,8,2023-11-14 HDSpatialScan,8,2023-11-14 IRexamples,8,2023-11-14 ISRaD,8,2023-11-14 PAMscapes,8,2023-11-14 SpatialRDD,8,2023-11-14 SurfaceTortoise,8,2023-11-14 VancouvR,8,2023-11-14 adw,8,2023-11-14 bangladesh,8,2023-11-14 basf,8,2023-11-14 bluebike,8,2023-11-14 covid19sf,8,2023-11-14 distanceto,8,2023-11-14 dots,8,2023-11-14 edbuildmapr,8,2023-11-14 envi,8,2023-11-14 forestecology,8,2023-11-14 fsr,8,2023-11-14 fude,8,2023-11-14 geocausal,8,2023-11-14 gtfs2emis,8,2023-11-14 icosa,8,2023-11-14 itsdm,8,2023-11-14 naturaList,8,2023-11-14 oceanic,8,2023-11-14 parlitools,8,2023-11-14 pressuRe,8,2023-11-14 qualmap,8,2023-11-14 rKIN,8,2023-11-14 rasterbc,8,2023-11-14 redistmetrics,8,2023-11-14 remap,8,2023-11-14 rgplates,8,2023-11-14 roads,8,2023-11-14 rts2,8,2023-11-14 sfdct,8,2023-11-14 sgsR,8,2023-11-14 siland,8,2023-11-14 sorvi,8,2023-11-14 stelfi,8,2023-11-14 swfscDAS,8,2023-11-14 telemac,8,2023-11-14 upstartr,8,2023-11-14 voluModel,8,2023-11-14 wflo,8,2023-11-14 EEAaq,7,2023-11-14 GISSB,7,2023-11-14 GWpcor,7,2023-11-14 LAGOSNE,7,2023-11-14 MainExistingDatasets,7,2023-11-14 MazamaSpatialPlots,7,2023-11-14 RWmisc,7,2023-11-14 Rlibkdv,7,2023-11-14 SRTsim,7,2023-11-14 SpatialGraph,7,2023-11-14 cartographer,7,2023-11-14 cft,7,2023-11-14 dafishr,7,2023-11-14 dispeRse,7,2023-11-14 divvy,7,2023-11-14 eiExpand,7,2023-11-14 evolMap,7,2023-11-14 geodrawr,7,2023-11-14 glottospace,7,2023-11-14 graphseg,7,2023-11-14 gwavr,7,2023-11-14 gwpcormapper,7,2023-11-14 hero,7,2023-11-14 kokudosuuchi,7,2023-11-14 lconnect,7,2023-11-14 linemap,7,2023-11-14 mapscanner,7,2023-11-14 ndi,7,2023-11-14 netmap,7,2023-11-14 ohsome,7,2023-11-14 onmaRg,7,2023-11-14 otpr,7,2023-11-14 plantTracker,7,2023-11-14 populR,7,2023-11-14 potential,7,2023-11-14 rasterpic,7,2023-11-14 rbenvo,7,2023-11-14 regfilter,7,2023-11-14 rgeopat2,7,2023-11-14 spacejamr,7,2023-11-14 stampr,7,2023-11-14 starsTileServer,7,2023-11-14 streamDepletr,7,2023-11-14 sugarbag,7,2023-11-14 tectonicr,7,2023-11-14 trackeRapp,7,2023-11-14 transfR,7,2023-11-14 treePlotArea,7,2023-11-14 waver,7,2023-11-14 zonebuilder,7,2023-11-14 GWnnegPCA,6,2023-11-14 SSIMmap,6,2023-11-14 TDLM,6,2023-11-14 comorosmaps,6,2023-11-14 densityarea,6,2023-11-14 ediblecity,6,2023-11-14 extRatum,6,2023-11-14 fisheye,6,2023-11-14 flightplot,6,2023-11-14 geomaroc,6,2023-11-14 ggautomap,6,2023-11-14 gps.track,6,2023-11-14 hwsdr,6,2023-11-14 ispdata,6,2023-11-14 jpgrid,6,2023-11-14 lazysf,6,2023-11-14 njgeo,6,2023-11-14 prisonbrief,6,2023-11-14 pseudohouseholds,6,2023-11-14 ptools,6,2023-11-14 rLFT,6,2023-11-14 raytracing,6,2023-11-14 rdwplus,6,2023-11-14 ref.ICAR,6,2023-11-14 rfishnet2,6,2023-11-14 roughsf,6,2023-11-14 seedreg,6,2023-11-14 simodels,6,2023-11-14 spatgeom,6,2023-11-14 spectator,6,2023-11-14 spnaf,6,2023-11-14 spqdep,6,2023-11-14 tongfen,6,2023-11-14 valhallr,6,2023-11-14 valuemap,6,2023-11-14 velociraptr,6,2023-11-14 vietnameseConverter,6,2023-11-14 Relectoral,5,2023-11-14 SMITIDstruct,5,2023-11-14 WEGE,5,2023-11-14 disaggregation,5,2023-11-14 habCluster,5,2023-11-14 hosm,5,2023-11-14 hypsoLoop,5,2023-11-14 jmastats,5,2023-11-14 klexdatr,5,2023-11-14 nswgeo,5,2023-11-14 pycno,5,2023-11-14 rTLsDeep,5,2023-11-14 segmetric,5,2023-11-14 smartmap,5,2023-11-14 smile,5,2023-11-14 spectralR,5,2023-11-14 tidygeoRSS,5,2023-11-14 tidyrgee,5,2023-11-14 SegEnvIneq,4,2023-11-14 climenv,4,2023-11-14 trigpoints,4,2023-11-14 uci,4,2023-11-14 mapStats,3,2023-11-14 SSN2,2,2023-11-14 versioning,1,2023-11-14 tidysdm,0,2023-11-14 ================================================ FILE: extdata/word-count-time.csv ================================================ n_words,chapter,date,n_pages 934,1,2017-04-15,3.1133333333333333 114,2,2017-04-15,0.38 623,3,2017-04-15,2.0766666666666667 486,4,2017-04-15,1.62 0,5,2017-04-15,0 1023,1,2017-04-29,3.41 254,2,2017-04-29,0.8466666666666667 824,3,2017-04-29,2.7466666666666666 548,4,2017-04-29,1.8266666666666667 47,5,2017-04-29,0.15666666666666668 24,6,2017-04-29,0.08 2958,1,2017-05-07,9.86 1089,2,2017-05-07,3.63 254,3,2017-05-07,0.8466666666666667 824,4,2017-05-07,2.7466666666666666 548,5,2017-05-07,1.8266666666666667 47,6,2017-05-07,0.15666666666666668 2960,1,2017-05-10,9.866666666666667 1091,2,2017-05-10,3.6366666666666667 254,3,2017-05-10,0.8466666666666667 824,4,2017-05-10,2.7466666666666666 548,5,2017-05-10,1.8266666666666667 47,6,2017-05-10,0.15666666666666668 1091,1,2017-05-11,3.6366666666666667 254,2,2017-05-11,0.8466666666666667 824,3,2017-05-11,2.7466666666666666 547,4,2017-05-11,1.8233333333333333 47,5,2017-05-11,0.15666666666666668 24,6,2017-05-11,0.08 1334,1,2017-05-17,NA 255,2,2017-05-17,NA 822,3,2017-05-17,NA 547,4,2017-05-17,NA 47,5,2017-05-17,NA 24,6,2017-05-17,NA 1558,1,2017-05-18,NA 929,2,2017-05-18,NA 11,3,2017-05-18,NA 547,4,2017-05-18,NA 862,5,2017-05-18,NA 47,6,2017-05-18,NA 1021,1,2017-05-19,NA 707,2,2017-05-19,NA 929,3,2017-05-19,NA 11,4,2017-05-19,NA 862,5,2017-05-19,NA 24,6,2017-05-19,NA 1056,1,2017-05-25,NA 1470,2,2017-05-25,NA 899,3,2017-05-25,NA 121,4,2017-05-25,NA 885,5,2017-05-25,NA 235,6,2017-05-25,NA 1058,1,2017-05-31,NA 1671,2,2017-05-31,NA 899,3,2017-05-31,NA 121,4,2017-05-31,NA 1192,5,2017-05-31,NA 235,6,2017-05-31,NA 1058,1,2017-06-08,NA 1887,2,2017-06-08,NA 2761,3,2017-06-08,NA 815,4,2017-06-08,NA 1192,5,2017-06-08,NA 235,6,2017-06-08,NA 1299,1,2017-06-16,NA 2565,2,2017-06-16,NA 3126,3,2017-06-16,NA 1224,4,2017-06-16,NA 1192,5,2017-06-16,NA 211,6,2017-06-16,NA 1377,1,2017-06-22,NA 4397,2,2017-06-22,NA 3126,3,2017-06-22,NA 1224,4,2017-06-22,NA 1192,5,2017-06-22,NA 211,6,2017-06-22,NA 2903,1,2017-06-29,NA 4933,2,2017-06-29,NA 3126,3,2017-06-29,NA 1224,4,2017-06-29,NA 1333,5,2017-06-29,NA 211,6,2017-06-29,NA 2934,1,2017-07-08,NA 4928,2,2017-07-08,NA 3126,3,2017-07-08,NA 1224,4,2017-07-08,NA 1333,5,2017-07-08,NA 211,6,2017-07-08,NA 3456,1,2017-07-15,NA 5381,2,2017-07-15,NA 3300,3,2017-07-15,NA 1659,4,2017-07-15,NA 1333,5,2017-07-15,NA 211,6,2017-07-15,NA 3421,1,2017-07-23,NA 5381,2,2017-07-23,NA 3964,3,2017-07-23,NA 2158,4,2017-07-23,NA 1333,5,2017-07-23,NA 211,6,2017-07-23,NA 3418,1,2017-08-01,NA 5841,2,2017-08-01,NA 3996,3,2017-08-01,NA 2158,4,2017-08-01,NA 1333,5,2017-08-01,NA 211,6,2017-08-01,NA 4652,1,2017-08-11,NA 6069,2,2017-08-11,NA 3866,3,2017-08-11,NA 3566,4,2017-08-11,NA 1333,5,2017-08-11,NA 1033,6,2017-08-11,NA 4998,1,2017-08-17,NA 6433,2,2017-08-17,NA 4095,3,2017-08-17,NA 3566,4,2017-08-17,NA 1333,5,2017-08-17,NA 1033,6,2017-08-17,NA 4998,1,2017-08-26,NA 6971,2,2017-08-26,NA 3880,3,2017-08-26,NA 3486,4,2017-08-26,NA 1347,5,2017-08-26,NA 1033,6,2017-08-26,NA 4987,1,2017-09-02,NA 7576,2,2017-09-02,NA 6683,3,2017-09-02,NA 3790,4,2017-09-02,NA 1878,5,2017-09-02,NA 1033,6,2017-09-02,NA 4989,1,2017-09-08,NA 7580,2,2017-09-08,NA 5451,3,2017-09-08,NA 7286,4,2017-09-08,NA 2137,5,2017-09-08,NA 1033,6,2017-09-08,NA 4989,1,2017-09-17,NA 7643,2,2017-09-17,NA 5448,3,2017-09-17,NA 7829,4,2017-09-17,NA 3566,5,2017-09-17,NA 1139,6,2017-09-17,NA 4989,1,2017-09-28,NA 7865,2,2017-09-28,NA 5382,3,2017-09-28,NA 7923,4,2017-09-28,NA 3781,5,2017-09-28,NA 1139,6,2017-09-28,NA 5034,1,2017-10-05,NA 7865,2,2017-10-05,NA 5410,3,2017-10-05,NA 8711,4,2017-10-05,NA 3781,5,2017-10-05,NA 1139,6,2017-10-05,NA 5034,1,2017-10-13,NA 8068,2,2017-10-13,NA 5437,3,2017-10-13,NA 8670,4,2017-10-13,NA 3967,5,2017-10-13,NA 1051,6,2017-10-13,NA 5101,1,2017-10-20,NA 8437,2,2017-10-20,NA 5431,3,2017-10-20,NA 8705,4,2017-10-20,NA 3972,5,2017-10-20,NA 1051,6,2017-10-20,NA 5181,1,2017-10-28,NA 9156,2,2017-10-28,NA 5528,3,2017-10-28,NA 9515,4,2017-10-28,NA 4056,5,2017-10-28,NA 1052,6,2017-10-28,NA 5181,1,2017-11-05,NA 9238,2,2017-11-05,NA 5534,3,2017-11-05,NA 9195,4,2017-11-05,NA 3991,5,2017-11-05,NA 1597,6,2017-11-05,NA 5190,1,2017-11-13,NA 9317,2,2017-11-13,NA 5534,3,2017-11-13,NA 9409,4,2017-11-13,NA 3999,5,2017-11-13,NA 2031,6,2017-11-13,NA 0,7,2017-11-13,NA 0,8,2017-11-13,NA 0,9,2017-11-13,NA 5190,1,2017-11-22,NA 9350,2,2017-11-22,NA 5534,3,2017-11-22,NA 8583,4,2017-11-22,NA 3999,5,2017-11-22,NA 3469,6,2017-11-22,NA 5192,1,2017-11-29,NA 9350,2,2017-11-29,NA 5534,3,2017-11-29,NA 7534,4,2017-11-29,NA 4560,5,2017-11-29,NA 4020,6,2017-11-29,NA 4360,7,2017-11-29,NA 1167,8,2017-11-29,NA 4903,9,2017-11-29,NA 994,10,2017-11-29,NA 1517,11,2017-11-29,NA 722,12,2017-11-29,NA 1,13,2017-11-29,NA 5152,1,2017-12-09,NA 9495,2,2017-12-09,NA 5534,3,2017-12-09,NA 7551,4,2017-12-09,NA 7314,5,2017-12-09,NA 4033,6,2017-12-09,NA 4360,7,2017-12-09,NA 1168,8,2017-12-09,NA 4903,9,2017-12-09,NA 994,10,2017-12-09,NA 1513,11,2017-12-09,NA 724,12,2017-12-09,NA 1,13,2017-12-09,NA 5152,1,2017-12-17,NA 9530,2,2017-12-17,NA 5534,3,2017-12-17,NA 7523,4,2017-12-17,NA 8352,5,2017-12-17,NA 4033,6,2017-12-17,NA 4305,7,2017-12-17,NA 2352,8,2017-12-17,NA 4903,9,2017-12-17,NA 994,10,2017-12-17,NA 1513,11,2017-12-17,NA 724,12,2017-12-17,NA 1,13,2017-12-17,NA 5152,1,2017-12-26,NA 9530,2,2017-12-26,NA 5736,3,2017-12-26,NA 7782,4,2017-12-26,NA 9155,5,2017-12-26,NA 4033,6,2017-12-26,NA 4412,7,2017-12-26,NA 3256,8,2017-12-26,NA 4903,9,2017-12-26,NA 994,10,2017-12-26,NA 1513,11,2017-12-26,NA 724,12,2017-12-26,NA 1,13,2017-12-26,NA 5152,1,2018-01-02,NA 9600,2,2018-01-02,NA 5736,3,2018-01-02,NA 7778,4,2018-01-02,NA 10168,5,2018-01-02,NA 4029,6,2018-01-02,NA 4379,7,2018-01-02,NA 4634,8,2018-01-02,NA 4903,9,2018-01-02,NA 994,10,2018-01-02,NA 1513,11,2018-01-02,NA 725,12,2018-01-02,NA 1,13,2018-01-02,NA 5152,1,2018-01-09,NA 9433,2,2018-01-09,NA 5760,3,2018-01-09,NA 7696,4,2018-01-09,NA 10216,5,2018-01-09,NA 4029,6,2018-01-09,NA 4481,7,2018-01-09,NA 6807,8,2018-01-09,NA 4903,9,2018-01-09,NA 994,10,2018-01-09,NA 1513,11,2018-01-09,NA 725,12,2018-01-09,NA 1,13,2018-01-09,NA 5152,1,2018-01-17,NA 9448,2,2018-01-17,NA 5763,3,2018-01-17,NA 7696,4,2018-01-17,NA 11200,5,2018-01-17,NA 4029,6,2018-01-17,NA 4481,7,2018-01-17,NA 7904,8,2018-01-17,NA 4903,9,2018-01-17,NA 994,10,2018-01-17,NA 1513,11,2018-01-17,NA 725,12,2018-01-17,NA 1,13,2018-01-17,NA 5152,1,2018-01-29,NA 9448,2,2018-01-29,NA 5807,3,2018-01-29,NA 7738,4,2018-01-29,NA 11604,5,2018-01-29,NA 4029,6,2018-01-29,NA 4481,7,2018-01-29,NA 7904,8,2018-01-29,NA 4903,9,2018-01-29,NA 994,10,2018-01-29,NA 1513,11,2018-01-29,NA 725,12,2018-01-29,NA 1,13,2018-01-29,NA 5151,1,2018-02-06,NA 9383,2,2018-02-06,NA 5971,3,2018-02-06,NA 7756,4,2018-02-06,NA 11874,5,2018-02-06,NA 4140,6,2018-02-06,NA 7821,7,2018-02-06,NA 4459,8,2018-02-06,NA 996,9,2018-02-06,NA 31,10,2018-02-06,NA 4923,11,2018-02-06,NA 5,12,2018-02-06,NA 1005,13,2018-02-06,NA 100,14,2018-02-06,NA 5,15,2018-02-06,NA 4,16,2018-02-06,NA 1519,17,2018-02-06,NA 711,18,2018-02-06,NA 1,19,2018-02-06,NA 5156,1,2018-02-13,NA 9383,2,2018-02-13,NA 5971,3,2018-02-13,NA 7756,4,2018-02-13,NA 11814,5,2018-02-13,NA 4140,6,2018-02-13,NA 7827,7,2018-02-13,NA 4461,8,2018-02-13,NA 1735,9,2018-02-13,NA 32,10,2018-02-13,NA 4923,11,2018-02-13,NA 13,12,2018-02-13,NA 1879,13,2018-02-13,NA 1007,14,2018-02-13,NA 101,15,2018-02-13,NA 6,16,2018-02-13,NA 4,17,2018-02-13,NA 1522,18,2018-02-13,NA 717,19,2018-02-13,NA 1,20,2018-02-13,NA 5156,1,2018-02-22,NA 9366,2,2018-02-22,NA 5971,3,2018-02-22,NA 7756,4,2018-02-22,NA 10372,5,2018-02-22,NA 4140,6,2018-02-22,NA 7827,7,2018-02-22,NA 4461,8,2018-02-22,NA 3333,9,2018-02-22,NA 4931,10,2018-02-22,NA 1718,11,2018-02-22,NA 13,12,2018-02-22,NA 2841,13,2018-02-22,NA 1007,14,2018-02-22,NA 101,15,2018-02-22,NA 6,16,2018-02-22,NA 4,17,2018-02-22,NA 1522,18,2018-02-22,NA 717,19,2018-02-22,NA 1,20,2018-02-22,NA 5160,1,2018-03-02,NA 9366,2,2018-03-02,NA 5967,3,2018-03-02,NA 7748,4,2018-03-02,NA 10361,5,2018-03-02,NA 4140,6,2018-03-02,NA 7828,7,2018-03-02,NA 4461,8,2018-03-02,NA 3734,9,2018-03-02,NA 4929,10,2018-03-02,NA 2577,11,2018-03-02,NA 13,12,2018-03-02,NA 3751,13,2018-03-02,NA 1007,14,2018-03-02,NA 101,15,2018-03-02,NA 6,16,2018-03-02,NA 4,17,2018-03-02,NA 1522,18,2018-03-02,NA 717,19,2018-03-02,NA 1,20,2018-03-02,NA 5160,1,2018-03-08,NA 9366,2,2018-03-08,NA 5967,3,2018-03-08,NA 7748,4,2018-03-08,NA 10361,5,2018-03-08,NA 4140,6,2018-03-08,NA 7816,7,2018-03-08,NA 4447,8,2018-03-08,NA 4756,9,2018-03-08,NA 4928,10,2018-03-08,NA 3024,11,2018-03-08,NA 13,12,2018-03-08,NA 3751,13,2018-03-08,NA 1007,14,2018-03-08,NA 101,15,2018-03-08,NA 6,16,2018-03-08,NA 4,17,2018-03-08,NA 1798,18,2018-03-08,NA 717,19,2018-03-08,NA 1,20,2018-03-08,NA 5157,1,2018-03-15,NA 9366,2,2018-03-15,NA 6056,3,2018-03-15,NA 7748,4,2018-03-15,NA 10362,5,2018-03-15,NA 4136,6,2018-03-15,NA 7816,7,2018-03-15,NA 4447,8,2018-03-15,NA 4786,9,2018-03-15,NA 4925,10,2018-03-15,NA 3349,11,2018-03-15,NA 13,12,2018-03-15,NA 4570,13,2018-03-15,NA 1007,14,2018-03-15,NA 101,15,2018-03-15,NA 6,16,2018-03-15,NA 4,17,2018-03-15,NA 1798,18,2018-03-15,NA 717,19,2018-03-15,NA 1,20,2018-03-15,NA 5157,1,2018-03-23,NA 9366,2,2018-03-23,NA 6056,3,2018-03-23,NA 7748,4,2018-03-23,NA 10348,5,2018-03-23,NA 4136,6,2018-03-23,NA 7816,7,2018-03-23,NA 4447,8,2018-03-23,NA 5297,9,2018-03-23,NA 4925,10,2018-03-23,NA 3514,11,2018-03-23,NA 13,12,2018-03-23,NA 5109,13,2018-03-23,NA 1007,14,2018-03-23,NA 101,15,2018-03-23,NA 6,16,2018-03-23,NA 4,17,2018-03-23,NA 1857,18,2018-03-23,NA 759,19,2018-03-23,NA 1,20,2018-03-23,NA 5143,1,2018-03-31,NA 9366,2,2018-03-31,NA 6056,3,2018-03-31,NA 7748,4,2018-03-31,NA 10348,5,2018-03-31,NA 4136,6,2018-03-31,NA 7816,7,2018-03-31,NA 4447,8,2018-03-31,NA 5386,9,2018-03-31,NA 4925,10,2018-03-31,NA 3523,11,2018-03-31,NA 13,12,2018-03-31,NA 6597,13,2018-03-31,NA 1007,14,2018-03-31,NA 101,15,2018-03-31,NA 6,16,2018-03-31,NA 4,17,2018-03-31,NA 206,18,2018-03-31,NA 1857,19,2018-03-31,NA 759,20,2018-03-31,NA 1,21,2018-03-31,NA 5258,1,2018-04-07,NA 9366,2,2018-04-07,NA 6056,3,2018-04-07,NA 7748,4,2018-04-07,NA 10353,5,2018-04-07,NA 4136,6,2018-04-07,NA 7816,7,2018-04-07,NA 4447,8,2018-04-07,NA 6646,9,2018-04-07,NA 4925,10,2018-04-07,NA 3523,11,2018-04-07,NA 13,12,2018-04-07,NA 6597,13,2018-04-07,NA 1007,14,2018-04-07,NA 101,15,2018-04-07,NA 6,16,2018-04-07,NA 4,17,2018-04-07,NA 1857,18,2018-04-07,NA 760,19,2018-04-07,NA 1,20,2018-04-07,NA 5295,1,2018-04-13,NA 9368,2,2018-04-13,NA 6056,3,2018-04-13,NA 7748,4,2018-04-13,NA 10353,5,2018-04-13,NA 4136,6,2018-04-13,NA 7770,7,2018-04-13,NA 4447,8,2018-04-13,NA 7089,9,2018-04-13,NA 4925,10,2018-04-13,NA 3523,11,2018-04-13,NA 13,12,2018-04-13,NA 6936,13,2018-04-13,NA 1007,14,2018-04-13,NA 101,15,2018-04-13,NA 6,16,2018-04-13,NA 4,17,2018-04-13,NA 1915,18,2018-04-13,NA 995,19,2018-04-13,NA 1,20,2018-04-13,NA 5295,1,2018-04-25,NA 9387,2,2018-04-25,NA 6056,3,2018-04-25,NA 7747,4,2018-04-25,NA 10353,5,2018-04-25,NA 4136,6,2018-04-25,NA 7771,7,2018-04-25,NA 4447,8,2018-04-25,NA 8505,9,2018-04-25,NA 4962,10,2018-04-25,NA 3855,11,2018-04-25,NA 13,12,2018-04-25,NA 6640,13,2018-04-25,NA 1016,14,2018-04-25,NA 101,15,2018-04-25,NA 6,16,2018-04-25,NA 4,17,2018-04-25,NA 1993,18,2018-04-25,NA 999,19,2018-04-25,NA 1,20,2018-04-25,NA 5578,1,2018-05-02,NA 9491,2,2018-05-02,NA 6056,3,2018-05-02,NA 7747,4,2018-05-02,NA 10353,5,2018-05-02,NA 4136,6,2018-05-02,NA 7771,7,2018-05-02,NA 4447,8,2018-05-02,NA 9209,9,2018-05-02,NA 4934,10,2018-05-02,NA 3855,11,2018-05-02,NA 13,12,2018-05-02,NA 6640,13,2018-05-02,NA 1016,14,2018-05-02,NA 101,15,2018-05-02,NA 6,16,2018-05-02,NA 4,17,2018-05-02,NA 1993,18,2018-05-02,NA 1271,19,2018-05-02,NA 1,20,2018-05-02,NA 5423,1,2018-05-10,NA 9491,2,2018-05-10,NA 6056,3,2018-05-10,NA 7747,4,2018-05-10,NA 10353,5,2018-05-10,NA 4136,6,2018-05-10,NA 7771,7,2018-05-10,NA 4447,8,2018-05-10,NA 9597,9,2018-05-10,NA 4934,10,2018-05-10,NA 3855,11,2018-05-10,NA 1928,12,2018-05-10,NA 6640,13,2018-05-10,NA 1016,14,2018-05-10,NA 101,15,2018-05-10,NA 6,16,2018-05-10,NA 4,17,2018-05-10,NA 1993,18,2018-05-10,NA 1281,19,2018-05-10,NA 1,20,2018-05-10,NA 5501,1,2018-05-19,NA 9542,2,2018-05-19,NA 6056,3,2018-05-19,NA 7916,4,2018-05-19,NA 10488,5,2018-05-19,NA 4136,6,2018-05-19,NA 7833,7,2018-05-19,NA 4562,8,2018-05-19,NA 9837,9,2018-05-19,NA 4934,10,2018-05-19,NA 3871,11,2018-05-19,NA 1928,12,2018-05-19,NA 6640,13,2018-05-19,NA 1016,14,2018-05-19,NA 101,15,2018-05-19,NA 6,16,2018-05-19,NA 4,17,2018-05-19,NA 2002,18,2018-05-19,NA 1326,19,2018-05-19,NA 1,20,2018-05-19,NA 5501,1,2018-05-25,NA 9604,2,2018-05-25,NA 6022,3,2018-05-25,NA 7539,4,2018-05-25,NA 9746,5,2018-05-25,NA 5416,6,2018-05-25,NA 4136,7,2018-05-25,NA 9858,8,2018-05-25,NA 4975,9,2018-05-25,NA 1927,10,2018-05-25,NA 6636,11,2018-05-25,NA 7408,12,2018-05-25,NA 4496,13,2018-05-25,NA 1012,14,2018-05-25,NA 4,15,2018-05-25,NA 2014,16,2018-05-25,NA 1280,17,2018-05-25,NA 1,18,2018-05-25,NA 5501,1,2018-06-08,NA 9545,2,2018-06-08,NA 6019,3,2018-06-08,NA 7716,4,2018-06-08,NA 9806,5,2018-06-08,NA 5416,6,2018-06-08,NA 4122,7,2018-06-08,NA 10002,8,2018-06-08,NA 5535,9,2018-06-08,NA 1949,10,2018-06-08,NA 6636,11,2018-06-08,NA 7408,12,2018-06-08,NA 4496,13,2018-06-08,NA 1012,14,2018-06-08,NA 4,15,2018-06-08,NA 2014,16,2018-06-08,NA 1280,17,2018-06-08,NA 1,18,2018-06-08,NA 5500,1,2018-06-15,NA 9592,2,2018-06-15,NA 6020,3,2018-06-15,NA 7705,4,2018-06-15,NA 9708,5,2018-06-15,NA 5409,6,2018-06-15,NA 4206,7,2018-06-15,NA 10020,8,2018-06-15,NA 5500,9,2018-06-15,NA 2080,10,2018-06-15,NA 6636,11,2018-06-15,NA 7429,12,2018-06-15,NA 4495,13,2018-06-15,NA 1012,14,2018-06-15,NA 4,15,2018-06-15,NA 2014,16,2018-06-15,NA 1285,17,2018-06-15,NA 1,18,2018-06-15,NA 5409,1,2018-06-27,NA 9631,2,2018-06-27,NA 6002,3,2018-06-27,NA 7708,4,2018-06-27,NA 9699,5,2018-06-27,NA 5411,6,2018-06-27,NA 4170,7,2018-06-27,NA 10140,8,2018-06-27,NA 7026,9,2018-06-27,NA 3511,10,2018-06-27,NA 6640,11,2018-06-27,NA 7429,12,2018-06-27,NA 4425,13,2018-06-27,NA 1012,14,2018-06-27,NA 4,15,2018-06-27,NA 2014,16,2018-06-27,NA 1285,17,2018-06-27,NA 1,18,2018-06-27,NA 5409,1,2018-07-08,NA 9633,2,2018-07-08,NA 6004,3,2018-07-08,NA 7688,4,2018-07-08,NA 9895,5,2018-07-08,NA 5452,6,2018-07-08,NA 4327,7,2018-07-08,NA 10131,8,2018-07-08,NA 7143,9,2018-07-08,NA 3552,10,2018-07-08,NA 6640,11,2018-07-08,NA 7429,12,2018-07-08,NA 4425,13,2018-07-08,NA 1012,14,2018-07-08,NA 4,15,2018-07-08,NA 2014,16,2018-07-08,NA 1285,17,2018-07-08,NA 1,18,2018-07-08,NA 5409,1,2018-07-16,NA 9627,2,2018-07-16,NA 6004,3,2018-07-16,NA 7692,4,2018-07-16,NA 9819,5,2018-07-16,NA 5471,6,2018-07-16,NA 4312,7,2018-07-16,NA 10127,8,2018-07-16,NA 7143,9,2018-07-16,NA 4355,10,2018-07-16,NA 6640,11,2018-07-16,NA 7429,12,2018-07-16,NA 4425,13,2018-07-16,NA 1012,14,2018-07-16,NA 4,15,2018-07-16,NA 2014,16,2018-07-16,NA 1285,17,2018-07-16,NA 1,18,2018-07-16,NA 5409,1,2018-07-23,NA 9634,2,2018-07-23,NA 6004,3,2018-07-23,NA 7692,4,2018-07-23,NA 10059,5,2018-07-23,NA 5471,6,2018-07-23,NA 4312,7,2018-07-23,NA 10124,8,2018-07-23,NA 7161,9,2018-07-23,NA 4544,10,2018-07-23,NA 6956,11,2018-07-23,NA 7429,12,2018-07-23,NA 4421,13,2018-07-23,NA 1107,14,2018-07-23,NA 4,15,2018-07-23,NA 2014,16,2018-07-23,NA 1285,17,2018-07-23,NA 1,18,2018-07-23,NA 5409,1,2018-08-02,NA 9954,2,2018-08-02,NA 6004,3,2018-08-02,NA 7719,4,2018-08-02,NA 10059,5,2018-08-02,NA 5442,6,2018-08-02,NA 4312,7,2018-08-02,NA 10128,8,2018-08-02,NA 7272,9,2018-08-02,NA 4575,10,2018-08-02,NA 6878,11,2018-08-02,NA 7429,12,2018-08-02,NA 4543,13,2018-08-02,NA 4774,14,2018-08-02,NA 4,15,2018-08-02,NA 2062,16,2018-08-02,NA 1276,17,2018-08-02,NA 1,18,2018-08-02,NA 5398,1,2018-08-15,NA 10014,2,2018-08-15,NA 5948,3,2018-08-15,NA 7714,4,2018-08-15,NA 10059,5,2018-08-15,NA 5442,6,2018-08-15,NA 4312,7,2018-08-15,NA 10220,8,2018-08-15,NA 7275,9,2018-08-15,NA 4575,10,2018-08-15,NA 6968,11,2018-08-15,NA 7426,12,2018-08-15,NA 4543,13,2018-08-15,NA 4834,14,2018-08-15,NA 91,15,2018-08-15,NA 2121,16,2018-08-15,NA 1278,17,2018-08-15,NA 1,18,2018-08-15,NA 5411,1,2018-08-23,NA 10014,2,2018-08-23,NA 5968,3,2018-08-23,NA 7714,4,2018-08-23,NA 10059,5,2018-08-23,NA 5442,6,2018-08-23,NA 4312,7,2018-08-23,NA 10349,8,2018-08-23,NA 7275,9,2018-08-23,NA 4578,10,2018-08-23,NA 4576,11,2018-08-23,NA 6965,12,2018-08-23,NA 7425,13,2018-08-23,NA 4543,14,2018-08-23,NA 4807,15,2018-08-23,NA 4835,16,2018-08-23,NA 91,17,2018-08-23,NA 2157,18,2018-08-23,NA 1294,19,2018-08-23,NA 1,20,2018-08-23,NA 5480,1,2018-08-31,NA 10038,2,2018-08-31,NA 6014,3,2018-08-31,NA 7948,4,2018-08-31,NA 10077,5,2018-08-31,NA 5442,6,2018-08-31,NA 4317,7,2018-08-31,NA 10381,8,2018-08-31,NA 7304,9,2018-08-31,NA 4587,10,2018-08-31,NA 6957,11,2018-08-31,NA 7431,12,2018-08-31,NA 4543,13,2018-08-31,NA 4832,14,2018-08-31,NA 1782,15,2018-08-31,NA 2162,16,2018-08-31,NA 1294,17,2018-08-31,NA 1,18,2018-08-31,NA 5480,1,2018-09-08,NA 10068,2,2018-09-08,NA 6044,3,2018-09-08,NA 7987,4,2018-09-08,NA 10066,5,2018-09-08,NA 5442,6,2018-09-08,NA 4321,7,2018-09-08,NA 10419,8,2018-09-08,NA 7305,9,2018-09-08,NA 4586,10,2018-09-08,NA 6957,11,2018-09-08,NA 7450,12,2018-09-08,NA 4543,13,2018-09-08,NA 5223,14,2018-09-08,NA 2533,15,2018-09-08,NA 2200,16,2018-09-08,NA 1374,17,2018-09-08,NA 1,18,2018-09-08,NA 5479,1,2018-09-17,NA 10076,2,2018-09-17,NA 6044,3,2018-09-17,NA 7983,4,2018-09-17,NA 10066,5,2018-09-17,NA 5554,6,2018-09-17,NA 4290,7,2018-09-17,NA 10871,8,2018-09-17,NA 7354,9,2018-09-17,NA 4586,10,2018-09-17,NA 6957,11,2018-09-17,NA 7450,12,2018-09-17,NA 4543,13,2018-09-17,NA 5223,14,2018-09-17,NA 2965,15,2018-09-17,NA 2200,16,2018-09-17,NA 1374,17,2018-09-17,NA 1,18,2018-09-17,NA 5465,1,2018-09-24,NA 10031,2,2018-09-24,NA 6035,3,2018-09-24,NA 8050,4,2018-09-24,NA 10068,5,2018-09-24,NA 5550,6,2018-09-24,NA 4192,7,2018-09-24,NA 10868,8,2018-09-24,NA 7368,9,2018-09-24,NA 4604,10,2018-09-24,NA 6953,11,2018-09-24,NA 7452,12,2018-09-24,NA 4330,13,2018-09-24,NA 5225,14,2018-09-24,NA 2958,15,2018-09-24,NA 2231,16,2018-09-24,NA 1374,17,2018-09-24,NA 1,18,2018-09-24,NA 5536,1,2018-09-30,NA 10174,2,2018-09-30,NA 6021,3,2018-09-30,NA 8054,4,2018-09-30,NA 10047,5,2018-09-30,NA 5551,6,2018-09-30,NA 4200,7,2018-09-30,NA 10914,8,2018-09-30,NA 7367,9,2018-09-30,NA 4618,10,2018-09-30,NA 6953,11,2018-09-30,NA 7452,12,2018-09-30,NA 4326,13,2018-09-30,NA 5574,14,2018-09-30,NA 3716,15,2018-09-30,NA 2695,16,2018-09-30,NA 1374,17,2018-09-30,NA 1,18,2018-09-30,NA 5593,1,2018-10-22,NA 10134,2,2018-10-22,NA 6050,3,2018-10-22,NA 8105,4,2018-10-22,NA 10137,5,2018-10-22,NA 5607,6,2018-10-22,NA 5422,7,2018-10-22,NA 11054,8,2018-10-22,NA 7401,9,2018-10-22,NA 5246,10,2018-10-22,NA 7033,11,2018-10-22,NA 7466,12,2018-10-22,NA 4358,13,2018-10-22,NA 5697,14,2018-10-22,NA 3851,15,2018-10-22,NA 3721,16,2018-10-22,NA 2599,17,2018-10-22,NA 1374,18,2018-10-22,NA 1,19,2018-10-22,NA 5568,1,2018-12-13,NA 10140,2,2018-12-13,NA 6059,3,2018-12-13,NA 8105,4,2018-12-13,NA 10144,5,2018-12-13,NA 5607,6,2018-12-13,NA 5422,7,2018-12-13,NA 11070,8,2018-12-13,NA 7401,9,2018-12-13,NA 5246,10,2018-12-13,NA 7034,11,2018-12-13,NA 7395,12,2018-12-13,NA 4360,13,2018-12-13,NA 5697,14,2018-12-13,NA 3853,15,2018-12-13,NA 3721,16,2018-12-13,NA 2838,17,2018-12-13,NA 1386,18,2018-12-13,NA 1,19,2018-12-13,NA 5471,1,2019-01-08,NA 10135,2,2019-01-08,NA 5779,3,2019-01-08,NA 8020,4,2019-01-08,NA 10178,5,2019-01-08,NA 5190,6,2019-01-08,NA 5173,7,2019-01-08,NA 10224,8,2019-01-08,NA 7441,9,2019-01-08,NA 5159,10,2019-01-08,NA 7028,11,2019-01-08,NA 7387,12,2019-01-08,NA 4377,13,2019-01-08,NA 5703,14,2019-01-08,NA 3865,15,2019-01-08,NA 2952,16,2019-01-08,NA 1370,17,2019-01-08,NA 1,18,2019-01-08,NA 5471,1,2019-01-16,NA 10135,2,2019-01-16,NA 5779,3,2019-01-16,NA 8020,4,2019-01-16,NA 10178,5,2019-01-16,NA 5190,6,2019-01-16,NA 5173,7,2019-01-16,NA 10224,8,2019-01-16,NA 7441,9,2019-01-16,NA 5159,10,2019-01-16,NA 7030,11,2019-01-16,NA 7411,12,2019-01-16,NA 4377,13,2019-01-16,NA 5703,14,2019-01-16,NA 3865,15,2019-01-16,NA 2992,16,2019-01-16,NA 1373,17,2019-01-16,NA 1,18,2019-01-16,NA 5477,1,2019-01-30,NA 10369,2,2019-01-30,NA 6049,3,2019-01-30,NA 8216,4,2019-01-30,NA 10498,5,2019-01-30,NA 5310,6,2019-01-30,NA 5363,7,2019-01-30,NA 10410,8,2019-01-30,NA 7573,9,2019-01-30,NA 5222,10,2019-01-30,NA 7170,11,2019-01-30,NA 7501,12,2019-01-30,NA 4459,13,2019-01-30,NA 5785,14,2019-01-30,NA 3877,15,2019-01-30,NA 3721,16,2019-01-30,NA 3000,17,2019-01-30,NA 1373,18,2019-01-30,NA 1,19,2019-01-30,NA 5478,1,2019-03-10,NA 10377,2,2019-03-10,NA 6059,3,2019-03-10,NA 8218,4,2019-03-10,NA 10503,5,2019-03-10,NA 5310,6,2019-03-10,NA 5364,7,2019-03-10,NA 10423,8,2019-03-10,NA 7576,9,2019-03-10,NA 5222,10,2019-03-10,NA 7170,11,2019-03-10,NA 7503,12,2019-03-10,NA 4467,13,2019-03-10,NA 5787,14,2019-03-10,NA 3877,15,2019-03-10,NA 2999,16,2019-03-10,NA 1374,17,2019-03-10,NA 1,18,2019-03-10,NA 5478,1,2019-05-27,NA 10377,2,2019-05-27,NA 6074,3,2019-05-27,NA 8219,4,2019-05-27,NA 10522,5,2019-05-27,NA 5312,6,2019-05-27,NA 5365,7,2019-05-27,NA 10361,8,2019-05-27,NA 7576,9,2019-05-27,NA 5222,10,2019-05-27,NA 7170,11,2019-05-27,NA 7503,12,2019-05-27,NA 4467,13,2019-05-27,NA 5787,14,2019-05-27,NA 3877,15,2019-05-27,NA 3045,16,2019-05-27,NA 1375,17,2019-05-27,NA 1,18,2019-05-27,NA 5478,1,2019-08-22,NA 10333,2,2019-08-22,NA 6086,3,2019-08-22,NA 8304,4,2019-08-22,NA 10519,5,2019-08-22,NA 5282,6,2019-08-22,NA 5411,7,2019-08-22,NA 10293,8,2019-08-22,NA 7552,9,2019-08-22,NA 5222,10,2019-08-22,NA 7170,11,2019-08-22,NA 7503,12,2019-08-22,NA 4435,13,2019-08-22,NA 5787,14,2019-08-22,NA 3877,15,2019-08-22,NA 3051,16,2019-08-22,NA 1390,17,2019-08-22,NA 1,18,2019-08-22,NA 5812,1,2019-08-28,NA 10522,2,2019-08-28,NA 6145,3,2019-08-28,NA 8398,4,2019-08-28,NA 10632,5,2019-08-28,NA 5340,6,2019-08-28,NA 5531,7,2019-08-28,NA 10591,8,2019-08-28,NA 7997,9,2019-08-28,NA 5356,10,2019-08-28,NA 7507,11,2019-08-28,NA 7799,12,2019-08-28,NA 4601,13,2019-08-28,NA 6102,14,2019-08-28,NA 3977,15,2019-08-28,NA 3120,16,2019-08-28,NA 1440,17,2019-08-28,NA 5,18,2019-08-28,NA 110318,1,2020-04-10,NA 5832,2,2020-04-10,NA 10515,3,2020-04-10,NA 6145,4,2020-04-10,NA 8398,5,2020-04-10,NA 10632,6,2020-04-10,NA 5389,7,2020-04-10,NA 5438,8,2020-04-10,NA 10643,9,2020-04-10,NA 7988,10,2020-04-10,NA 5356,11,2020-04-10,NA 7561,12,2020-04-10,NA 7811,13,2020-04-10,NA 4553,14,2020-04-10,NA 6102,15,2020-04-10,NA 3960,16,2020-04-10,NA 3122,17,2020-04-10,NA 1585,18,2020-04-10,NA 5,19,2020-04-10,NA 5832,1,2020-08-04,NA 10682,2,2020-08-04,NA 6149,3,2020-08-04,NA 8398,4,2020-08-04,NA 10650,5,2020-08-04,NA 5391,6,2020-08-04,NA 5440,7,2020-08-04,NA 10643,8,2020-08-04,NA 8008,9,2020-08-04,NA 5356,10,2020-08-04,NA 7603,11,2020-08-04,NA 7886,12,2020-08-04,NA 4553,13,2020-08-04,NA 6215,14,2020-08-04,NA 3960,15,2020-08-04,NA 3168,16,2020-08-04,NA 1400,17,2020-08-04,NA 5,18,2020-08-04,NA 5832,1,2020-09-18,NA 10682,2,2020-09-18,NA 6149,3,2020-09-18,NA 8398,4,2020-09-18,NA 10650,5,2020-09-18,NA 5391,6,2020-09-18,NA 5440,7,2020-09-18,NA 10643,8,2020-09-18,NA 8008,9,2020-09-18,NA 5356,10,2020-09-18,NA 7603,11,2020-09-18,NA 7886,12,2020-09-18,NA 4553,13,2020-09-18,NA 6215,14,2020-09-18,NA 3960,15,2020-09-18,NA 3168,16,2020-09-18,NA 1399,17,2020-09-18,NA 5,18,2020-09-18,NA ================================================ FILE: geocompr.Rproj ================================================ Version: 1.0 RestoreWorkspace: No SaveWorkspace: No AlwaysSaveHistory: Yes EnableCodeIndexing: Yes UseSpacesForTab: Yes NumSpacesForTab: 2 Encoding: UTF-8 RnwWeave: knitr LaTeX: pdfLaTeX BuildType: Website ================================================ FILE: geocompr.bib ================================================ @misc{_map_1993, title = {Map Projections}, year = {1993}, publisher = {US Geological Survey}, doi = {10.3133/70047422} } @book{abelson_structure_1996, title = {Structure and Interpretation of Computer Programs}, author = {Abelson, Harold and Sussman, Gerald Jay and Sussman, Julie}, year = {1996}, series = {The {{MIT}} Electrical Engineering and Computer Science Series}, edition = {Second}, publisher = {MIT Press}, address = {Cambridge, Massachusetts}, isbn = {0-262-01153-0}, lccn = {QA76.6 .A255 1985}, keywords = {Computer programming,LISP (Computer program language),nosource} } @article{adams_seeded_1994, title = {Seeded Region Growing}, author = {Adams, R. and Bischof, L.}, year = {1994}, month = jun, journal = {IEEE Transactions on Pattern Analysis and Machine Intelligence}, volume = {16}, number = {6}, pages = {641--647}, issn = {01628828}, doi = {10.1109/34.295913}, urldate = {2022-09-23}, abstract = {We present here a new algorithm for segmentation of intensity images which is robust, rapid, and free of tuning parameters. The method, however, requires the input of a number of seeds, either individual pixels or regions, which will control the formation of regions into which the image will be segmented. In this correspondence, we present the algorithm, discuss briefly its properties, and suggest two ways in which it can be employed, namely, by using manual seed selection or by automated procedures.}, langid = {english} } @book{akima_akima_2016, title = {Akima: {{Interpolation}} of {{Irregularly}} and {{Regularly Spaced Data}}}, author = {Akima, Hiroshi and Gebhardt, Albrecht}, year = {2016}, publisher = {R package}, keywords = {nosource} } @article{alessandretti_multimodal_2022, title = {Multimodal Urban Mobility and Multilayer Transport Networks}, author = {Alessandretti, Laura and Natera Orozco, Luis Guillermo and Battiston, Federico and Saberi, Meead and Szell, Michael}, year = {2022}, month = jul, journal = {Environment and Planning B: Urban Analytics and City Science}, pages = {23998083221108190}, publisher = {SAGE Publications Ltd STM}, issn = {2399-8083}, doi = {10.1177/23998083221108190}, urldate = {2022-07-20}, abstract = {Transportation networks, from bicycle paths to buses and railways, are the backbone of urban mobility. In large metropolitan areas, the integration of different transport modes has become crucial to guarantee the fast and sustainable flow of people. Using a network science approach, multimodal transport systems can be described as multilayer networks, where the networks associated to different transport modes are not considered in isolation, but as a set of interconnected layers. Despite the importance of multimodality in modern cities, a unified view of the topic is currently missing. Here, we provide a comprehensive overview of the emerging research areas of multilayer transport networks and multimodal urban mobility, focusing on contributions from the interdisciplinary fields of complex systems, urban data science, and science of cities. First, we present an introduction to the mathematical framework of multilayer networks. We apply it to survey models of multimodal infrastructures, as well as measures used for quantifying multimodality, and related empirical findings. We review modeling approaches and observational evidence in multimodal mobility and public transport system dynamics, focusing on integrated real-world mobility patterns, where individuals navigate urban systems using different transport modes. We then provide a survey of freely available datasets on multimodal infrastructure and mobility, and a list of open-source tools for their analyses. Finally, we conclude with an outlook on open research questions and promising directions for future research.}, langid = {english}, keywords = {complex systems,human mobility,multilayer networks,science of cities,transport networks,urban data science} } @article{appel_gdalcubes_2019, title = {On-Demand Processing of Data Cubes from Satellite Image Collections with the Gdalcubes Library}, author = {Appel, Marius and Pebesma, Edzer}, year = {2019}, journal = {Data}, volume = {4}, number = {3}, doi = {10.3390/data4030092}, article-number = {92} } @book{baddeley_spatial_2015, ids = {baddeley_spatial_2015-1}, title = {Spatial Point Patterns: Methodology and Applications with {{R}}}, author = {Baddeley, Adrian and Rubak, Ege and Turner, Rolf}, year = {2015}, publisher = {CRC Press}, keywords = {nosource} } @article{baddeley_spatstat_2005, title = {Spatstat: An {{R}} Package for Analyzing Spatial Point Patterns}, author = {Baddeley, Adrian and Turner, Rolf}, year = {2005}, journal = {Journal of statistical software}, volume = {12}, number = {6}, pages = {1--42}, doi = {10/gf29tr}, keywords = {conditional intensity,edge corrections,exploratory data analysis,generalised,hood,inhomogeneous point patterns,Linear Models,marked point patterns,maximum pseudolikeli-,nosource,spatial clustering} } @book{becker_mlr3_2022, title = {Applied {{Machine Learning Using}} Mlr3 in \{\vphantom\}{{R}}\vphantom\{\}}, editor = {Bischl, Bernd and Sonabend, R. and Kotthoff, Lars and Lang, Michel}, year = {2024}, publisher = {CRC Press} } @book{bellos_alex_2011, title = {Alex's {{Adventures}} in {{Numberland}}}, author = {Bellos, Alex}, year = {2011}, month = apr, publisher = {Bloomsbury Paperbacks}, address = {London}, abstract = {The world of maths can seem mind-boggling, irrelevant and, let's face it, boring. This groundbreaking book reclaims maths from the geeks. Mathematical ideas underpin just about everything in our lives: from the surprising geometry of the 50p piece to how probability can help you win in any casino. In search of weird and wonderful mathematical phenomena, Alex Bellos travels across the globe and meets the world's fastest mental calculators in Germany and a startlingly numerate chimpanzee in Japan. Packed with fascinating, eye-opening anecdotes, Alex's Adventures in Numberland is an exhilarating cocktail of history, reportage and mathematical proofs that will leave you awestruck.}, isbn = {978-1-4088-0959-4}, langid = {english} } @book{berg_computational_2008, title = {Computational {{Geometry}}: {{Algorithms}} and {{Applications}}}, shorttitle = {Computational {{Geometry}}}, author = {de Berg, Mark and Cheong, Otfried and van Kreveld, Marc and Overmars, Mark}, year = {2008}, month = mar, publisher = {Springer Science \& Business Media}, abstract = {Computational geometry emerged from the field of algorithms design and analysis in the late 1970s. It has grown into a recognized discipline with its own journals, conferences, and a large community of active researchers. The success of the ?eld as a research discipline can on the one hand be explained from the beauty of the problems studied and the solutions obtained, and, on the other hand, by the many application domains---computer graphics, geographic information systems (GIS), robotics, and others---in which geometric algorithms play a fundamental role. For many geometric problems the early algorithmic solutions were either slow or dif?cult to understand and implement. In recent years a number of new algorithmic techniques have been developed that improved and simpli?ed many of the previous approaches. In this textbook we have tried to make these modern algorithmic solutions accessible to a large audience. The book has been written as a textbook for a course in computational geometry, but it can also be used for self-study.}, googlebooks = {tkyG8W2163YC}, isbn = {978-3-540-77973-5}, langid = {english}, keywords = {Computers / Computer Graphics,Computers / Computer Science,Computers / Data Processing,Computers / Databases / General,Computers / Information Technology,Computers / Programming / Algorithms,Mathematics / Discrete Mathematics,Mathematics / Geometry / General,Science / Earth Sciences / General,Technology & Engineering / General} } @book{bischl_applied_2024, title = {Applied {{Machine Learning Using}} Mlr3 in {{R}}}, author = {Bischl, Bernd and Sonabend, Raphael and Kotthoff, Lars and Lang, Michel}, year = {2024}, month = jan, publisher = {CRC Press}, abstract = {mlr3 is an award-winning ecosystem of R packages that have been developed to enable state-of-the-art machine learning capabilities in R. Applied Machine Learning Using mlr3 in R gives an overview of flexible and robust machine learning methods, with an emphasis on how to implement them using mlr3 in R. It covers various key topics, including basic machine learning tasks, such as building and evaluating a predictive model; hyperparameter tuning of machine learning approaches to obtain peak performance; building machine learning pipelines that perform complex operations such as pre-processing followed by modelling followed by aggregation of predictions; and extending the mlr3 ecosystem with custom learners, measures, or pipeline components.Features: In-depth coverage of the mlr3 ecosystem for users and developers Explanation and illustration of basic and advanced machine learning concepts Ready to use code samples that can be adapted by the user for their application Convenient and expressive machine learning pipelining enabling advanced modelling Coverage of topics that are often ignored in other machine learning books The book is primarily aimed at researchers, practitioners, and graduate students who use machine learning or who are interested in using it. It can be used as a textbook for an introductory or advanced machine learning class that uses R, as a reference for people who work with machine learning methods, and in industry for exploratory experiments in machine learning.}, googlebooks = {5wrsEAAAQBAJ}, isbn = {978-1-00-383057-3}, langid = {english}, keywords = {Computers / Artificial Intelligence / General,Computers / Data Science / Machine Learning,Computers / Mathematical & Statistical Software,Mathematics / Probability & Statistics / General,Technology & Engineering / Automation,Technology & Engineering / Environmental / General} } @article{bischl_mlr:_2016, title = {Mlr: {{Machine Learning}} in {{R}}}, author = {Bischl, Bernd and Lang, Michel and Kotthoff, Lars and Schiffner, Julia and Richter, Jakob and Studerus, Erich and Casalicchio, Giuseppe and Jones, Zachary M.}, year = {2016}, journal = {Journal of Machine Learning Research}, volume = {17}, number = {170}, pages = {1--5}, keywords = {No DOI found,nosource} } @book{bivand_applied_2013, ids = {bivand_applied_2013a}, title = {Applied Spatial Data Analysis with {{R}}}, author = {Bivand, Roger and Pebesma, Edzer and {G{\'o}mez-Rubio}, Virgilio}, year = {2013}, publisher = {Springer}, googlebooks = {v0eIU9ObJXgC}, keywords = {Mathematics / Probability & Statistics / General,Medical / Biostatistics,Medical / General,Science / Earth Sciences / Geography,Science / Environmental Science,Technology & Engineering / Environmental / General} } @article{bivand_comparing_2015, title = {Comparing {{Implementations}} of {{Estimation Methods}} for {{Spatial Econometrics}}}, author = {Bivand, Roger and Piras, Gianfranco}, year = {2015}, journal = {Journal of Statistical Software}, volume = {63}, number = {18}, pages = {1--36}, doi = {10/cqxj}, keywords = {nosource} } @article{bivand_implementing_2000, title = {Implementing Functions for Spatial Statistical Analysis Using the Language}, author = {Bivand, Roger and Gebhardt, Albrecht}, year = {2000}, journal = {Journal of Geographical Systems}, volume = {2}, number = {3}, pages = {307--317}, doi = {10.1007/PL00011460}, urldate = {2017-07-12}, keywords = {nosource} } @book{bivand_maptools_2017, title = {Maptools: {{Tools}} for {{Reading}} and {{Handling Spatial Objects}}}, author = {Bivand, Roger and {Lewin-Koh}, Nicholas}, year = {2017}, publisher = {R package}, keywords = {nosource} } @article{bivand_more_2001, title = {More on {{Spatial Data Analysis}}}, author = {Bivand, Roger}, year = {2001}, journal = {R News}, volume = {1}, number = {3}, pages = {13--17}, keywords = {No DOI found,nosource} } @inproceedings{bivand_open_2000, title = {Open Source Geocomputation: Using the {{R}} Data Analysis Language Integrated with {{GRASS GIS}} and {{PostgreSQL}} Data Base Systems}, booktitle = {Proceedings of the 5th {{International Conference}} on {{GeoComputation}}}, author = {Bivand, Roger and Neteler, Markus}, editor = {Neteler, Markus and Bivand, Roger S.}, year = {2000}, keywords = {No DOI found,nosource} } @article{bivand_progress_2021, title = {Progress in the {{R}} Ecosystem for Representing and Handling Spatial Data}, author = {Bivand, Roger}, year = {2021}, month = oct, journal = {Journal of Geographical Systems}, volume = {23}, number = {4}, pages = {515--546}, issn = {1435-5949}, doi = {10/ghnwg3}, urldate = {2021-12-17}, abstract = {Twenty years have passed since Bivand and Gebhardt (J Geogr Syst 2(3):307--317, 2000. https://doi.org/10.1007/PL00011460) indicated that there was a good match between the then nascent open-source R programming language and environment and the needs of researchers analysing spatial data. Recalling the development of classes for spatial data presented in book form in Bivand et al. (Applied spatial data analysis with R. Springer, New York, 2008, Applied spatial data analysis with R, 2nd edn. Springer, New York, 2013), it is important to present the progress now occurring in representation of spatial data, and possible consequences for spatial data handling and the statistical analysis of spatial data. Beyond this, it is imperative to discuss the relationships between R-spatial software and the larger open-source geospatial software community on whose work R packages crucially depend.}, langid = {english} } @book{bivand_rgrass7_2016, title = {Rgrass7: {{Interface Between GRASS}} 7 {{Geographical Information System}} and {{R}}}, author = {Bivand, Roger}, year = {2016}, publisher = {R package}, keywords = {nosource} } @book{bivand_spdep_2017, title = {Spdep: {{Spatial Dependence}}: {{Weighting Schemes}}, {{Statistics}} and {{Models}}}, author = {Bivand, Roger}, year = {2017}, publisher = {R package}, keywords = {nosource} } @book{bivand_spgrass6_2016, title = {Spgrass6: {{Interface}} between {{GRASS}} 6 and {{R}}}, author = {Bivand, Roger}, year = {2016}, publisher = {R package}, keywords = {nosource} } @article{bivand_using_2000, title = {Using the {{R}} Statistical Data Analysis Language on {{GRASS}} 5.0 {{GIS}} Database Files}, author = {Bivand, Roger}, year = {2000}, journal = {Computers \& Geosciences}, volume = {26}, number = {9}, pages = {1043--1052}, doi = {10.1016/S0098-3004(00)00057-1}, urldate = {2017-07-11}, keywords = {nosource} } @book{blangiardo_spatial_2015, title = {Spatial and {{Spatio-temporal Bayesian Models}} with {{R-INLA}}}, shorttitle = {Spatial and {{Spatio-temporal Bayesian Models}} with {{R-INLA}}}, author = {Blangiardo, Marta and Cameletti, Michela}, year = {2015}, month = apr, publisher = {John Wiley \& Sons, Ltd}, address = {Chichester, UK}, doi = {10.1002/9781118950203}, urldate = {2018-02-07}, isbn = {978-1-118-95020-3 978-1-118-32655-8}, langid = {english}, keywords = {nosource} } @incollection{bohner_image_2006, title = {Image Segmentation Using Representativeness Analysis and Region Growing}, booktitle = {{{SAGA}} - {{Analysis}} and {{Modelling Applications}}}, author = {B{\"o}hner, J{\"u}rgen and Selige, Thomas and Ringeler, Andre}, editor = {{B{\"o}hner, J{\"u}rgen} and {McCloy, K.R.} and {Strobl, J.}}, year = {2006}, pages = {10}, publisher = {Goettinger Geographische Abhandlungen}, address = {Goettingen}, abstract = {Image segmentation is a crucial task in the emerging field of object oriented image analysis. This paper contributes to the ongoing debate by presenting a segmentation procedure currently implemented in SAGA. Key feature at the core of the segmentation procedure is the representativeness analysis, performed for each pixel using geostatistical (semi-variogram) analysis measures. The representativeness layer supports conventional region growing algorithm with necessary start seeds, brake of criterions, and additional opportunities for fast performing initial image segmentation. The segmentation procedure aims to create spatially discrete object primitives and homogenous regions from remotely sensed images as the basic entities for further image classification procedures and thematic mapping applications. In a comprehensive evaluation study comparing eCognition, RHSEG and SAGA segmentation procedures, the SAGA approach was tested as robust and fast. SAGA performed at high quality a detailed segmentation of the actual landscape pattern represented by the remotely sensed imagery.}, langid = {english} } @incollection{bohner_spatial_2006, title = {Spatial Prediction of Soil Attributes Using Terrain Analysis and Climate Regionalisation}, booktitle = {{{SAGA}} - {{Analysis}} and {{Modelling Applications}}}, author = {B{\"o}hner, J{\"u}rgen and Selige, Thomas}, editor = {B{\"o}hner, J and {McCloy, K.R.} and {Strobl, J.}}, year = {2006}, pages = {19}, publisher = {Goettinger Geographische Abhandlungen}, address = {Goettingen}, abstract = {A method of predicting spatial soil parameters is proposed and tested. The method uses a digital terrain model (DTM) of the area and regionalised climate data to derive the soil regionalised variables that form the basis of the prediction. The method was tested using 94 soil profile samples in the Quaternary stratum of the Schatterbach test site, a 2387 ha investigation area in the Bavarian Tertiary Hills (Germany). The approach is based on the assumption that the shape of the landscape and the late Quaternary climate history determines slope development and soil forming processes. To develop the method, a suite of terrain- indices and complex process parameters was derived from DTM and climate data. Step-wise linear regression was then used to identify which of these terrain indices and process parameters were most useful in predicting the required soil attributes. Testing of the approach showed that 88.1\% of the variance was explained by a combination of the sediment transport, mass balance and solifluction parameters, providing a sound basis for the prediction of soil parameters in hilly terrain.}, langid = {english} } @article{bondaruk_assessing_2020, title = {Assessing the State of the Art in {{Discrete Global Grid Systems}}: {{OGC}} Criteria and Present Functionality}, shorttitle = {Assessing the State of the Art in {{Discrete Global Grid Systems}}}, author = {Bondaruk, Ben and Roberts, Steven A. and Robertson, Colin}, year = {2020}, month = mar, journal = {Geomatica}, volume = {74}, number = {1}, pages = {9--30}, publisher = {NRC Research Press}, issn = {1195-1036}, doi = {10.1139/geomat-2019-0015}, urldate = {2021-08-12} } @book{borcard_numerical_2011, title = {Numerical Ecology with {{R}}}, author = {Borcard, Daniel and Gillet, Fran{\c c}ois and Legendre, Pierre}, year = {2011}, series = {Use {{R}}!}, publisher = {Springer}, address = {New York}, isbn = {978-1-4419-7975-9}, lccn = {QH541.15.S72 B67 2011}, keywords = {Data processing,Ecology,nosource,R (Computer program language),Statistical methods}, annotation = {OCLC: ocn690089213} } @article{borland_rainbow_2007, title = {Rainbow Color Map (Still) Considered Harmful}, author = {Borland, David and Taylor II, Russell M}, year = {2007}, journal = {IEEE computer graphics and applications}, volume = {27}, number = {2}, publisher = {IEEE}, doi = {10.1109/MCG.2007.323435}, keywords = {nosource} } @article{breiman_random_2001, title = {Random {{Forests}}}, author = {Breiman, Leo}, year = {2001}, month = oct, journal = {Machine Learning}, volume = {45}, number = {1}, pages = {5--32}, issn = {1573-0565}, doi = {10/d8zjwq}, keywords = {nosource} } @book{brenning_arcgis_2012, title = {{{RPyGeo}}: {{ArcGIS Geoprocessing}} in {{R}} via {{Python}}}, author = {Brenning, Alexander}, year = {2012}, publisher = {R package}, keywords = {nosource} } @inproceedings{brenning_spatial_2012, title = {Spatial Cross-Validation and Bootstrap for the Assessment of Prediction Rules in Remote Sensing: {{The R}} Package Sperrorest}, shorttitle = {Spatial Cross-Validation and Bootstrap for the Assessment of Prediction Rules in Remote Sensing}, author = {Brenning, Alexander}, year = {2012}, month = jul, pages = {5372--5375}, publisher = {IEEE}, doi = {10/gf238w}, urldate = {2017-11-24}, isbn = {978-1-4673-1159-5 978-1-4673-1160-1 978-1-4673-1158-8}, keywords = {nosource} } @book{brewer_designing_2015, title = {Designing {{Better Maps}}: {{A Guide}} for {{GIS Users}}}, shorttitle = {Designing {{Better Maps}}}, author = {Brewer, Cynthia A.}, year = {2015}, month = dec, edition = {Second}, publisher = {Esri Press}, address = {Redlands, California}, isbn = {978-1-58948-440-5}, langid = {english} } @techreport{bristol_city_council_deprivation_2015, title = {Deprivation in {{Bristol}} 2015}, author = {{Bristol City Council}}, year = {2015}, institution = {Bristol City Council}, keywords = {nosource} } @book{brunsdon_introduction_2015, title = {An {{Introduction}} to {{R}} for {{Spatial Analysis}} and {{Mapping}}}, author = {Brunsdon, Chris and Comber, Lex}, year = {2015}, month = feb, publisher = {SAGE Publications Ltd}, address = {Los Angeles}, abstract = {"In an age of big data, data journalism and with a wealth of quantitative information around us, it is not enough for students to be taught only 100 year old statistical methods using 'out of the box' software. They need to have 21st-century analytical skills too. This is an excellent and student-friendly text from two of the world leaders in the teaching and development of spatial analysis. It shows clearly why the open source software R is not just an alternative to commercial GIS, it may actually be the better choice for mapping, analysis and for replicable research. Providing practical tips as well as fully working code, this is a practical 'how to' guide ideal for undergraduates as well as those using R for the first time. It will be required reading on my own courses." - Richard Harris, Professor of Quantitative Social Science, University of Bristol R is a powerful open source computing tool that supports geographical analysis and mapping for the many geography and `non-geography' students and researchers interested in spatial analysis and mapping. This book provides an introduction to the use of R for spatial statistical analysis, geocomputation and the analysis of geographical information for researchers collecting and using data with location attached, largely through increased GPS functionality. Brunsdon and Comber take readers from `zero to hero' in spatial analysis and mapping through functions they have developed and compiled into R packages. This enables practical R applications in GIS, spatial analyses, spatial statistics, mapping, and web-scraping. Each chapter includes: Example data and commands for exploring it Scripts and coding to exemplify specific functionality Advice for developing greater understanding - through functions such as locator(), View(), and alternative coding to achieve the same ends Self-contained exercises for students to work through Embedded code within the descriptive text. ~This is a definitive 'how to' that takes students - of any discipline - from coding to actual applications and uses of R.}, isbn = {978-1-4462-7295-4}, langid = {english} } @article{brus_sampling_2018, title = {Sampling for Digital Soil Mapping: {{A}} Tutorial Supported by {{R}} Scripts}, shorttitle = {Sampling for Digital Soil Mapping}, author = {Brus, D. J.}, year = {2018}, month = aug, journal = {Geoderma}, issn = {0016-7061}, doi = {10/gf34fk}, urldate = {2018-09-11}, abstract = {In the past decade, substantial progress has been made in model-based optimization of sampling designs for mapping. This paper is an update of the overview of sampling designs for mapping presented by de Gruijter et al. (2006). For model-based estimation of values at unobserved points (mapping), probability sampling is not required, which opens up the possibility of optimized non-probability sampling. Non-probability sampling designs for mapping are regular grid sampling, spatial coverage sampling, k-means sampling, conditioned Latin hypercube sampling, response surface sampling, Kennard-Stone sampling and model-based sampling. In model-based sampling a preliminary model of the spatial variation of the soil variable of interest is used for optimizing the sample size and or the spatial coordinates of the sampling locations. Kriging requires knowledge of the variogram. Sampling designs for variogram estimation are nested sampling, independent random sampling of pairs of points, and model-based designs in which either the uncertainty about the variogram parameters, or the uncertainty about the kriging variance is minimized. Various minimization criteria have been proposed for designing a single sample that is suitable both for estimating the variogram and for mapping. For map validation, additional probability sampling is recommended, so that unbiased estimates of map quality indices and their standard errors can be obtained. For all sampling designs, R scripts are available in the supplement. Further research is recommended on sampling designs for mapping with machine learning techniques, designs that are robust against deviations of modeling assumptions, designs tailored at mapping multiple soil variables of interest and soil classes or fuzzy memberships, and probability sampling designs that are efficient both for design-based estimation of populations means and for model-based mapping.}, keywords = {K-means sampling,Kriging,Latin hypercube sampling,Model-based sampling,nosource,Spatial coverage sampling,Spatial simulated annealing,Variogram} } @book{brzustowicz_data_2017, title = {Data Science with {{Java}}: [Practical Methods for Scientists and Engineers]}, shorttitle = {Data Science with {{Java}}}, author = {Brzustowicz, Michael R.}, year = {2017}, edition = {First}, publisher = {O{\textasciiacute}Reilly}, address = {Beijing Boston Farnham}, isbn = {978-1-4919-3411-1}, langid = {english}, keywords = {Data Mining,Data mining Software,Datenanalyse,Java,Java (Computer program language),nosource}, annotation = {OCLC: 993428657} } @article{bucklin_rpostgis_2018, title = {Rpostgis: {{Linking R}} with a {{PostGIS Spatial Database}}}, author = {Bucklin, David and Basille, Mathieu}, year = {2018}, journal = {The R Journal}, doi = {10/c7fc}, keywords = {nosource} } @book{burrough_principles_2015, title = {Principles of Geographical Information Systems}, author = {Burrough, P. A. and McDonnell, Rachael and Lloyd, Christopher D.}, year = {2015}, edition = {Third}, publisher = {Oxford University Press}, address = {Oxford, New York}, isbn = {978-0-19-874284-5}, lccn = {G70.212 .B87 2015}, keywords = {Geographic information systems,nosource}, annotation = {OCLC: ocn915100245} } @article{calenge_package_2006, title = {The Package Adehabitat for the {{R}} Software: Tool for the Analysis of Space and Habitat Use by Animals}, author = {Calenge, C.}, year = {2006}, journal = {Ecological Modelling}, volume = {197}, pages = {1035}, doi = {10.1016/j.ecolmodel.2006.03.017}, keywords = {nosource} } @article{cawley_overfitting_2010, title = {On Over-Fitting in Model Selection and Subsequent Selection Bias in Performance Evaluation}, author = {Cawley, Gavin C. and Talbot, Nicola LC}, year = {2010}, journal = {Journal of Machine Learning Research}, volume = {11}, number = {Jul}, pages = {2079--2107}, keywords = {No DOI found,nosource} } @book{chambers_extending_2016, title = {Extending {{R}}}, author = {Chambers, John M.}, year = {2016}, month = jun, publisher = {CRC Press}, abstract = {Up-to-Date Guidance from One of the Foremost Members of the R Core Team Written by John M. Chambers, the leading developer of the original S software, Extending R covers key concepts and techniques in R to support analysis and research projects. It presents the core ideas of R, provides programming guidance for projects of all scales, and introduces new, valuable techniques that extend R. The book first describes the fundamental characteristics and background of R, giving readers a foundation for the remainder of the text. It next discusses topics relevant to programming with R, including the apparatus that supports extensions. The book then extends R's data structures through object-oriented programming, which is the key technique for coping with complexity. The book also incorporates a new structure for interfaces applicable to a variety of languages. A reflection of what R is today, this guide explains how to design and organize extensions to R by correctly using objects, functions, and interfaces. It enables current and future users to add their own contributions and packages to R.}, googlebooks = {kxxjDAAAQBAJ}, isbn = {978-1-4987-7572-4}, langid = {english}, keywords = {Business & Economics / Statistics,Mathematics / Probability & Statistics / General} } @incollection{cheshire_spatial_2015, title = {Spatial Data Visualisation with {{R}}}, booktitle = {Geocomputation}, author = {Cheshire, James and Lovelace, Robin}, editor = {Brunsdon, Chris and Singleton, Alex}, year = {2015}, pages = {1--14}, publisher = {SAGE Publications}, keywords = {nosource} } @article{clementini_comparison_1995, title = {A Comparison of Methods for Representing Topological Relationships}, author = {Clementini, Eliseo and Di Felice, Paolino}, year = {1995}, month = may, journal = {Information Sciences - Applications}, volume = {3}, number = {3}, pages = {149--178}, issn = {1069-0115}, doi = {10/ddtnhx}, urldate = {2021-11-13}, abstract = {In the field of spatial information systems, a primary need is to develop a sound theory of topological relationships between spatial objects. A category of formal methods for representing topological relationships is based on point-set theory. In this paper, a high level calculus-based method is compared with such point-set methods. It is shown that the calculus-based method is able to distinguish among finer topological configurations than most of the point-set methods. The advantages of the calculus-based method are the direct use in a calculus-based spatial query language and the capability of representing topological relationships among a significant set of spatial objects by means of only five relationship names and two boundary operators.}, langid = {english} } @article{conrad_system_2015, title = {System for {{Automated Geoscientific Analyses}} ({{SAGA}}) v. 2.1.4}, author = {Conrad, O. and Bechtel, B. and Bock, M. and Dietrich, H. and Fischer, E. and Gerlitz, L. and Wehberg, J. and Wichmann, V. and B{\"o}hner, J.}, year = {2015}, month = jul, journal = {Geosci. Model Dev.}, volume = {8}, number = {7}, pages = {1991--2007}, issn = {1991-9603}, doi = {10.5194/gmd-8-1991-2015}, urldate = {2017-06-12}, abstract = {The System for Automated Geoscientific Analyses (SAGA) is an open source geographic information system (GIS), mainly licensed under the GNU General Public License. Since its first release in 2004, SAGA has rapidly developed from a specialized tool for digital terrain analysis to a comprehensive and globally established GIS platform for scientific analysis and modeling. SAGA is coded in C++ in an object oriented design and runs under several operating systems including Windows and Linux. Key functional features of the modular software architecture comprise an application programming interface for the development and implementation of new geoscientific methods, a user friendly graphical user interface with many visualization options, a command line interpreter, and interfaces to interpreted languages like R and Python. The current version 2.1.4 offers more than 600 tools, which are implemented in dynamically loadable libraries or shared objects and represent the broad scopes of SAGA in numerous fields of geoscientific endeavor and beyond. In this paper, we inform about the system's architecture, functionality, and its current state of development and implementation. Furthermore, we highlight the wide spectrum of scientific applications of SAGA in a review of published studies, with special emphasis on the core application areas digital terrain analysis, geomorphology, soil science, climatology and meteorology, as well as remote sensing.} } @book{cooley_sfheaders_2020, title = {Sfheaders: {{Converts}} between {{R}} Objects and Simple Feature Objects}, author = {Cooley, David}, year = {2020}, publisher = {R package} } @article{coombes_efficient_1986, title = {An {{Efficient Algorithm}} to {{Generate Official Statistical Reporting Areas}}: {{The Case}} of the 1984 {{Travel-to-Work Areas Revision}} in {{Britain}}}, shorttitle = {An {{Efficient Algorithm}} to {{Generate Official Statistical Reporting Areas}}}, author = {Coombes, M. G. and Green, A. E. and Openshaw, S.}, year = {1986}, month = oct, journal = {The Journal of the Operational Research Society}, volume = {37}, number = {10}, eprint = {2582282}, eprinttype = {jstor}, pages = {943}, issn = {01605682}, doi = {10/b58h3x}, urldate = {2017-12-18}, keywords = {nosource} } @article{coppock_history_1991, title = {The History of {{GIS}}}, author = {Coppock, J Terry and Rhind, David W}, year = {1991}, journal = {Geographical Information Systems: Principles and Applications, vol. 1.}, volume = {1}, number = {1}, pages = {21--43}, abstract = {Coppock, J. T., and Rhind, D. W. 1991. The History of GIS. In Geographical Information Systems: Principles and Applications, vol. 1, ed. D. J. Maguire, M. F. Goodchild, and D. W. Rhind, pp. 21-43. New York: John Wiley and Sons.}, keywords = {History of GIS,No DOI found,nosource} } @book{dieck_algebraic_2008, title = {Algebraic Topology}, author = {tom Dieck, Tammo}, year = {2008}, series = {{{EMS}} Textbooks in Mathematics}, publisher = {European Mathematical Society}, address = {Z{\"u}rich}, isbn = {978-3-03719-048-7}, lccn = {QA612 .D53 2008}, keywords = {Algebraic topology,Homology theory,Homotopy theory}, annotation = {OCLC: ocn261176011} } @book{diggle_modelbased_2007, title = {Model-Based Geostatistics}, author = {Diggle, Peter and Ribeiro, Paulo Justiniano}, year = {2007}, publisher = {Springer}, keywords = {nosource} } @incollection{dillon_lomas_2003, title = {The {{Lomas}} Formations of Coastal {{Peru}}: {{Composition}} and Biogeographic History}, booktitle = {El {{Ni{\~n}o}} in {{Peru}}: {{Biology}} and Culture over 10,000 Years}, author = {Dillon, M. O. and Nakazawa, M. and Leiva, S. G.}, editor = {Haas, J. and Dillon, M. O.}, year = {2003}, pages = {1--9}, publisher = {Field Museum of Natural History}, address = {Chicago}, keywords = {nosource} } @book{dorman_learning_2014, title = {Learning {{R}} for {{Geospatial Analysis}}}, author = {Dorman, Michael}, year = {2014}, publisher = {Packt Publishing Ltd}, keywords = {nosource} } @article{douglas_algorithms_1973, title = {Algorithms for the Reduction of the Number of Points Required to Represent a Digitized Line or Its Caricature}, author = {Douglas, David H and Peucker, Thomas K}, year = {1973}, journal = {Cartographica: The International Journal for Geographic Information and Geovisualization}, volume = {10}, number = {2}, pages = {112--122}, doi = {10/bjwv52}, keywords = {nosource} } @book{dunnington_ggspatial_2021, title = {{{ggspatial}}: Spatial Data Framework for {{ggplot2}}}, author = {Dunnington, Dewey}, year = {2021}, publisher = {R package} } @article{eddelbuettel_extending_2018, title = {Extending {{R}} with {{C}}++: {{A Brief Introduction}} to {{Rcpp}}}, shorttitle = {Extending {{R}} with {{C}}++}, author = {Eddelbuettel, Dirk and Balamuta, James Joseph}, year = {2018}, month = jan, journal = {The American Statistician}, volume = {72}, number = {1}, pages = {28--36}, issn = {0003-1305}, doi = {10/gdg3fb}, urldate = {2018-10-01}, abstract = {R has always provided an application programming interface (API) for extensions. Based on the C language, it uses a number of macros and other low-level constructs to exchange data structures between the R process and any dynamically loaded component modules authors added to it. With the introduction of the Rcpp package, and its later refinements, this process has become considerably easier yet also more robust. By now, Rcpp has become the most popular extension mechanism for R. This article introduces Rcpp, and illustrates with several examples how the Rcpp Attributes mechanism in particular eases the transition of objects between R and C++ code. Supplementary materials for this article are available online.}, keywords = {nosource} } @inproceedings{egenhofer_mathematical_1990, title = {A Mathematical Framework for the Definition of Topological Relations}, booktitle = {Proc. the Fourth International Symposium on Spatial Data Handing}, author = {Egenhofer, Max and Herring, John}, year = {1990}, pages = {803--813}, keywords = {No DOI found} } @article{essd-11-647-2019, title = {{{ICGEM}} -- 15 Years of Successful Collection and Distribution of Global Gravitational Models, Associated Services, and Future Plans}, author = {Ince, E. S. and Barthelmes, F. and Rei{\ss}land, S. and Elger, K. and F{\"o}rste, C. and Flechtner, F. and Schuh, H.}, year = {2019}, journal = {Earth System Science Data}, volume = {11}, number = {2}, pages = {647--674}, doi = {10/gg5tzm} } @article{galletti_land_2016, title = {Land Changes and Their Drivers in the Cloud Forest and Coastal Zone of {{Dhofar}}, {{Oman}}, between 1988 and 2013}, author = {Galletti, Christopher S. and Turner, Billie L. and Myint, Soe W.}, year = {2016}, journal = {Regional Environmental Change}, volume = {16}, number = {7}, pages = {2141--2153}, issn = {1436-3798, 1436-378X}, doi = {10/gkb5bm}, urldate = {2018-10-17}, langid = {english}, keywords = {nosource} } @book{garrard_geoprocessing_2016, title = {Geoprocessing with {{Python}}}, author = {Garrard, Chris}, year = {2016}, publisher = {Manning Publications}, address = {Shelter Island, NY}, isbn = {978-1-61729-214-9}, lccn = {GA102.4.E4 G37 2016}, keywords = {Cartography,Computer programs,Data processing,Geospatial data,nosource,Python (Computer program language)}, annotation = {OCLC: ocn915498655} } @book{gelfand_handbook_2010, title = {Handbook of Spatial Statistics}, author = {Gelfand, Alan E and Diggle, Peter and Guttorp, Peter and Fuentes, Montserrat}, year = {2010}, publisher = {CRC Press}, isbn = {1-4200-7288-9}, keywords = {nosource} } @book{gillespie_efficient_2016, title = {Efficient {{R Programming}}: {{A Practical Guide}} to {{Smarter Programming}}}, author = {Gillespie, Colin and Lovelace, Robin}, year = {2016}, publisher = {O'Reilly Media}, isbn = {978-1-4919-5078-4}, keywords = {nosource} } @book{giraud_mapsf_2021, title = {Mapsf: {{Thematic}} Cartography}, author = {Giraud, Timoth{\'e}e}, year = {2021}, publisher = {R package} } @article{goetz_evaluating_2015, title = {Evaluating Machine Learning and Statistical Prediction Techniques for Landslide Susceptibility Modeling}, author = {Goetz, J.N. and Brenning, A. and Petschko, H. and Leopold, P.}, year = {2015}, month = aug, journal = {Computers \& Geosciences}, volume = {81}, pages = {1--11}, issn = {00983004}, doi = {10/f7hcgp}, urldate = {2017-11-24}, langid = {english}, keywords = {nosource} } @article{gold_outsidein_1996, title = {Outside-in: An Alternative Approach to Forest Map Digitizing}, shorttitle = {Outside-In}, author = {Gold, C. M. and Nantel, J. and Yang, W.}, year = {1996}, month = apr, journal = {International Journal of Geographical Information Science}, publisher = {Taylor \& Francis Group}, doi = {10.1080/02693799608902080}, urldate = {2024-05-04}, abstract = {Abstract. This paper examines the problem of polygon digitizing, and suggests an inversion of the traditional approach for polygons of the environmental type, where each individual polygon, rather ...}, copyright = {Copyright Taylor and Francis Group, LLC}, langid = {english}, annotation = {21 citations (Crossref) [2024-05-04]} } @book{gomez-rubio_bayesian_2020, title = {Bayesian Inference with {{INLA}}}, author = {{G{\'o}mez-Rubio}, Virgilio}, year = {2020}, publisher = {CRC Press} } @article{goncalves_segoptim_2019, title = {{{SegOptim}}---{{A}} New {{R}} Package for Optimizing Object-Based Image Analyses of High-Spatial Resolution Remotely-Sensed Data}, author = {Gon{\c c}alves, Jo{\~a}o and P{\^o}{\c c}as, Isabel and Marcos, Bruno and M{\"u}cher, C.A. and Honrado, Jo{\~a}o P.}, year = {2019}, month = apr, journal = {International Journal of Applied Earth Observation and Geoinformation}, volume = {76}, pages = {218--230}, issn = {15698432}, doi = {10.1016/j.jag.2018.11.011}, urldate = {2022-10-06}, abstract = {Geographic Object-based Image Analysis (GEOBIA) is increasingly used to process high-spatial resolution imagery, with applications ranging from single species detection to habitat and land cover mapping. Image segmentation plays a key role in GEOBIA workflows, allowing to partition images into homogenous and mutually exclusive regions. Nonetheless, segmentation techniques require a robust parameterization to achieve the best results. Frequently, inappropriate parameterization leads to sub-optimal results and difficulties in comparing distinct methods.}, langid = {english} } @book{goovaerts_geostatistics_1997, title = {Geostatistics for Natural Resources Evaluation}, author = {Goovaerts, Pierre}, year = {1997}, series = {Applied Geostatistics Series}, publisher = {Oxford University Press}, address = {New York}, isbn = {978-0-19-511538-3}, lccn = {QE33.2.M3 G66 1997}, keywords = {Geology,nosource,Statistical methods} } @article{graser_processing_2015, title = {Processing: {{A Python Framework}} for the {{Seamless Integration}} of {{Geoprocessing Tools}} in {{QGIS}}}, author = {Graser, Anita and Olaya, Victor}, year = {2015}, volume = {4}, number = {4}, doi = {10/f76d7c}, urldate = {2017-06-12}, abstract = {Processing is an object-oriented Python framework for the popular open source Geographic Information System QGIS, which provides a seamless integration of geoprocessing tools from a variety of different software libraries. In this paper, we present the development history, software architecture and features of the Processing framework, which make it a versatile tool for the development of geoprocessing algorithms and workflows, as well as an efficient integration platform for algorithms from different sources. Using real-world application examples, we furthermore illustrate how the Processing architecture enables typical geoprocessing use cases in research and development, such as automating and documenting workflows, combining algorithms from different software libraries, as well as developing and integrating custom algorithms. Finally, we discuss how Processing can facilitate reproducible research and provide an outlook towards future development goals.}, keywords = {nosource} } @book{grolemund_r_2016, title = {R for {{Data Science}}}, author = {Grolemund, Garrett and Wickham, Hadley}, year = {2016}, month = jul, publisher = {O'Reilly Media}, isbn = {978-1-4919-1039-9}, langid = {english} } @article{harris_more_2017, title = {More Bark than Bytes? {{Reflections}} on 21+ Years of Geocomputation}, shorttitle = {More Bark than Bytes?}, author = {Harris, Richard and O'Sullivan, David and Gahegan, Mark and Charlton, Martin and Comber, Lex and Longley, Paul and Brunsdon, Chris and Malleson, Nick and Heppenstall, Alison and Singleton, Alex and {Arribas-Bel}, Daniel and Evans, Andy}, year = {2017}, month = jul, journal = {Environment and Planning B: Urban Analytics and City Science}, doi = {10/ggr3jb}, urldate = {2017-07-10}, abstract = {This year marks the 21st anniversary of the International GeoComputation Conference Series. To celebrate the occasion, Environment and Planning B invited some members of the geocomputational community to reflect on its achievements, some of the unrealised potential, and to identify some of the on-going challenges.}, langid = {english}, keywords = {nosource} } @book{hengl_practical_2007, title = {A Practical Guide to Geostatistical Mapping of Environmental Variables}, author = {Hengl, Tomislav}, year = {2007}, publisher = {Publications Office}, address = {Luxembourg}, abstract = {Geostatistical mapping can be defined as analytical production of maps by using field observations, auxiliary information and a computer program that calculates values at locations of interest. Today, increasingly the heart of a mapping project is, in fact, the computer program that implements some (geo)statistical algorithm to a given point data set. Purpose of this guide is to assist you in producing quality maps by using fully-operational tools, without a need for serious additional investments. It will first introduce you the to the basic principles of geostatistical mapping and regression-kriging, as the key prediction technique, then it will guide you through four software packages: ILWIS GIS, R+gstat, SAGA GIS and Google Earth, which will be used to prepare the data, run analysis and make final layouts. These materials have been used for the five-days advanced training course "Hands-on-geostatistics: merging GIS and spatial statistics", that is regularly organized by the author and collaborators. Visit the course website to obtain a copy of the datasets used in this exercise. [R{\'e}sum{\'e} de l'auteur].}, isbn = {978-92-79-06904-8}, langid = {english}, keywords = {nosource}, annotation = {OCLC: 758643236} } @article{hengl_random_2018, title = {Random Forest as a Generic Framework for Predictive Modeling of Spatial and Spatio-Temporal Variables}, author = {Hengl, Tomislav and Nussbaum, Madlene and Wright, Marvin N. and Heuvelink, Gerard B.M. and Gr{\"a}ler, Benedikt}, year = {2018}, month = aug, journal = {PeerJ}, volume = {6}, pages = {e5518}, issn = {2167-8359}, doi = {10/gd66jm}, urldate = {2018-09-25}, langid = {english}, keywords = {nosource} } @dataset{hengl_t_2021_5774954, title = {Global {{MODIS-based}} Snow Cover Monthly Long-Term (2000-2012) at 500 m, and Aggregated Monthly Values (2000-2020) at 1 Km}, author = {Hengl, T.}, year = {2021}, month = dec, publisher = {Zenodo}, doi = {10.5281/zenodo.5774954}, version = {v1.0} } @article{hesselbarth_opensource_2021, title = {Open-Source Tools in {{R}} for Landscape Ecology}, author = {Hesselbarth, Maximillian H.K. and Nowosad, Jakub and Signer, Johannes and Graham, Laura J.}, year = {2021}, month = jun, volume = {6}, number = {3}, pages = {97--111}, publisher = {{Springer Science and Business Media LLC}}, doi = {10/gnckbj} } @article{hickman_transitions_2011, title = {Transitions to Low Carbon Transport Futures: Strategic Conversations from {{London}} and {{Delhi}}}, shorttitle = {Transitions to Low Carbon Transport Futures}, author = {Hickman, Robin and Ashiru, Olu and Banister, David}, year = {2011}, month = nov, journal = {Journal of Transport Geography}, series = {Special Section on {{Alternative Travel}} Futures}, volume = {19}, number = {6}, pages = {1553--1562}, issn = {0966-6923}, doi = {10/cwxs9s}, urldate = {2016-05-14}, abstract = {Climate change is a global problem and across the world there are major difficulties being experienced in reducing carbon dioxide (CO2) emissions. The transport sector in particular is finding it difficult to reduce CO2 emissions. This paper reports on two studies carried out by the authors in London (UK) and Delhi (India). It considers the common objectives for transport CO2 reduction, but the very different contexts and baselines, potentials for change, and some possible synergies. Different packages of measures are selected and scenarios developed for each context which are consistent with contraction and convergence objectives. CO2 reduction potentials are modelled and quantified by package and scenario. London is considering deep reductions on current transport CO2 emission levels; Delhi is seeking to break the huge projected rise in transport CO2 emissions. The scale of policy intervention required to achieve these goals is huge and there is certainly little public discussion of the magnitude of the changes required. The paper argues for a `strategic conversation' at the city level, using scenario analysis, to discuss the priorities for intervention in delivering low carbon transport futures. A greater focus is required in developing participatory approaches to decision making, alongside network investments, urban planning, low emission vehicles and wider initiatives. Aspirations towards equitable target emissions may assist in setting sufficiently demanding targets. Only then is a wider awareness and ownership of potential carbon efficient transport futures likely to take place.}, keywords = {City planning,CO2,Delhi,London,Sustainable,Transport} } @book{hijmans_geosphere_2016, title = {Geosphere: {{Spherical Trigonometry}}}, author = {Hijmans, Robert J.}, year = {2016}, publisher = {R package}, keywords = {nosource} } @manual{hijmans_terra_2021, type = {Manual}, title = {Terra: {{Spatial}} Data Analysis}, author = {Hijmans, Robert J.}, year = {2021} } @book{hollander_transport_2016, title = {Transport {{Modelling}} for a {{Complete Beginner}}}, author = {Hollander, Yaron}, year = {2016}, month = dec, publisher = {CTthink!}, abstract = {Finally! A book about transport modelling which doesn't require any previous knowledge. "Transport modelling for a complete beginner" explains the basics of transport modelling in a simple language, with lots of silly drawings, and without using any mathematics. Click here to watch a 3-minute introductory video (or search for the book name on YouTube if the link doesn't show). ~ This book is aimed at transport planners, town planners, students in transport-related courses, policy advisors, economists, project managers, property developers, investors, politicians, journalists, and anyone else who wants to understand the process of making decisions on transport infrastructure. It is suitable for readers in any country.~ ~ The book is split into two parts. The first part is about the principles of transport modelling. This part talks about travel demand, transport networks, zones, trip matrices, the value of time, trip generation, mode split, destination choice, model calibration -- lots of scary words that need explaining in order to understand the role of models in the assessment of transport projects. All modes of transport are covered: cars, buses, trains, trucks, taxis, walking, cycling and others. Hot air balloons may be the only transport mode that is hardly mentioned.~ ~ The second part of the book covers more strategic issues. It talks about the culture of transport modelling, including the management of transport modelling work, the way model outputs are communicated, and the professional environment where this is done. This part of the book also contains an honest discussion of common modelling practices which should be recommended and others which should not.~ ~ ``Transport modelling for a complete beginner'' will help you ensure that anything you do with a transport model remains fair, effective and based on real evidence.}, isbn = {978-0-9956624-1-4}, langid = {english} } @book{horni_multi-agent_2016, title = {The {{Multi-Agent Transport Simulation MATSim}}}, author = {Horni, Andreas and Nagel, Kai and Axhausen, Kay W.}, year = {2016}, month = aug, publisher = {Ubiquity Press}, urldate = {2017-12-29}, abstract = {The MATSim (Multi-Agent Transport Simulation) software project was started around 2006 with the goal of generating traffic and congestion patterns by following individual synthetic travelers through their daily or weekly activity programme. It has since then evolved from a collection of stand-alone C++ programs to an integrated Java-based framework which is publicly hosted, open-source available, automatically regression tested. It is currently used by about 40 groups throughout the world. This book takes stock of the current status.}, isbn = {978-1-909188-77-8 978-1-909188-75-4 978-1-909188-78-5 978-1-909188-76-1}, langid = {english}, keywords = {nosource} } @inproceedings{hornik_approaches_2003, title = {Approaches to {{Classes}} for {{Spatial Data}} in {{R}}}, booktitle = {Proceedings of {{DSC}}}, author = {Bivand, Roger}, editor = {Hornik, Kurt and Leisch, Friedrich and Zeileis, Achim}, year = {2003}, urldate = {2017-06-27}, keywords = {No DOI found,nosource} } @article{huang_geospark_2017, title = {{{GeoSpark SQL}}: {{An Effective Framework Enabling Spatial Queries}} on {{Spark}}}, shorttitle = {{{GeoSpark SQL}}}, author = {Huang, Zhou and Chen, Yiran and Wan, Lin and Peng, Xia}, year = {2017}, month = sep, journal = {ISPRS International Journal of Geo-Information}, volume = {6}, number = {9}, pages = {285}, issn = {2220-9964}, doi = {10/gcnq5h}, urldate = {2018-06-29}, langid = {english}, keywords = {nosource} } @article{huff_probabilistic_1963, title = {A {{Probabilistic Analysis}} of {{Shopping Center Trade Areas}}}, author = {Huff, David L.}, year = {1963}, journal = {Land Economics}, volume = {39}, number = {1}, eprint = {3144521}, eprinttype = {jstor}, pages = {81--90}, issn = {0023-7639}, doi = {10/b69ptc}, urldate = {2017-11-06}, keywords = {nosource} } @book{hunziker_velox:_2017, title = {Velox: {{Fast Raster Manipulation}} and {{Extraction}}}, author = {Hunziker, Philipp}, year = {2017}, keywords = {nosource} } @article{jafari_investigation_2015, title = {Investigation of {{Centroid Connector Placement}} for {{Advanced Traffic Assignment Models}} with {{Added Network Detail}}}, author = {Jafari, Ehsan and Gemar, Mason D. and Juri, Natalia Ruiz and Duthie, Jennifer}, year = {2015}, month = jun, journal = {Transportation Research Record: Journal of the Transportation Research Board}, volume = {2498}, pages = {19--26}, issn = {0361-1981}, doi = {10/gkb5nj}, urldate = {2018-01-01}, langid = {english} } @book{james_introduction_2013, title = {An Introduction to Statistical Learning: With Applications in {{R}}}, shorttitle = {An Introduction to Statistical Learning}, editor = {James, Gareth and Witten, Daniela and Hastie, Trevor and Tibshirani, Robert}, year = {2013}, series = {Springer Texts in Statistics}, number = {103}, publisher = {Springer}, address = {New York}, isbn = {978-1-4614-7137-0}, lccn = {QA276 .I585 2013}, keywords = {Mathematical models,Mathematical statistics,nosource,R (Computer program language),Statistics}, annotation = {OCLC: ocn828488009} } @article{jasiewicz_geomorphons_2013, title = {Geomorphons --- a Pattern Recognition Approach to Classification and Mapping of Landforms}, author = {Jasiewicz, Jaros{\l}aw and Stepinski, Tomasz F.}, year = {2013}, month = jan, journal = {Geomorphology}, volume = {182}, pages = {147--156}, issn = {0169555X}, doi = {10.1016/j.geomorph.2012.11.005}, urldate = {2020-06-29}, abstract = {We introduce a novel method for classification and mapping of landform elements from a DEM based on the principle of pattern recognition rather than differential geometry. At the core of the method is the concept of geomorphon (geomorphologic phonotypes) --- a simple ternary pattern that serves as an archetype of a particular terrain morphology. A finite number of 498 geomorphons constitute a comprehensive and exhaustive set of all possible morphological terrain types including standard elements of landscape, as well as unfamiliar forms rarely found in natural terrestrial surfaces. A single scan of a DEM assigns an appropriate geomorphon to every cell in the raster using a procedure that self-adapts to identify the most suitable spatial scale at each location. As a result, the method classifies landform elements at a range of different spatial scales with unprecedented computational efficiency. A general purpose geomorphometric map --- an interpreted map of topography --- is obtained by generalizing allgeomorphons to a small number of the most common landform elements. Due to the robustness and high computational efficiency of the method high resolution geomorphometric maps having continental and even global extents can be generated from giga-cell DEMs. Such maps are a valuable new resource for both manual and automated geomorphometric analyses. In order to demonstrate a practical application of this new method, a 30 m cell-1 geomorphometric map of the entire country of Poland is generated and the features and potential usage of this map are briefly discussed. The computer implementation of the method is outlined. The code is available in the public domain.}, langid = {english} } @incollection{jenny_guide_2017, title = {A Guide to Selecting Map Projections for World and Hemisphere Maps}, booktitle = {Choosing a {{Map Projection}}}, author = {Jenny, Bernhard and {\v S}avri{\v c}, Bojan and Arnold, Nicholas D and Marston, Brooke E and Preppernau, Charles A}, editor = {Lapaine, Miljenko and Usery, Lynn}, year = {2017}, pages = {213--228}, publisher = {Springer}, keywords = {nosource} } @article{kahle_ggmap_2013, title = {Ggmap: {{Spatial Visualization}} with Ggplot2}, author = {Kahle, D and Wickham, Hadley}, year = {2013}, journal = {The R Journal}, volume = {5}, pages = {144--161}, doi = {10.32614/RJ-2013-014}, keywords = {nosource} } @article{kaiser_algorithms_1993, title = {Algorithms for Computing Centroids}, author = {Kaiser, M.J. and Morin, T.L.}, year = {1993}, month = feb, journal = {Computers \& Operations Research}, volume = {20}, number = {2}, pages = {151--165}, issn = {03050548}, doi = {10/dvxsr3}, urldate = {2018-07-10}, abstract = {Algorithms are given for the computation of centroids of discrete, polygonal, and continuous convex regions in the plane. These include the zero-dimensional center-of-gravity for discrete systems, and the area, perimeter, and curvature centroids for both discrete and continuous regions. The zero-dimensional inter-of-gravity is motivated through analytic, arithmetic, and geometric fo{\textasciitilde}ulations, and is an integral part of the computations of the area, perimeter, and curvature centroids. Several remarks are made that connect the computation of the centoid points to optimization theory and their practical application in various fields. The complexity of each algorithm is aho examined.}, langid = {english}, keywords = {nosource} } @article{karatzoglou_kernlab_2004, title = {Kernlab - {{An S4}} {{Package}} for {{Kernel Methods}} in {{R}}}, author = {Karatzoglou, Alexandros and Smola, Alex and Hornik, Kurt and Zeileis, Achim}, year = {2004}, journal = {Journal of Statistical Software}, volume = {11}, number = {9}, issn = {1548-7660}, doi = {10/gdq9pc}, urldate = {2018-03-28}, langid = {english}, keywords = {nosource} } @article{knuth_computer_1974, title = {Computer {{Programming As}} an {{Art}}}, author = {Knuth, Donald E.}, year = {1974}, month = dec, journal = {Commun. ACM}, volume = {17}, number = {12}, pages = {667--673}, issn = {0001-0782}, doi = {10/fhrtw3}, urldate = {2018-07-11}, abstract = {When Communications of the ACM began publication in 1959, the members of ACM's Editorial Board made the following remark as they described the purposes of ACM's periodicals [2]: ``If computer programming is to become an important part of computer research and development, a transition of programming from an art to a disciplined science must be effected.'' Such a goal has been a continually recurring theme during the ensuing years; for example, we read in 1970 of the ``first steps toward transforming the art of programming into a science'' [26]. Meanwhile we have actually succeeded in making our discipline a science, and in a remarkably simple way: merely by deciding to call it ``computer science.''}, keywords = {nosource} } @book{krainski_advanced_2018, title = {Advanced {{Spatial Modeling}} with {{Stochastic Partial Differential Equations Using R}} and {{INLA}}}, author = {Krainski, Elias and G{\'o}mez Rubio, Virgilio and Bakka, Haakon and Lenzi, Amanda and {Castro-Camilo}, Daniela and Simpson, Daniel and Lindgren, Finn and Rue, H{\aa}vard}, year = {2018}, month = sep, abstract = {Book on spatial and spatio-temporal modeling with SPDEs and INLA. R code and free Gitbook version here: http://www.r-inla.org/spde-book .}, isbn = {978-1-138-36985-6} } @article{krug_clearing_2010, title = {Clearing of Invasive Alien Plants under Different Budget Scenarios: Using a Simulation Model to Test Efficiency}, shorttitle = {Clearing of Invasive Alien Plants under Different Budget Scenarios}, author = {Krug, Rainer M. and {Roura-Pascual}, N{\'u}ria and Richardson, David M.}, year = {2010}, journal = {Biological invasions}, volume = {12}, number = {12}, pages = {4099--4112}, doi = {10/fn3bmr}, urldate = {2017-08-24}, keywords = {nosource} } @book{kuhn_applied_2013, title = {Applied Predictive Modeling}, author = {Kuhn, Max and Johnson, Kjell}, year = {2013}, publisher = {Springer}, address = {New York}, isbn = {978-1-4614-6848-6}, lccn = {QA276 .K79 2013}, keywords = {Mathematical models,Mathematical statistics,nosource,Prediction theory}, annotation = {OCLC: ocn827083441} } @book{lahn_openeo_2021, title = {Openeo: {{Client}} Interface for '{{openEO}}' Servers}, author = {Lahn, Florian}, year = {2021}, publisher = {R package} } @book{lamigueiro_displaying_2014, title = {Displaying Time Series, Spatial, and Space-Time Data with {{R}}}, author = {Lamigueiro, Oscar}, year = {2014}, publisher = {CRC Press}, keywords = {nosource} } @book{lamigueiro_displaying_2018, title = {Displaying {{Time Series}}, {{Spatial}}, and {{Space-Time Data}} with {{R}}}, author = {Lamigueiro, Oscar Perpinan}, year = {2018}, month = aug, edition = {Second}, publisher = {Chapman \& Hall/CRC}, address = {Boca Raton}, abstract = {Focusing on the exploration of data with visual methods, Displaying Time Series, Spatial, and Space-Time Data with R, Second Edition, presents methods and R code for producing high-quality static graphics, interactive visualizations, and animations of time series, spatial, and space-time data. Practical examples using real-world datasets help you understand how to apply the methods and code.The book illustrates how to display a dataset starting with an easy and direct approach, and progressively adds improvements that involve more complexity. Each of the three parts of the book is devoted to different types of data. In each part, the chapters are grouped according to the various visualization methods or data characteristics.The first edition of this book was mainly focused on static graphics. Four years later, recent developments in the "htmlwidgets" family of packages are covered in this second edition with many new interactive graphics. In addition, the "ggplot2" approach is now used in most of the spatial graphics, thanks to the new "sf" package. Finally, code has been cleaned and improved, and data has been updated.Features{$\bullet$} Offers detailed information on producing high-quality graphics, interactive visualizations, and animations{$\bullet$} Uses real data from meteorological, climate, economic, social science, energy, engineering, environmental, and epidemiological research in many practical examples{$\bullet$} Shows how to improve graphics based on visualization theory{$\bullet$} Provides the graphics, data, and R code on the author's website, enabling you to practice with the methods and modify the code to suit your own needs.}, isbn = {978-1-138-08998-3}, langid = {english} } @article{landa_new_2008, title = {New {{GUI}} for {{GRASS GIS}} Based on {{wxPython}}}, author = {Landa, Martin}, year = {2008}, journal = {Departament of Geodesy and Cartography}, pages = {1--17}, keywords = {No DOI found,nosource} } @article{landau_targets_2021, title = {The Targets {{R}} Package: A Dynamic {{Make-like}} Function-Oriented Pipeline Toolkit for Reproducibility and High-Performance Computing}, author = {Landau, William Michael}, year = {2021}, journal = {Journal of Open Source Software}, volume = {6}, number = {57}, pages = {2959}, doi = {10.21105/joss.02959} } @article{lang_mlr3_2019, title = {Mlr3: {{A}} Modern Object-Oriented Machine Learning Framework in {{R}}}, shorttitle = {Mlr3}, author = {Lang, Michel and Binder, Martin and Richter, Jakob and Schratz, Patrick and Pfisterer, Florian and Coors, Stefan and Au, Quay and Casalicchio, Giuseppe and Kotthoff, Lars and Bischl, Bernd}, year = {2019}, journal = {Journal of Open Source Software}, volume = {4}, number = {44}, pages = {1903}, doi = {10.21105/joss.01903} } @article{lefkowitz_identification_1975, title = {Identification of Adenylate Cyclase-Coupled Beta-Adrenergic Receptors with Radiolabeled Beta-Adrenergic Antagonists}, author = {Lefkowitz, R. J.}, year = {1975}, month = sep, journal = {Biochemical Pharmacology}, volume = {24}, number = {18}, pages = {1651--1658}, issn = {0006-2952}, langid = {english}, pmid = {11}, keywords = {Adenylyl Cyclases,Adrenergic beta-Antagonists,Alprenolol,Animals,Anura,Binding Sites,Catecholamines,Cattle,Cell Membrane,Eels,Erythrocytes,Guinea Pigs,In Vitro Techniques,Isoproterenol,Kinetics,nosource,Propranolol,Receptors Adrenergic,Stereoisomerism,Tritium} } @article{li_natural_1993, title = {A {{Natural Principle}} for the {{Objective Generalization}} of {{Digital Maps}}}, author = {Li, Zhilin and Openshaw, Stan}, year = {1993}, month = jan, journal = {Cartography and Geographic Information Systems}, volume = {20}, number = {1}, pages = {19--29}, issn = {1050-9844}, doi = {10.1559/152304093782616779}, urldate = {2024-05-04}, langid = {english}, annotation = {56 citations (Crossref) [2024-05-04]} } @book{liu_essential_2009, title = {Essential Image Processing and {{GIS}} for Remote Sensing}, author = {Liu, Jian-Guo and Mason, Philippa J.}, year = {2009}, publisher = {Wiley-Blackwell}, address = {Chichester, West Sussex, UK, Hoboken, NJ}, isbn = {978-0-470-51032-2 978-0-470-51031-5}, lccn = {G70.4 .L583 2009}, keywords = {Earth (Planet),Geographic information systems,Image processing,nosource,Remote sensing,Surface Remote sensing} } @book{livingstone_geographical_1992, title = {The {{Geographical Tradition}}: {{Episodes}} in the {{History}} of a {{Contested Enterprise}}}, shorttitle = {The {{Geographical Tradition}}}, author = {Livingstone, David N.}, year = {1992}, month = dec, publisher = {John Wiley \& Sons Ltd}, address = {Oxford, UK ; Cambridge, USA}, abstract = {The Geographical Tradition presents the history of an essentially contested tradition. By examining a series of key episodes in geography{$\prime$}s history since 1400, Livingstone argues that the messy contingencies of history are to be preferred to the manufactured idealizations of the standard chronicles. Throughout, the development of geographical thought and practice is portrayed against the background of the broader social and intellectual contexts of the times. Among the topics investigated are geography during the Age of Reconnaissance, the Scientific Revolution and The Englightenment; subsequently geography{$\prime$}s relationships with Darwinism, imperialism, regionalism, and quantification are elaborated.}, isbn = {978-0-631-18586-4}, langid = {english} } @article{loecher_rgooglemaps_2015, title = {{{RgoogleMaps}} and Loa: {{Unleashing R Graphics Power}} on {{Map Tiles}}}, shorttitle = {{{RgoogleMaps}} and Loa}, author = {Loecher, Markus and Ropkins, Karl}, year = {2015}, month = feb, journal = {Journal of Statistical Software}, volume = {63}, pages = {1--18}, issn = {1548-7660}, doi = {10/gfgwng}, urldate = {2021-11-01}, abstract = {The RgoogleMaps package provides (1) an R interface to query the Google and the OpenStreetMap servers for static maps in the form of PNGs, and (2) enables the user to overlay plots on those maps within R. The loa package provides dedicated panel functions to integrate RgoogleMaps within the lattice plotting environment. In addition to solving the generic task of plotting on a map background in R, we introduce several specific algorithms to detect and visualize spatio-temporal clusters. This task can often be reduced to detecting over-densities in space relative to a background density. The relative density estimation is framed as a binary classification problem. An integrated hotspot visualizer is presented which allows the efficient identification and visualization of clusters in one environment. Competing clustering methods such as the scan statistic and the density scan offer higher detection power at a much larger computational cost. Such clustering methods can then be extended using the lattice trellis framework to provide further insight into the relationship between clusters and potentially influential parameters. While there are other options for such map `mashups' we believe that the integration of RgoogleMaps and lattice using loa can in certain circumstances be advantageous, e.g., by providing a highly intuitive working environment for multivariate analysis and flexible testbed for the rapid development of novel data visualizations.}, copyright = {Copyright (c) 2013 Markus Loecher, Karl Ropkins}, langid = {english} } @article{loidl_spatial_2016, ids = {loidl_spatial_2016a}, title = {Spatial Patterns and Temporal Dynamics of Urban Bicycle Crashes---{{A}} Case Study from {{Salzburg}} ({{Austria}})}, author = {Loidl, Martin and Traun, Christoph and Wallentin, Gudrun}, year = {2016}, month = apr, journal = {Journal of Transport Geography}, volume = {52}, number = {Supplement C}, pages = {38--50}, issn = {0966-6923}, doi = {10/f8qrzb}, urldate = {2017-10-18}, abstract = {Most bicycle crash analyses are designed as explanatory studies. They aim to identify contributing risk factors and calculate risk rates based on -- most of the time -- highly aggregated statistical data. In contrast to such explanatory study designs, the presented study follows an exploratory approach, focusing on the absolute number of crashes. The aim is to reveal and describe patterns and dynamics of urban bicycle crashes on various spatial scale levels and temporal resolutions through a multi-stage workflow. Spatial units are delineated in the network space and serve as initial units of aggregation. In order to facilitate comparisons among regions and quantify temporal dynamics, a reference value of crash frequency is simulated for each unit of the respective spatial scale level and temporal resolution. For the presented case study, over 3000 geo-coded bicycle crashes in the city of Salzburg (Austria) were analyzed. The data set covers 10years and comprises all bicycle crashes reported by the police. Distinct spatial and temporal patterns with clusters, seasonal variations, and regional particularities could be revealed. These insights are indicators for urban dynamics in the transport system and allow for further, targeted in-depth analyses and subsequent counter measures. Moreover, the results prove the applicability of the proposed multi-stage workflow and demonstrate the added value of analyses of small aggregates on various scale levels, down to single crashes, and temporal resolutions.}, keywords = {Bicycle crashes,Exploratory analysis,Spatial and temporal dynamics} } @book{longley_geocomputation_1998, title = {Geocomputation: {{A Primer}}}, shorttitle = {Geocomputation}, editor = {Longley, Paul and Brooks, Sue M. and McDonnell, Rachael and MacMillan, Bill}, year = {1998}, month = oct, publisher = {Wiley}, address = {Chichester, England; New York}, abstract = {Geocomputation A Primer edited by Paul A Longley Sue M Brooks Rachael McDonnell School of Geographical Sciences, University of Bristol, UK and Bill Macmillan School of Geography, University of Oxford, UK This book encompasses all that is new in geocomputation. It is also a primer - that is, a book which sets out the foundations and scope of this important emergent area from the same contemporary perspective. The catalyst to the emergence of geocomputation is the new and creative application of computers to devise and depict digital representations of the Earth's surface. The environment for geocomputation is provided by geographical information systems (GIS), yet geocomputation is much more than GIS. Geocomputation is a blend of research-led applications which emphasise process over form, dynamics over statics, and interaction over passive response. This book presents a timely blend of current research and practice, written by the leading figures in the field. It provides insights to a new and rapidly developing area, and identifies the key foundations to future developments. It should be read by all who seek to use geocomputational methods for solving real world problems.}, isbn = {978-0-471-98576-1}, langid = {english} } @book{longley_geographic_2015, title = {Geographic Information Science \& Systems}, author = {Longley, Paul}, year = {2015}, edition = {Fourth}, publisher = {Wiley}, address = {Hoboken, NJ}, abstract = {"Effective use of today's powerful GIS technology requires an understanding of the science of problem-solving that underpins it. Since the first edition published over a decade ago, this book has led the way, with its focus on the scientific principles that support GIS usage. It has also provided thorough, upto- date coverage of GIS procedures, techniques and public policy applications. This unique combination of science, technology and practical problem solving has made this book a best-seller across a broad spectrum of disciplines. This fully updated 4th edition continues to deliver on these strengths"--}, isbn = {978-1-118-67695-0}, lccn = {G70.212 .L658 2015}, keywords = {Geographic information systems,nosource,Technology & Engineering / Remote Sensing & Geographic Information Systems} } @techreport{lott_geographic_2015, title = {Geographic Information-{{Well-known}} Text Representation of Coordinate Reference Systems}, author = {Lott, Roger}, year = {2015}, institution = {Open Geospatial Consortium} } @book{lovelace_geocomputation_2019, title = {Geocomputation with {{R}}}, author = {Lovelace, Robin and Nowosad, Jakub and Muenchow, Jannes}, year = {2019}, publisher = {CRC Press}, urldate = {2017-10-05}, abstract = {Book on geographic data with R.}, isbn = {1-138-30451-4} } @article{lovelace_jittering_2022b, title = {Jittering: {{A Computationally Efficient Method}} for {{Generating Realistic Route Networks}} from {{Origin-Destination Data}}}, shorttitle = {Jittering}, author = {Lovelace, Robin and F{\'e}lix, Rosa and Carlino, Dustin}, year = {2022}, month = apr, journal = {Findings}, pages = {33873}, publisher = {Findings Press}, doi = {10.32866/001c.33873}, urldate = {2022-05-05}, abstract = {Origin-destination (OD) datasets are often represented as `desire lines' between zone centroids. This paper presents a `jittering' approach to pre-processing and conversion of OD data into geographic desire lines that (1) samples unique origin and destination locations for each OD pair, and (2) splits `large' OD pairs into `sub-OD' pairs. Reproducible findings, based on the open source \_odjitter\_ Rust crate, show that route networks generated from jittered desire lines are more geographically diffuse than route networks generated by `unjittered' data. We conclude that the approach is a computationally efficient and flexible way to simulate transport patterns, particularly relevant for modelling active modes. Further work is needed to validate the approach and to find optimal settings for sampling and disaggregation.}, copyright = {Creative Commons Attribution-ShareAlike 4.0 International Licence (CC-BY-SA)}, langid = {english} } @article{lovelace_open_2021, title = {Open Source Tools for Geographic Analysis in Transport Planning}, author = {Lovelace, Robin}, year = {2021}, month = oct, journal = {Journal of Geographical Systems}, volume = {23}, number = {4}, pages = {547--578}, issn = {1435-5949}, doi = {10.1007/s10109-020-00342-2}, urldate = {2024-10-06}, abstract = {Geographic analysis has long supported transport plans that are appropriate to local contexts. Many incumbent `tools of the trade' are proprietary and were developed to support growth in motor traffic, limiting their utility for transport planners who have been tasked with twenty-first century objectives such as enabling citizen participation, reducing pollution, and increasing levels of physical activity by getting more people walking and cycling. Geographic techniques---such as route analysis, network editing, localised impact assessment and interactive map visualisation---have great potential to support modern transport planning priorities. The aim of this paper is to explore emerging open source tools for geographic analysis in transport planning, with reference to the literature and a review of open source tools that are already being used. A key finding is that a growing number of options exist, challenging the current landscape of proprietary tools. These can be classified as command-line interface, graphical user interface or web-based user interface tools and by the framework in which they were implemented, with numerous tools released as R, Python and JavaScript packages, and QGIS plugins. The review found a diverse and rapidly evolving `ecosystem' tools, with 25 tools that were designed for geographic analysis to support transport planning outlined in terms of their popularity and functionality based on online documentation. They ranged in size from single-purpose tools such as the QGIS plugin AwaP to sophisticated stand-alone multi-modal traffic simulation software such as MATSim, SUMO and Veins. Building on their ability to re-use the most effective components from other open source projects, developers of open source transport planning tools can avoid `reinventing the wheel' and focus on innovation, the `gamified' A/B Street https://github.com/dabreegster/abstreet/\#abstreetsimulation software, based on OpenStreetMap, a case in point. The paper, the source code of which can be found at https://github.com/robinlovelace/open-gat, concludes that, although many of the tools reviewed are still evolving and further research is needed to understand their relative strengths and barriers to uptake, open source tools for geographic analysis in transport planning already hold great potential to help generate the strategic visions of change and evidence that is needed by transport planners in the twenty-first century.}, copyright = {CC0 1.0 Universal Public Domain Dedication}, langid = {english}, keywords = {Artificial Intelligence,C6,Geographic data,Geographic data analysis,Open source,R41,Software,Transport modelling,Transport planning} } @article{lovelace_propensity_2017, title = {The {{Propensity}} to {{Cycle Tool}}: {{An}} Open Source Online System for Sustainable Transport Planning}, shorttitle = {The {{Propensity}} to {{Cycle Tool}}}, author = {Lovelace, Robin and Goodman, Anna and Aldred, Rachel and Berkoff, Nikolai and Abbas, Ali and Woodcock, James}, year = {2017}, month = jan, journal = {Journal of Transport and Land Use}, volume = {10}, number = {1}, issn = {1938-7849}, doi = {10/gfgzf7}, urldate = {2017-06-01}, abstract = {Getting people cycling is an increasingly common objective in transport planning institutions worldwide. A growing evidence base indicates that high quality infrastructure can boost local cycling rates. Yet for infrastructure and other cycling measures to be effective, it is important to intervene in the right places, such as along `desire lines' of high latent demand. This creates the need for tools and methods to help answer the question `where to build?'. Following a brief review of the policy and research context related to this question, this paper describes the design, features and potential applications of such a tool. The Propensity to Cycle Tool (PCT) is an online, interactive planning support system that was initially developed to explore and map cycling potential across England (see www.pct.bike). Based on origin-destination data it models cycling levels at area, desire line, route and route network levels, for current levels of cycling, and for scenario-based `cycling futures.' Four scenarios are presented, including `Go Dutch' and `Ebikes,' which explore what would happen if English people had the same propensity to cycle as Dutch people and the potential impact of electric cycles on cycling uptake. The cost effectiveness of investment depends not only on the number of additional trips cycled, but on wider impacts such as health and carbon benefits. The PCT reports these at area, desire line, and route level for each scenario. The PCT is open source, facilitating the creation of scenarios and deployment in new contexts. We conclude that the PCT illustrates the potential of online tools to inform transport decisions and raises the wider issue of how models should be used in transport planning.}, copyright = {Copyright (c) 2016 Robin Lovelace, Anna Goodman, Rachel Aldred, Nikolai Berkoff, Ali Abbas, James Woodcock}, langid = {english}, keywords = {Cycling,modelling,Participatory,Planning} } @book{lovelace_spatial_2016, title = {Spatial Microsimulation with {{R}}}, author = {Lovelace, Robin and Dumont, Morgane}, year = {2016}, publisher = {CRC Press}, keywords = {nosource} } @book{majure_sgeostat_2016, title = {Sgeostat: {{An Object-Oriented Framework}} for {{Geostatistical Modeling}} in {{S}}+}, author = {Majure, James J. and Gebhardt, Albrecht}, year = {2016}, publisher = {R package}, keywords = {nosource} } @book{maling_coordinate_1992, title = {Coordinate Systems and Map Projections}, author = {Maling, D. H.}, year = {1992}, edition = {Second}, publisher = {Pergamon Press}, address = {Oxford ; New York}, isbn = {978-0-08-037234-1}, lccn = {GA110 .M32 1992}, keywords = {Grids (Cartography),Map projection,nosource} } @book{mccune_analysis_2002, title = {Analysis of Ecological Communities}, author = {McCune, Bruce and Grace, James B. and Urban, Dean L.}, year = {2002}, edition = {Second}, publisher = {MjM Software Design}, address = {Gleneden Beach, Oregon}, isbn = {978-0-9721290-0-8}, langid = {english}, keywords = {nosource}, annotation = {OCLC: 846056595} } @article{meulemans_small_2017, title = {Small {{Multiples}} with {{Gaps}}}, author = {Meulemans, Wouter and Dykes, Jason and Slingsby, Aidan and Turkay, Cagatay and Wood, Jo}, year = {2017}, month = jan, journal = {IEEE Transactions on Visualization and Computer Graphics}, volume = {23}, number = {1}, pages = {381--390}, issn = {1077-2626}, doi = {10/f92gd5}, urldate = {2018-09-02}, abstract = {Small multiples enable comparison by providing different views of a single data set in a dense and aligned manner. A common frame defines each view, which varies based upon values of a conditioning variable. An increasingly popular use of this technique is to project two-dimensional locations into a gridded space (e.g. grid maps), using the underlying distribution both as the conditioning variable and to determine the grid layout. Using whitespace in this layout has the potential to carry information, especially in a geographic context. Yet, the effects of doing so on the spatial properties of the original units are not understood. We explore the design space offered by such small multiples with gaps. We do so by constructing a comprehensive suite of metrics that capture properties of the layout used to arrange the small multiples for comparison (e.g. compactness and alignment) and the preservation of the original data (e.g. distance, topology and shape). We study these metrics in geographic data sets with varying properties and numbers of gaps. We use simulated annealing to optimize for each metric and measure the effects on the others. To explore these effects systematically, we take a new approach, developing a system to visualize this design space using a set of interactive matrices. We find that adding small amounts of whitespace to small multiple arrays improves some of the characteristics of 2D layouts, such as shape, distance and direction. This comes at the cost of other metrics, such as the retention of topology. Effects vary according to the input maps, with degree of variation in size of input regions found to be a factor. Optima exist for particular metrics in many cases, but at different amounts of whitespace for different maps. We suggest multiple metrics be used in optimized layouts, finding topology to be a primary factor in existing manually-crafted solutions, followed by a trade-off between shape and displacement. But the rich range of possible optimized layouts leads us to challenge single-solution thinking; we suggest to consider alternative optimized layouts for small multiples with gaps. Key to our work is the systematic, quantified and visual approach to exploring design spaces when facing a trade-off between many competing criteria---an approach likely to be of value to the analysis of other design spaces.}, langid = {english}, keywords = {nosource} } @article{meyer_improving_2018, title = {Improving Performance of Spatio-Temporal Machine Learning Models Using Forward Feature Selection and Target-Oriented Validation}, author = {Meyer, Hanna and Reudenbach, Christoph and Hengl, Tomislav and Katurji, Marwan and Nauss, Thomas}, year = {2018}, month = mar, journal = {Environmental Modelling \& Software}, volume = {101}, pages = {1--9}, issn = {13648152}, doi = {10/gc2tsg}, urldate = {2018-04-18}, langid = {english}, keywords = {nosource} } @article{miller_tobler_2004, title = {Tobler's First Law and Spatial Analysis}, author = {Miller, Harvey J.}, year = {2004}, journal = {Annals of the Association of American Geographers}, volume = {94}, number = {2}, doi = {10/dh39xr}, abstract = {Discusses Tobler's First Law of (TFL) Geography, that everything is related to everything else, but near things are more related than distant things. Relatonships between two geographic entities; TFL as the core of spatial autocorrelation statistics; Quantitative techniques for analyzing correlation relative to distance or connectivity relationships.}, keywords = {nosource} } @book{moraga_geospatial_2019, title = {Geospatial Health Data: {{Modeling}} and Visualization with {{R-INLA}} and Shiny}, shorttitle = {Geospatial Health Data}, author = {Moraga, Paula}, year = {2019}, publisher = {CRC Press} } @book{moraga_spatial_2023, title = {Spatial {{Statistics}} for {{Data Science}}: {{Theory}} and {{Practice}} with {{R}}}, shorttitle = {Spatial {{Statistics}} for {{Data Science}}}, author = {Moraga, Paula}, year = {2023}, month = dec, edition = {1st}, publisher = {Chapman \& Hall/CRC}, address = {Boca Raton, Florida}, abstract = {Spatial data is crucial to improve decision-making in a wide range of fields including environment, health, ecology, urban planning, economy, and society. Spatial Statistics for Data Science: Theory and Practice with R describes statistical methods, modeling approaches, and visualization techniques to analyze spatial data using R. The book provides a comprehensive overview of the varying types of spatial data, and detailed explanations of the theoretical concepts of spatial statistics, alongside fully reproducible examples which demonstrate how to simulate, describe, and analyze spatial data in various applications. Combining theory and practice, the book includes real-world data science examples such as disease risk mapping, air pollution prediction, species distribution modeling, crime mapping, and real state analyses. The book utilizes publicly available data and offers clear explanations of the R code for importing, manipulating, analyzing, and visualizing data, as well as the interpretation of the results. This ensures contents are easily accessible and fully reproducible for students, researchers, and practitioners.Key Features:Describes R packages for retrieval, manipulation, and visualization of spatial data.Offers a comprehensive overview of spatial statistical methods including spatial autocorrelation, clustering, spatial interpolation, model-based geostatistics, and spatial point processes.Provides detailed explanations on how to fit and interpret Bayesian spatial models using the integrated nested Laplace approximation (INLA) and stochastic partial differential equation (SPDE) approaches.}, isbn = {978-1-03-263351-0}, langid = {english} } @article{moreno-monroy_public_2017, title = {Public Transport and School Location Impacts on Educational Inequalities: {{Insights}} from {{S{\~a}o Paulo}}}, shorttitle = {Public Transport and School Location Impacts on Educational Inequalities}, author = {{Moreno-Monroy}, Ana I. and Lovelace, Robin and Ramos, Frederico R.}, year = {2017}, month = sep, journal = {Journal of Transport Geography}, issn = {0966-6923}, doi = {10/gdkhrz}, urldate = {2017-10-15}, abstract = {In many large Latin American urban areas such as the S{\~a}o Paulo Metropolitan Region (SPMR), growing social and economic inequalities are embedded through high spatial inequality in the provision of state schools and affordable public transport to these schools. This paper sheds light on the transport-education inequality nexus with reference to school accessibility by public transport in the SPMR. To assess school accessibility, we develop an accessibility index which combines information on the spatial distribution of adolescents, the location of existing schools, and the public transport provision serving the school catchment area into a single measure. The index is used to measure school accessibility locally across 633 areas within the SPMR. We use the index to simulate the impact of a policy aiming at increasing the centralisation of public secondary education provision, and find that it negatively affects public transport accessibility for students with the lowest levels of accessibility. These results illustrate how existing inequalities can be amplified by variable accessibility to schools across income groups and geographical space. The research suggests that educational inequality impacts of school agglomeration policies should be considered before centralisation takes place.}, keywords = {Accessibility,Inequality,Latin America,nosource,Public transport,Schools} } @article{morgan_opentripplanner_2019, title = {{{OpenTripPlanner}} for {{R}}}, author = {Morgan, Malcolm and Young, Marcus and Lovelace, Robin and Hama, Layik}, year = {2019}, month = dec, journal = {Journal of Open Source Software}, volume = {4}, number = {44}, pages = {1926}, issn = {2475-9066}, doi = {10/gkb5nh}, urldate = {2020-01-29}, abstract = {Morgan et al., (2019). OpenTripPlanner for R. Journal of Open Source Software, 4(44), 1926, https://doi.org/10.21105/joss.01926}, langid = {english}, annotation = {ZSCC: 0000000} } @article{morgan_travel_2020, ids = {morgan_travel_2020a}, title = {Travel Flow Aggregation: Nationally Scalable Methods for Interactive and Online Visualisation of Transport Behaviour at the Road Network Level}, author = {Morgan, Malcolm and Lovelace, Robin}, year = {2020}, journal = {Environment \& Planning B: Planning \& Design}, volume = {48}, number = {6}, publisher = {SAGE PublicationsSage UK: London, England}, doi = {10/gh6gb5}, copyright = {CC0 1.0 Universal Public Domain Dedication} } @book{morganwall_rayshader_2021, title = {Rayshader: {{Create}} Maps and Visualize Data in {{2D}} and {{3D}}}, author = {{Morgan-Wall}, Tyler}, year = {2021}, publisher = {R package} } @article{muenchow_geomorphic_2012, title = {Geomorphic Process Rates of Landslides along a Humidity Gradient in the Tropical {{Andes}}}, author = {Muenchow, Jannes and Brenning, Alexander and Richter, Michael}, year = {2012}, month = feb, journal = {Geomorphology}, volume = {139--140}, pages = {271--284}, issn = {0169555X}, doi = {10/dp554q}, urldate = {2017-06-23}, langid = {english}, keywords = {nosource} } @article{muenchow_predictive_2013, title = {Predictive Mapping of Species Richness and Plant Species' Distributions of a {{Peruvian}} Fog Oasis along an Altitudinal Gradient}, author = {Muenchow, Jannes and Br{\"a}uning, Achim and Rodr{\'i}guez, Eric Frank and {von Wehrden}, Henrik}, year = {2013}, month = sep, journal = {Biotropica}, volume = {45}, number = {5}, pages = {557--566}, issn = {1744-7429}, doi = {10/f49j56}, urldate = {2017-08-28}, abstract = {Tropical arid to semi-arid ecosystems are nearly as diverse as more humid forests and occupy large parts of the tropics. In comparison, however, they are vastly understudied. For instance, fog precipitation alone supports a unique vegetation formation, locally termed lomas, on coastal mountains in the Peruvian desert. To effectively protect these highly endemic and threatened ecosystems, we must increase our understanding of their diversity patterns in relation to environmental factors. Consequently, we recorded all vascular species from 100 random 4~{\texttimes}~4~m plots on the fog-exposed southern slope of the mountain Mong{\'o}n. We used topographic and remotely sensed covariates in statistical models to generate spatial predictions of alpha diversity and plant species' distribution probabilities. Altitude was the most important predictor in all models and may represent fog moisture levels. Other significant covariates in the models most likely refer also to water availability but on a finer spatial scale. Additionally, model-based clustering revealed five altitudinal vegetation zones. This study contributes to a better spatial understanding of the biodiversity and spatial arrangement of vegetation belts of the largely unknown but highly unique lomas formations. Furthermore, mapping species richness and plant species' distributions could support a long-needed lomas strategic conservation scheme.}, langid = {english}, keywords = {biodiversity conservation,climatic gradient,El Nino Southern Oscillation (ENSO),La Nina,lomas,nosource,species distribution models,species richness model,tropical plant diversity} } @article{muenchow_review_2018, title = {A Review of Ecological Gradient Research in the {{Tropics}}: Identifying Research Gaps, Future Directions, and Conservation Priorities}, shorttitle = {A Review of Ecological Gradient Research in the {{Tropics}}}, author = {Muenchow, Jannes and Dieker, Petra and Kluge, J{\"u}rgen and Kessler, Michael and {von Wehrden}, Henrik}, year = {2018}, journal = {Biodiversity and Conservation}, volume = {27}, number = {2}, pages = {273--285}, issn = {0960-3115, 1572-9710}, doi = {10/gcthf9}, urldate = {2017-11-23}, langid = {english}, keywords = {nosource} } @article{muenchow_rqgis:_2017, title = {{{RQGIS}}: {{Integrating R}} with {{QGIS}} for Statistical Geocomputing}, author = {Muenchow, Jannes and Schratz, Patrick and Brenning, Alexander}, year = {2017}, journal = {The R Journal}, volume = {9}, number = {2}, pages = {409--428}, doi = {10/gf3d48}, keywords = {nosource} } @article{muenchow_soil_2013, title = {Soil Texture and Altitude, Respectively, Largely Determine the Floristic Gradient of the Most Diverse Fog Oasis in the {{Peruvian}} Desert}, author = {Muenchow, Jannes and Hauenstein, Simon and Br{\"a}uning, Achim and B{\"a}umler, Rupert and Rodr{\'i}guez, Eric Frank and {von Wehrden}, Henrik}, year = {2013}, month = sep, journal = {Journal of Tropical Ecology}, volume = {29}, number = {05}, pages = {427--438}, issn = {0266-4674, 1469-7831}, doi = {10/f5b5v7}, urldate = {2017-09-21}, langid = {english}, keywords = {nosource} } @book{murrell_r_2016, title = {R {{Graphics}}}, author = {Murrell, Paul}, year = {2016}, month = apr, edition = {Second}, publisher = {CRC Press}, abstract = {Extensively updated to reflect the evolution of statistics and computing, the second edition of the bestselling R Graphics comes complete with new packages and new examples. Paul Murrell, widely known as the leading expert on R graphics, has developed an in-depth resource that helps both neophyte and seasoned users master the intricacies of R graphics. New in the Second Edition Updated information on the core graphics engine, the traditional graphics system, the grid graphics system, and the lattice package A new chapter on the ggplot2 package New chapters on applications and extensions of R Graphics, including geographic maps, dynamic and interactive graphics, and node-and-edge graphs Organized into five parts, R Graphics covers both "traditional" and newer, R-specific graphics systems. The book reviews the graphics facilities of the R language and describes R's powerful grid graphics system. It then covers the graphics engine, which represents a common set of fundamental graphics facilities, and provides a series of brief overviews of the major areas of application for R graphics and the major extensions of R graphics.}, isbn = {978-1-4398-3177-9}, langid = {english}, keywords = {Computers / Computer Graphics,Computers / General,Mathematics / Probability & Statistics / General} } @book{neteler_open_2008, title = {Open Source {{GIS}}: A {{GRASS GIS}} Approach}, shorttitle = {Open Source {{GIS}}}, author = {Neteler, Markus and Mitasova, Helena}, year = {2008}, edition = {Third}, publisher = {Springer}, address = {New York}, isbn = {978-0-387-35767-6 978-0-387-68574-8}, langid = {english}, keywords = {Analyse,Computerkartographie,Geographic information systems,Geoinformationssystem,GIS,GRASS,GRASS (Electronic computer system),Open source,Open source software,Programm,Programmierung,Raster,Software,Vektor,Visualisierung}, annotation = {OCLC: 255568974} } @book{nolan_xml_2014, title = {{{XML}} and Web Technologies for Data Sciences with {{R}}}, author = {Nolan, Deborah and Lang, Duncan Temple}, year = {2014}, series = {Use {{R}}!}, publisher = {Springer}, address = {New York}, abstract = {Web technologies are increasingly relevant to scientists working with data, for both accessing data and creating rich dynamic and interactive displays. The XML and JSON data formats are widely used in Web services, regular Web pages and JavaScript code, and visualization formats such as SVG and KML for Google Earth and Google Maps. In addition, scientists use HTTP and other network protocols to scrape data from Web pages, access REST and SOAP Web Services, and interact with NoSQL databases and text search applications. This book provides a practical hands-on introduction to these technologies, including high-level functions the authors have developed for data scientists. It describes strategies and approaches for extracting data from HTML, XML, and JSON formats and how to programmatically access data from the Web. Along with these general skills, the authors illustrate several applications that are relevant to data scientists, such as reading and writing spreadsheet documents both locally and via GoogleDocs, creating interactive and dynamic visualizations, displaying spatial-temporal displays with Google Earth, and generating code from descriptions of data structures to read and write data. These topics demonstrate the rich possibilities and opportunities to do new things with these modern technologies. The book contains many examples and case-studies that readers can use directly and adapt to their own work}, isbn = {978-1-4614-7900-0 978-1-4614-7899-7}, langid = {english}, keywords = {nosource}, annotation = {OCLC: 841520665} } @article{nowosad_extended_2022, title = {Extended {{SLIC}} Superpixels Algorithm for Applications to Non-Imagery Geospatial Rasters}, author = {Nowosad, Jakub and Stepinski, Tomasz F.}, year = {2022}, month = aug, journal = {International Journal of Applied Earth Observation and Geoinformation}, volume = {112}, pages = {102935}, issn = {15698432}, doi = {10.1016/j.jag.2022.102935}, urldate = {2022-08-04}, abstract = {Converting an image to a set of superpixels is a useful preprocessing step in many computer vision applications; it reduces the dimensionality of the data and removes noise. The most popular superpixels algorithm is the Simple Linear Iterative Clustering (SLIC). To use original SLIC with non-imagery data (for example, rasters of discrete probability distributions, time-series, or matrices describing local texture or pattern), the data needs to be converted to the false-color RGB image constructed from the first three principal components. Here we propose to extend the SLIC algorithm so it can work with non-imagery data structures without data reduction and conversion to the false-color image. The modification allows for using a data distance measure most appropriate to a particular data structure and for using a custom function for averaging values of clusters centers. Comparisons between the extended and original SLIC algorithms in three different mapping tasks are presented and discussed. The results show that the extended SLIC improves the accuracy of the final products in reverse proportion to the percentage of variability explained by the three-dimensional (RGB) approximation to multidimensional non-imagery data. Thus, the largest advantage of using the modified SLIC can be expected in applications to data that cannot be compressed to three dimensions without a significant departure from its original variability.}, copyright = {Creative Commons Attribution 4.0 International License (CC-BY)}, langid = {english} } @book{obe_postgis_2015, title = {{{PostGIS}} in Action}, author = {Obe, Regina O. and Hsu, Leo S.}, year = {2015}, edition = {Second}, publisher = {Manning}, address = {Shelter Island, NY}, abstract = {"PostGIS in Action, Second Edition teaches you to solve real-world goedata problems. It first gives you a background in vector-, raster-, and topology-based GIS and then quickly moves into analyzing, viewing, and mapping data. You'll learn how to optimize queries for maximum speed, simplify geometrics for greater efficiency, and create custom functions for your own applications. You'll also learn how to apply your existing GIS knowledge to PostGIS and integrate with other GIS tools. What's Inside: An introduction to spatial databases -- geometry, geography, raster, and topology spatial types, functions, and queries -- Applying PostGIS to real-world problems -- Extending PostGIS to web and desktop applications -- Updated for PostGIS 2.x and PostgreSQL 9.x"--Back cover}, isbn = {978-1-61729-139-5}, lccn = {G70.212 .O23 2015}, keywords = {Database searching,Geographic information systems,nosource}, annotation = {OCLC: ocn872985108} } @article{obrien_interactive_2016, title = {Interactive Mapping for Large, Open Demographic Data Sets Using Familiar Geographical Features}, author = {O'Brien, Oliver and Cheshire, James}, year = {2016}, month = aug, journal = {Journal of Maps}, volume = {12}, number = {4}, pages = {676--683}, issn = {null}, doi = {10/gkb5fm}, urldate = {2017-05-22}, abstract = {Ever-increasing numbers of large demographic data sets are becoming available. Many of these data sets are provided as open data, but are in basic repositories where it is incumbent on the user to generate their own visualisations and analysis in order to garner insights. In a bid to facilitate the use and exploration of such data sets, we have created a web mapping platform called DataShine. We link data from the 2011 Census for England and Wales with open geographical data to demonstrate the power and utility of creating a conventional map and combining it with a simple but flexible interface and a highly detailed demographic data set.}, keywords = {census,Census,choropleth,DataShine,interactive,nosource,Open data,population,Population} } @misc{office_for_national_statistics_workplace_2014, title = {Workplace {{Zones}}: {{A}} New Geography for Workplace Statistics - {{Datasets}}}, author = {{Office for National Statistics}}, year = {2014}, urldate = {2018-01-13}, howpublished = {https://data.gov.uk/dataset/workplace-zones-a-new-geography-for-workplace-statistics3}, keywords = {nosource} } @techreport{opengeospatialconsortium_wellknown_2019, type = {Implementation {{Standard}}}, title = {Well-Known Text Representation of Coordinate Reference Systems}, author = {{Open Geospatial Consortium}}, year = {2019}, number = {18-010r7}, institution = {Open Geospatial Consortium}, urldate = {2022-01-22}, copyright = {Copyright {\copyright} 2019 Open Geospatial Consortium To obtain additional rights of use, visit http://www.opengeospatial.org/legal/.}, langid = {english} } @book{openshaw_geocomputation_2000, title = {Geocomputation}, editor = {Openshaw, Stan and Abrahart, Robert J.}, year = {2000}, month = may, publisher = {CRC Press}, address = {London; New York}, abstract = {Geocomputation is essentially the follow-on revolution from Geographic Information Science and is expected to gather speed and momentum in the first decade of the 21st century. It comes into use once a GIS database has been set up, with a digital data library, and expanded and linked to a global geographical two or three dimensional co-ordinate system. It exploits developments in IT and new data gathering and earth observing technologies, and takes the notion of GIS beyond data and towards its analysis, modelling, and use in problem solving. This book provides pointers on how to harness these technologies in tandem and in the context of multiple different subjects and problem areas. It seeks to establish the principles and set the foundations for subsequent growth.L}, isbn = {978-0-7484-0900-6}, langid = {english} } @book{orourke_computational_1998, title = {Computational {{Geometry}} in {{C}}}, author = {O'Rourke, Joseph}, year = {1998}, month = oct, edition = {Second}, publisher = {Cambridge University Press}, address = {Cambridge, UK; New York}, abstract = {This is the newly revised and expanded edition of the popular introduction to the design and implementation of geometry algorithms arising in areas such as computer graphics, robotics, and engineering design. The second edition contains material on several new topics, such as randomized algorithms for polygon triangulation, planar point location, 3D convex hull construction, intersection algorithms for ray-segment and ray-triangle, and point-in-polyhedron. A new "Sources" chapter points to supplemental literature for readers needing more information on any topic. A novel aspect is the inclusion of working C code for many of the algorithms, with discussion of practical implementation issues. The self-contained treatment presumes only an elementary knowledge of mathematics, but reaches topics on the frontier of current research, making it a useful reference for practitioners at all levels. The code in this new edition is significantly improved from the first edition, and four new routines are included. Java versions for this new edition are also available. All code is accessible from the book's Web site (http://cs.smith.edu/{\textasciitilde}orourke/) or by anonymous ftp.}, isbn = {978-0-521-64976-6}, langid = {english} } @article{pebesma_classes_2005, title = {Classes and Methods for Spatial Data in {{R}}}, author = {Pebesma, Edzer and Bivand, Roger}, year = {2005}, journal = {R news}, volume = {5}, number = {2}, pages = {9--13}, keywords = {No DOI found,nosource} } @article{pebesma_measurement_2016, title = {Measurement {{Units}} in {{R}}}, author = {Pebesma, Edzer and Mailund, Thomas and Hiebert, James}, year = {2016}, month = dec, journal = {The R Journal}, volume = {8}, number = {2}, pages = {486--494}, doi = {10/gkb5pd}, keywords = {nosource} } @article{pebesma_r_2012, title = {The {{R}} Software Environment in Reproducible Geoscientific Research}, author = {Pebesma, Edzer and N{\"u}st, Daniel and Bivand, Roger}, year = {2012}, month = apr, journal = {Eos, Transactions American Geophysical Union}, volume = {93}, number = {16}, pages = {163--163}, issn = {2324-9250}, doi = {10/gd8djc}, urldate = {2017-10-25}, abstract = {Reproducibility is an important aspect of scientific research, because the credibility of science is at stake when research is not reproducible. Like science, the development of good, reliable scientific software is a social process. A mature and growing community relies on the R software environment for carrying out geoscientific research. Here we describe why people use R and how it helps in communicating and reproducing research.}, langid = {english}, keywords = {0520 Data analysis: algorithms and implementation,0530 Data presentation and visualization,1694 Instruments and techniques,1819 Hydrology: Geographic Information Systems (GIS),1978 Software re-use,nosource,R project,reproducible research} } @article{pebesma_simple_2018, ids = {pebesma_simple_2018-1}, title = {Simple Features for {{R}}: {{Standardized}} Support for Spatial Vector Data}, author = {Pebesma, Edzer}, year = {2018}, journal = {The R Journal}, volume = {10}, number = {1}, doi = {10/gf2ztt}, keywords = {nosource} } @book{pebesma_spatial_2022, title = {Spatial {{Data Science}} with Applications in {{R}}}, author = {Pebesma, Edzer and Bivand, Roger}, year = {2023} } @book{pebesma_spatial_2023, title = {Spatial {{Data Science}}: {{With Applications}} in {{R}}}, shorttitle = {Spatial {{Data Science}}}, author = {Pebesma, Edzer and Bivand, Roger}, year = {2023}, publisher = {CRC Press} } @book{pebesma_stars_2021, title = {\{stars\}: {{Spatiotemporal}} Arrays, Raster and Vector Data Cubes}, author = {Pebesma, Edzer}, year = {2021}, publisher = {R package} } @book{pedersen_gganimate_2020, title = {Gganimate: {{A}} Grammar of Animated Graphics}, author = {Pedersen, Thomas Lin and Robinson, David}, year = {2020}, publisher = {R package} } @article{pereira_r5r_2021, title = {R5r: {{Rapid Realistic Routing}} on {{Multimodal Transport Networks}} with {{R}}{\textsuperscript{5}} in {{R}}}, shorttitle = {R5r}, author = {Pereira, Rafael H. M. and Saraiva, Marcus and Herszenhut, Daniel and Braga, Carlos Kaue Vieira and Conway, Matthew Wigginton}, year = {2021}, month = mar, journal = {Findings}, pages = {21262}, publisher = {Network Design Lab}, doi = {10.32866/001c.21262}, urldate = {2021-03-30}, abstract = {Routing is a key step in transport planning and research. Nonetheless, researchers and practitioners often face challenges when performing this task due to long computation times and the cost of licensed software. R{\textasciicircum}5{\textasciicircum} is a multimodal transport network router that offers multiple routing features, such as calculating travel times over a time window and returning multiple itineraries for origin/destination pairs. This paper describes r5r, an open-source R package that leverages R{\textasciicircum}5{\textasciicircum} to efficiently compute travel time matrices and generate detailed itineraries between sets of origins and destinations at no expense using seamless parallel computing.}, langid = {english} } @book{perpinan_rastervis_2016, title = {{{rasterVis}}}, author = {Perpi{\~n}{\'a}n, Oscar and Hijmans, Robert}, year = {2016}, keywords = {nosource} } @article{pezanowski_senseplace3_2018, title = {{{SensePlace3}}: A Geovisual Framework to Analyze Place--Time--Attribute Information in Social Media}, shorttitle = {{{SensePlace3}}}, author = {Pezanowski, Scott and MacEachren, Alan M and Savelyev, Alexander and Robinson, Anthony C}, year = {2018}, month = sep, journal = {Cartography and Geographic Information Science}, volume = {45}, number = {5}, pages = {420--437}, issn = {1523-0406, 1545-0465}, doi = {10/gc95n9}, urldate = {2018-09-30}, abstract = {SensePlace3 (SP3) is a geovisual analytics framework and web application that supports overview + detail analysis of social media, focusing on extracting meaningful information from the Twitterverse. SP3 leverages social media related to crisis events. It differs from most existing systems by enabling an analyst to obtain place-relevant information from tweets that have implicit as well as explicit geography. Specifically, SP3 includes not just the ability to utilize the explicit geography of geolocated tweets but also analyze implicit geography by recognizing and geolocating references in both tweet text, which indicates locations tweeted about, and in Twitter profiles, which indicates locations affiliated with users. Key features of SP3 reported here include flexible search and filtering capabilities to support information foraging; an ingest, processing, and indexing pipeline that produces near real-time access for big streaming data; and a novel strategy for implementing a web-based multi-view visual interface with dynamic linking of entities across views. The SP3 system architecture was designed to support crisis management applications, but its design flexibility makes it easily adaptable to other domains. We also report on a user study that provided input to SP3 interface design and suggests next steps for effective spatiotemporal analytics using social media sources.}, langid = {english}, keywords = {nosource} } @article{probst_hyperparameters_2018, title = {Hyperparameters and {{Tuning Strategies}} for {{Random Forest}}}, author = {Probst, Philipp and Wright, Marvin and Boulesteix, Anne-Laure}, year = {2018}, month = apr, journal = {arXiv:1804.03515 [cs, stat]}, eprint = {1804.03515}, primaryclass = {cs, stat}, urldate = {2018-08-02}, abstract = {The random forest algorithm (RF) has several hyperparameters that have to be set by the user, e.g., the number of observations drawn randomly for each tree and whether they are drawn with or without replacement, the number of variables drawn randomly for each split, the splitting rule, the minimum number of samples that a node must contain and the number of trees. In this paper, we first provide a literature review on the parameters' influence on the prediction performance and on variable importance measures, also considering interactions between hyperparameters. It is well known that in most cases RF works reasonably well with the default values of the hyperparameters specified in software packages. Nevertheless, tuning the hyperparameters can improve the performance of RF. In the second part of this paper, after a brief overview of tuning strategies we demonstrate the application of one of the most established tuning strategies, model-based optimization (MBO). To make it easier to use, we provide the tuneRanger R package that tunes RF with MBO automatically. In a benchmark study on several datasets, we compare the prediction performance and runtime of tuneRanger with other tuning implementations in R and RF with default hyperparameters.}, archiveprefix = {arXiv}, keywords = {Computer Science - Machine Learning,No DOI found,nosource,Statistics - Machine Learning} } @article{qiu_development_2012, title = {The {{Development}} of an {{Areal Interpolation ArcGIS Extension}} and a {{Comparative Study}}}, author = {Qiu, Fang and Zhang, Caiyun and Zhou, Yuhong}, year = {2012}, month = sep, journal = {GIScience \& Remote Sensing}, volume = {49}, number = {5}, pages = {644--663}, issn = {1548-1603}, doi = {10/gkb5fn}, urldate = {2017-08-07}, keywords = {nosource} } @book{rcoreteam_introduction_2021, title = {An {{Introduction}} to {{R}}}, author = {{R Core Team}}, year = {2021}, abstract = {An Introduction to R is based on the former `Notes on R', gives an introduction to the language and how to use R for doing statistical analysis and graphics.}, keywords = {nosource} } @book{ribeirojr._geor_2016, title = {{{geoR}}: {{Analysis}} of {{Geostatistical Data}}}, author = {Ribeiro Jr., Paulo J. and Diggle, Peter J.}, year = {2016}, publisher = {R package}, keywords = {nosource} } @article{ripley_spatial_2001, title = {Spatial {{Statistics}} in {{R}}}, author = {Ripley, Brian D}, year = {2001}, journal = {R News}, volume = {1}, number = {2}, pages = {14--15}, keywords = {No DOI found,nosource} } @book{rodrigue_geography_2013, title = {The {{Geography}} of {{Transport Systems}}}, author = {Rodrigue, Jean-Paul and Comtois, Claude and Slack, Brian}, year = {2013}, month = jun, edition = {Third}, publisher = {Routledge}, address = {London, New York}, isbn = {978-0-415-82254-1}, langid = {english} } @article{Roussel2020, title = {{{lidR}}: {{An}} r Package for Analysis of Airborne Laser Scanning ({{ALS}}) Data}, author = {Roussel, Jean-Romain and Auty, David and Coops, Nicholas C. and Tompalski, Piotr and Goodbody, Tristan R.H. and Meador, Andrew S{\'a}nchez and Bourdon, Jean-Fran{\c c}ois and {de Boissieu}, Florian and Achim, Alexis}, year = {2020}, month = dec, journal = {Remote Sensing of Environment}, volume = {251}, pages = {112061}, publisher = {Elsevier BV}, doi = {10/ghddxb} } @inproceedings{rowlingson_rasp:_2003, title = {Rasp: {{A Package}} for {{Spatial Statistics}}}, booktitle = {Proceedings of the 3rd {{International Workshop}} on {{Distributed Statistical Computing}}}, author = {Rowlingson, Barry and Baddeley, Adrian and Turner, Rolf and Diggle, Peter}, editor = {Hornik, Kurt}, year = {2003}, editors = {Kurt Hornik and Friedrich Leisch and Achim Zeileis}, keywords = {No DOI found,nosource} } @article{rowlingson_splancs_1993, title = {Splancs: {{Spatial}} Point Pattern Analysis Code in {{S-plus}}}, shorttitle = {Splancs}, author = {Rowlingson, B. S and Diggle, P. J}, year = {1993}, month = may, journal = {Computers \& Geosciences}, volume = {19}, number = {5}, pages = {627--655}, issn = {0098-3004}, doi = {10/dvzffd}, urldate = {2017-07-20}, abstract = {In recent years, Geographical Information Systems have provided researchers in many fields with facilities for mapping and analyzing spatially referenced data. Commercial systems have excellent facilities for database handling and a range of spatial operations. However, none can claim to be a rich environment for statistical analysis of spatial data. We have made some powerful enhancements to the S-Plus system to produce a tool for display and analysis of spatial point pattern data. In this paper we give a brief introduction to the S-Plus system and a detailed description of the S-Plus enhancements. We then present three worked examples: two from geomorphology and one from epidemiology.}, keywords = {Epidemiology,Geographical Information Systems,Geomorphology,nosource,Software,Spatial statistics} } @book{rowlingson_splancs_2017, title = {Splancs: {{Spatial}} and {{Space-Time Point Pattern Analysis}}}, author = {Rowlingson, Barry and Diggle, Peter}, year = {2017}, publisher = {R package}, keywords = {nosource} } @article{rs13132428, title = {Satellite Image Time Series Analysis for Big Earth Observation Data}, author = {Simoes, Rolf and Camara, Gilberto and Queiroz, Gilberto and Souza, Felipe and Andrade, Pedro R. and Santos, Lorena and Carvalho, Alexandre and Ferreira, Karine}, year = {2021}, journal = {Remote Sensing}, volume = {13}, number = {13}, issn = {2072-4292}, doi = {10.3390/rs13132428}, abstract = {The development of analytical software for big Earth observation data faces several challenges. Designers need to balance between conflicting factors. Solutions that are efficient for specific hardware architectures can not be used in other environments. Packages that work on generic hardware and open standards will not have the same performance as dedicated solutions. Software that assumes that its users are computer programmers are flexible but may be difficult to learn for a wide audience. This paper describes sits, an open-source R package for satellite image time series analysis using machine learning. To allow experts to use satellite imagery to the fullest extent, sits adopts a time-first, space-later approach. It supports the complete cycle of data analysis for land classification. Its API provides a simple but powerful set of functions. The software works in different cloud computing environments. Satellite image time series are input to machine learning classifiers, and the results are post-processed using spatial smoothing. Since machine learning methods need accurate training data, sits includes methods for quality assessment of training samples. The software also provides methods for validation and accuracy measurement. The package thus comprises a production environment for big EO data analysis. We show that this approach produces high accuracy for land use and land cover maps through a case study in the Cerrado biome, one of the world's fast moving agricultural frontiers for the year 2018.}, article-number = {2428} } @article{savric_projection_2016, title = {Projection {{Wizard}} -- {{An Online Map Projection Selection Tool}}}, author = {{\v S}avri{\v c}, Bojan and Jenny, Bernhard and Jenny, Helen}, year = {2016}, journal = {The Cartographic Journal}, volume = {53}, number = {2}, pages = {177--185}, doi = {10/ggsx6z}, keywords = {nosource} } @article{schramm_openeo_2021, title = {The Openeo Api--Harmonising the Use of Earth Observation Cloud Services Using Virtual Data Cube Functionalities}, author = {Schramm, Matthias and Pebesma, Edzer and Milenkovi{\'c}, Milutin and Foresta, Luca and Dries, Jeroen and Jacob, Alexander and Wagner, Wolfgang and Mohr, Matthias and Neteler, Markus and Kadunc, Miha and others}, year = {2021}, journal = {Remote Sensing}, volume = {13}, number = {6}, pages = {1125}, publisher = {Multidisciplinary Digital Publishing Institute}, doi = {10.3390/rs13061125} } @article{schratz_hyperparameter_2019, title = {Hyperparameter Tuning and Performance Assessment of Statistical and Machine-Learning Algorithms Using Spatial Data}, author = {Schratz, Patrick and Muenchow, Jannes and Iturritxa, Eugenia and Richter, Jakob and Brenning, Alexander}, year = {2019}, month = aug, journal = {Ecological Modelling}, volume = {406}, pages = {109--120}, issn = {0304-3800}, doi = {10.1016/j.ecolmodel.2019.06.002}, urldate = {2022-02-23}, abstract = {While the application of machine-learning algorithms has been highly simplified in the last years due to their well-documented integration in commonly used statistical programming languages (such as R or Python), there are several practical challenges in the field of ecological modeling related to unbiased performance estimation. One is the influence of spatial autocorrelation in both hyperparameter tuning and performance estimation. Grouped cross-validation strategies have been proposed in recent years in environmental as well as medical contexts to reduce bias in predictive performance. In this study we show the effects of spatial autocorrelation on hyperparameter tuning and performance estimation by comparing several widely used machine-learning algorithms such as boosted regression trees (BRT), k-nearest neighbor (KNN), random forest (RF) and support vector machine (SVM) with traditional parametric algorithms such as logistic regression (GLM) and semi-parametric ones like generalized additive models (GAM) in terms of predictive performance. Spatial and non-spatial cross-validation methods were used to evaluate model performances aiming to obtain bias-reduced performance estimates. A detailed analysis on the sensitivity of hyperparameter tuning when using different resampling methods (spatial/non-spatial) was performed. As a case study the spatial distribution of forest disease (Diplodia sapinea) in the Basque Country (Spain) was investigated using common environmental variables such as temperature, precipitation, soil and lithology as predictors. Random Forest (mean Brier score estimate of 0.166) outperformed all other methods with regard to predictive accuracy. Though the sensitivity to hyperparameter tuning differed between the ML algorithms, there were in most cases no substantial differences between spatial and non-spatial partitioning for hyperparameter tuning. However, spatial hyperparameter tuning maintains consistency with spatial estimation of classifier performance and should be favored over non-spatial hyperparameter optimization. High performance differences (up to 47\%) between the bias-reduced (spatial cross-validation) and overoptimistic (non-spatial cross-validation) cross-validation settings showed the high need to account for the influence of spatial autocorrelation. Overoptimistic performance estimates may lead to false actions in ecological decision making based on biased model predictions.}, langid = {english}, keywords = {Hyperparameter tuning,Machine-learning,Spatial autocorrelation,Spatial cross-validation,Spatial modeling} } @article{schratz_mlr3spatiotempcv_2021, title = {Mlr3spatiotempcv: {{Spatiotemporal}} Resampling Methods for Machine Learning in {{R}}}, shorttitle = {Mlr3spatiotempcv}, author = {Schratz, Patrick and Becker, Marc and Lang, Michel and Brenning, Alexander}, year = {2021}, journal = {arXiv preprint arXiv:2110.12674}, eprint = {2110.12674}, archiveprefix = {arXiv}, keywords = {No DOI found} } @article{schratz_performance_nodate, title = {Performance Evaluation and Hyperparameter Tuning of Statistical and Machine-Learning Models Using Spatial Data}, author = {Schratz, Patrick and Muenchow, J. and Iturritxa, Eugenia and Richter, Jakob and Brenning, A.}, year = {2018}, keywords = {Computer Science - Machine Learning,No DOI found,nosource,Statistics - Machine Learning,Statistics - Methodology} } @article{shen_classification_2018, title = {Classification of Topological Relations between Spatial Objects in Two-Dimensional Space within the Dimensionally Extended 9-Intersection Model}, author = {Shen, Jingwei and Chen, Min and Liu, Xintao}, year = {2018}, journal = {Transactions in GIS}, volume = {22}, number = {2}, pages = {514--541}, issn = {1467-9671}, doi = {10/gnhcx9}, urldate = {2021-11-13}, abstract = {As an important topological relation model, the dimensionally extended 9-intersection model (DE-9IM) has been widely used as a basis for standards of queries in spatial databases. However, the negative conditions for the specification of the topological relations within the DE-9IM have not been studied. The specification of the topological relations is closely related to the definition of the spatial objects and the topological relation models. The interior, boundary, and exterior of the spatial objects, including the point, line, and region, are defined. Within the framework of the DE-9IM, 43 negative conditions are proposed to eliminate impossible topological relations. Configurations of region/region, region/line, line/line, region/point, line/point, and point/point relations are drawn. The mutual exclusion of the negative conditions is discussed, and the topological relations within the framework of 9IM and DE-9IM are compared. The results show that: (1) impossible topological relations between spatial objects can be eliminated by the application of 43 negative conditions; and (2) 12 relations between two regions, 31 relations between a region and a line, 47 relations between two lines, three relations between a region and a point, three relations between a line and a point, and two relations between two points can be distinguished by the DE-9IM.}, langid = {english} } @book{sherman_desktop_2008, title = {Desktop {{GIS}}: {{Mapping}} the {{Planet}} with {{Open Source Tools}}}, author = {Sherman, Gary}, year = {2008}, publisher = {Pragmatic Bookshelf}, keywords = {nosource} } @inproceedings{simoes_rstac_2021, title = {Rstac: {{An R}} Package to Access Spatiotemporal Asset Catalog Satellite Imagery}, booktitle = {2021 {{IEEE}} International Geoscience and Remote Sensing Symposium {{IGARSS}}}, author = {Simoes, Rolf and Souza, Felipe and Zaglia, Matheus and Queiroz, Gilberto Ribeiro and Santos, Rafael and Ferreira, Karine}, year = {2021}, pages = {7674--7677}, doi = {10.1109/IGARSS47720.2021.9553518} } @article{sorensen_calculation_2006, title = {On the Calculation of the Topographic Wetness Index: Evaluation of Different Methods Based on Field Observations}, author = {S{\o}rensen, R and Zinko, U and Seibert, J}, year = {2006}, journal = {Hydrology and Earth System Sciences}, pages = {13}, doi = {10.5194/hess-10-101-2006}, abstract = {The topographic wetness index (TWI, ln(a/tan{$\beta$})), which combines local upslope contributing area and slope, is commonly used to quantify topographic control on hydrological processes. Methods of computing this index differ primarily in the way the upslope contributing area is calculated. In this study we compared a number of calculation methods for TWI and evaluated them in terms of their correlation with the following measured variables: vascular plant species richness, soil pH, groundwater level, soil moisture, and a constructed wetness degree. The TWI was calculated by varying six parameters affecting the distribution of accumulated area among downslope cells and by varying the way the slope was calculated. All possible combinations of these parameters were calculated for two separate boreal forest sites in northern Sweden. We did not find a calculation method that performed best for all measured variables; rather the best methods seemed to be variable and site specific. However, we were able to identify some general characteristics of the best methods for different groups of measured variables. The results provide guiding principles for choosing the best method for estimating species richness, soil pH, groundwater level, and soil moisture by the TWI derived from digital elevation models.}, langid = {english} } @book{spanier_algebraic_1995, title = {Algebraic Topology}, author = {Spanier, Edwin Henry}, year = {1995}, edition = {1st}, publisher = {Springer}, isbn = {978-0-387-94426-5 978-3-540-90646-9 978-0-387-90646-1} } @book{talbert_ancient_2014, title = {Ancient {{Perspectives}}: {{Maps}} and {{Their Place}} in {{Mesopotamia}}, {{Egypt}}, {{Greece}}, and {{Rome}}}, shorttitle = {Ancient {{Perspectives}}}, author = {Talbert, Richard J. A.}, year = {2014}, month = feb, publisher = {University of Chicago Press}, abstract = {Ancient Perspectives encompasses a vast arc of space and time---Western Asia to North Africa and Europe from the third millennium BCE to the fifth century CE---to explore mapmaking and worldviews in the ancient civilizations of Mesopotamia, Egypt, Greece, and Rome. In each society, maps served as critical economic, political, and personal tools, but there was little consistency in how and why they were made. Much like today, maps in antiquity meant very different things to different people. Ancient Perspectives presents an ambitious, fresh overview of cartography and its uses. The seven chapters range from broad-based analyses of mapping in Mesopotamia and Egypt to a close focus on Ptolemy's ideas for drawing a world map based on the theories of his Greek predecessors at Alexandria. The remarkable accuracy of Mesopotamian city-plans is revealed, as is the creation of maps by Romans to support the proud claim that their emperor's rule was global in its reach. By probing the instruments and techniques of both Greek and Roman surveyors, one chapter seeks to uncover how their extraordinary planning of roads, aqueducts, and tunnels was achieved. Even though none of these civilizations devised the means to measure time or distance with precision, they still conceptualized their surroundings, natural and man-made, near and far, and felt the urge to record them by inventive means that this absorbing volume reinterprets and compares.}, googlebooks = {srTbAgAAQBAJ}, isbn = {978-0-226-78940-8}, langid = {english}, keywords = {History / Ancient / Egypt,History / Ancient / Greece,History / Ancient / Rome,History / Asia / Central Asia,History / General,Science / Earth Sciences / Geography,Technology & Engineering / Cartography} } @article{tallon_bristol_2007, title = {Bristol}, author = {Tallon, Andrew R.}, year = {2007}, month = feb, journal = {Cities}, volume = {24}, number = {1}, pages = {74--88}, issn = {02642751}, doi = {10/dmr8rv}, urldate = {2018-01-03}, langid = {english}, keywords = {nosource} } @book{tennekes_elegant_2022, title = {Elegant and Informative Maps with \{tmap\}}, author = {Tennekes, Martijn and Nowosad, Jakub}, year = {2024}, publisher = {(in progress)} } @article{tennekes_tmap_2018, title = {Tmap: {{Thematic Maps}} in {{R}}}, author = {Tennekes, Martijn}, year = {2018}, journal = {Journal of Statistical Software, Articles}, volume = {84}, number = {6}, pages = {1--39}, issn = {1548-7660}, doi = {10/gfdd6z}, abstract = {Thematic maps show spatial distributions. The theme refers to the phenomena that is shown, which is often demographical, social, cultural, or economic. The best known thematic map type is the choropleth, in which regions are colored according to the distribution of a data variable. The R package tmap offers a coherent plotting system for thematic maps that is based on the layered grammar of graphics. Thematic maps are created by stacking layers, where per layer, data can be mapped to one or more aesthetics. It is also possible to generate small multiples. Thematic maps can be further embellished by configuring the map layout and by adding map attributes, such as a scale bar and a compass. Besides plotting thematic maps on the graphics device, they can also be made interactive as an HTML widget. In addition, the R package tmaptools contains several convenient functions for reading and processing spatial data.}, keywords = {nosource,R,spatial data,thematic maps} } @article{theeconomist_autonomous_2016, title = {The Autonomous Car's Reality Check}, author = {{The Economist}}, year = {2016}, journal = {The Economist}, issn = {0013-0613}, urldate = {2018-05-11}, abstract = {Building highly detailed maps for robotic vehicles}, keywords = {nosource} } @article{thiele_r_2014, title = {R {{Marries NetLogo}}: {{Introduction}} to the {{RNetLogo Package}}}, author = {Thiele, J}, year = {2014}, journal = {Journal of Statistical Software}, volume = {58}, number = {2}, pages = {1--41}, doi = {10/ghfbck}, keywords = {nosource} } @article{tobler_computer_1970, title = {A Computer Movie Simulating Urban Growth in the {{Detroit}} Region}, author = {Tobler, Waldo R}, year = {1970}, journal = {Economic geography}, pages = {234--240}, issn = {0013-0095}, doi = {10.2307/143141} } @article{tobler_smooth_1979, title = {Smooth {{Pycnophylactic Interpolation}} for {{Geographical Regions}}}, author = {Tobler, Waldo R.}, year = {1979}, month = sep, journal = {Journal of the American Statistical Association}, volume = {74}, number = {367}, pages = {519--530}, issn = {0162-1459, 1537-274X}, doi = {10/ghz78f}, urldate = {2017-08-07}, langid = {english}, keywords = {nosource} } @article{tomintz_geography_2008, title = {The Geography of Smoking in {{Leeds}}: Estimating Individual Smoking Rates and the Implications for the Location of Stop Smoking Services}, author = {Tomintz, Melanie N M.N. and Clarke, Graham P and Rigby, Janette E J.E.}, year = {2008}, journal = {Area}, volume = {40}, number = {3}, pages = {341--353}, doi = {10/dn8x5b}, keywords = {geography of smoking,health geography,location-allocation,microsimulation,modelling,nosource,stop smoking services} } @book{tomlin_geographic_1990, title = {Geographic Information Systems and Cartographic Modeling}, author = {Tomlin, C. Dana}, year = {1990}, publisher = {Prentice Hall}, address = {Englewood Cliffs, N.J}, isbn = {978-0-13-350927-4}, lccn = {G70.2 .T64 1990}, keywords = {Cartography,Data processing,Geographic information systems,nosource} } @article{tomlin_map_1994, title = {Map Algebra: One Perspective}, shorttitle = {Map Algebra}, author = {Tomlin, C. Dana}, year = {1994}, journal = {Landscape and Urban Planning}, volume = {30}, number = {1-2}, pages = {3--12}, publisher = {Elsevier}, doi = {10/dm2qm2} } @book{usgs_geological_2016, title = {U.{{S}}. {{Geological Survey}} ({{USGS}}) {{Earth Resources Observation}} and {{Science}} ({{EROS}}) {{Center}}}, author = {{USGS}}, year = {2016}, keywords = {nosource} } @book{venables_modern_2002, title = {Modern {{Applied Statistics}} with {{S}}}, author = {Venables, W. N. and Ripley, B. D.}, year = {2002}, edition = {Fourth}, publisher = {Springer}, address = {New York}, keywords = {nosource} } @article{visvalingam_line_1993, title = {Line Generalisation by Repeated Elimination of Points}, author = {Visvalingam, M. and Whyatt, J. D.}, year = {1993}, month = jun, journal = {The Cartographic Journal}, volume = {30}, number = {1}, pages = {46--51}, issn = {0008-7041, 1743-2774}, doi = {10/fx74gh}, urldate = {2018-01-03}, langid = {english}, keywords = {nosource} } @article{vonwehrden_pluralism_2009, title = {Pluralism and Diversity: Trends in the Use and Application of Ordination Methods 1990-2007}, shorttitle = {Pluralism and Diversity}, author = {{von Wehrden}, Henrik and Hanspach, Jan and Bruelheide, Helge and Wesche, Karsten}, year = {2009}, month = aug, journal = {Journal of Vegetation Science}, volume = {20}, number = {4}, pages = {695--705}, issn = {11009233, 16541103}, doi = {10/ffp89h}, urldate = {2018-07-25}, langid = {english}, keywords = {nosource} } @article{waldykowski_sustainable_2021, title = {Sustainable {{Urban Transport}}---{{Why}} a {{Fast Investment}} in a {{Complete Cycling Network Is Most Profitable}} for a {{City}}}, author = {Wa{\l}dykowski, Piotr and Adamczyk, Joanna and Dorotkiewicz, Maciej}, year = {2021}, month = dec, journal = {Sustainability}, volume = {14}, pages = {119}, doi = {10.3390/su14010119}, abstract = {The development of safe cycling as a mode of transport is an important objective of the transformation towards sustainable mobility in European cities. A significant number of European cities are faced with the need to implement the assumptions of the European Green Deal, of which the promotion of sustainable urban transport is a part. The article presented a simulation of the Perfect Cycling City Model in real conditions that inspired the design of two scenarios for the fast development of bicycle routes in a key transport network area in Warsaw. Scenario 1 assumes building subsidiary bicycle routes and links between the main routes. In Scenario 2, the development of all optimal cycling links at the local level is assumed. An increase in cycling participation is expected in both scenarios. The comparison of projected costs of each scenario indicated that building a complete network of connections is more profitable in terms of increased cycling participation and could counter the dominance of private car use. For this to happen, measures encouraging individuals combined with improved safety and convenience of cycling around the city must be undertaken.} } @book{walker_analyzing_2022, title = {Analyzing {{US Census Data}}: {{Methods}}, {{Maps}}, and {{Models}} in {{R}}}, author = {Walker, Kyle E.}, year = {2022}, publisher = {Chapman \& Hall/CRC} } @article{wardrop_theoretical_1952, title = {Some Theoretical Aspects of Road Traffic Research}, author = {Wardrop, J G}, year = {1952}, month = may, journal = {Proceedings of the Institution of Civil Engineers}, volume = {1}, number = {3}, pages = {325--362}, publisher = {ICE Publishing}, doi = {10.1680/ipeds.1952.11259}, urldate = {2023-11-29}, keywords = {ALTERNATIVE,BEHAVIOUR,CAPACITY,DISTRIBUTION,FREQUENCY,GREENFORD,INTERSECTIONS,JOURNEYS,MIDDLESEX,OVERTAKING,QUEUES,RESEARCH,ROADS,ROUTES,SIGNALS,SPEED,THEORETICAL,TIME,TRAFFIC,UK,WESTERN AVENUE}, annotation = {1619 citations (Crossref) [2023-11-29]} } @book{wegmann_remote_2016, title = {Remote Sensing and {{GIS}} for Ecologists: Using Open Source Software}, shorttitle = {Remote Sensing and {{GIS}} for Ecologists}, editor = {Wegmann, Martin and Leutner, Benjamin and Dech, Stefan}, year = {2016}, series = {Data in the Wild}, publisher = {Pelagic Publishing}, address = {Exeter}, isbn = {978-1-78427-022-3 978-1-78427-023-0 978-1-78427-024-7 978-1-78427-025-4 978-1-78427-028-5}, langid = {english}, keywords = {nosource}, annotation = {OCLC: 945979372} } @book{wickham_advanced_2019, title = {Advanced {{R}}, {{Second Edition}}}, author = {Wickham, Hadley}, year = {2019}, month = may, publisher = {CRC Press}, abstract = {Advanced R helps you understand how R works at a fundamental level. It is designed for R programmers who want to deepen their understanding of the language, and programmers experienced in other languages who want to understand what makes R different and special. This book will teach you the foundations of R; three fundamental programming paradigms (functional, object-oriented, and metaprogramming); and powerful techniques for debugging and optimisingyour code.By reading this book, you will learn: The difference between an object and its name, and why the distinction is important The important vector data structures, how they fit together, and how you can pull them apart using subsetting The fine details of functions and environments The condition system, which powers messages, warnings, and errors The powerful functional programming paradigm, which can replace many for loops The three most important OO systems: S3, S4, and R6 The tidy eval toolkit for metaprogramming, which allows you to manipulate code and control evaluation Effective debugging techniques that you can deploy, regardless of how your code is run How to find and remove performance bottlenecks The second edition is a comprehensive update: New foundational chapters: "Names and values," "Control flow," and "Conditions" comprehensive coverage of object oriented programming with chapters on S3, S4, R6, and how to choose between them Much deeper coverage of metaprogramming, including the new tidy evaluation framework use of new package like rlang (http://rlang.r-lib.org), which provides a clean interface to low-level operations, and purr (http://purrr.tidyverse.org/) for functional programming Use of color in code chunks and figuresHadley Wickham is Chief Scientist at RStudio, an Adjunct Professor at Stanford University and the University of Auckland, and a member of the R Foundation. He is the lead developer of the tidyverse, a collection of R packages, including ggplot2 and dplyr, designed to support data science. He is also the author of R for Data Science (with Garrett Grolemund), R Packages, and ggplot2: Elegant Graphics for Data Analysis.}, googlebooks = {JAOaDwAAQBAJ}, isbn = {978-1-351-20129-2}, langid = {english}, keywords = {Mathematics / Probability & Statistics / General,Reference / General} } @book{wickham_ggplot2_2016, title = {Ggplot2: {{Elegant Graphics}} for {{Data Analysis}}}, shorttitle = {Ggplot2}, author = {Wickham, Hadley}, year = {2016}, month = jun, edition = {Second}, publisher = {Springer}, address = {New York, NY}, abstract = {This new edition to the classic book by ggplot2 creator Hadley Wickham highlights compatibility with knitr and RStudio. ggplot2 is a data visualization package for R that helps users create data graphics, including those that are multi-layered, with ease. With ggplot2, it's easy to: produce handsome, publication-quality plots with automatic legends created from the plot specification superimpose multiple layers (points, lines, maps, tiles, box plots) from different data sources with automatically adjusted common scales add customizable smoothers that use powerful modeling capabilities of R, such as loess, linear models, generalized additive models, and robust regression save any ggplot2 plot (or part thereof) for later modification or reuse create custom themes that capture in-house or journal style requirements and that can easily be applied to multiple plots approach a graph from a visual perspective, thinking about how each component of the data is represented on the final plot This book will be useful to everyone who has struggled with displaying data in an informative and attractive way. Some basic knowledge of R is necessary (e.g., importing data into R). ggplot2 is a mini-language specifically tailored for producing graphics, and you'll learn everything you need in the book. After reading this book you'll be able to produce graphics customized precisely for your problems, and you'll find it easy to get graphics out of your head and on to the screen or page.}, isbn = {978-3-319-24275-0}, langid = {english} } @book{wickham_mastering_2021, title = {Mastering {{Shiny}}: {{Build Interactive Apps}}, {{Reports}}, and {{Dashboards Powered}} by {{R}}}, shorttitle = {Mastering {{Shiny}}}, author = {Wickham, Hadley}, year = {2021}, month = may, publisher = {O'Reilly Media}, address = {Sebastopol, CA}, abstract = {Master the Shiny web framework-and take your R skills to a whole new level. By letting you move beyond static reports, Shiny helps you create fully interactive web apps for data analyses. Users will be able to jump between datasets, explore different subsets or facets of the data, run models with parameter values of their choosing, customize visualizations, and much more. Hadley Wickham from RStudio shows data scientists, data analysts, statisticians, and scientific researchers with no knowledge of HTML, CSS, or JavaScript how to create rich web apps from R. This in-depth guide provides a learning path that you can follow with confidence, as you go from a Shiny beginner to an expert developer who can write large, complex apps that are maintainable and performant. Get started: Discover how the major pieces of a Shiny app fit together Put Shiny in action: Explore Shiny functionality with a focus on code samples, example apps, and useful techniques Master reactivity: Go deep into the theory and practice of reactive programming and examine reactive graph components Apply best practices: Examine useful techniques for making your Shiny apps work well in production}, isbn = {978-1-4920-4738-4}, langid = {english} } @article{wickham_tidy_2014, title = {Tidy {{Data}}}, author = {Wickham, Hadley}, year = {2014}, journal = {Journal of Statistical Software}, volume = {59}, number = {10}, issn = {1548-7660}, doi = {10/gdm3p7}, urldate = {2018-08-20}, langid = {english}, keywords = {nosource} } @article{wieland_market_2017, title = {Market {{Area Analysis}} for {{Retail}} and {{Service Locations}} with {{MCI}}}, author = {Wieland, Thomas}, year = {2017}, journal = {The R Journal}, volume = {9}, number = {1}, pages = {298--323}, doi = {10/gkb5ft}, keywords = {nosource} } @book{wilkinson_grammar_2005, title = {The Grammar of Graphics}, author = {Wilkinson, Leland and Wills, Graham}, year = {2005}, publisher = {Springer Science+ Business Media}, keywords = {nosource} } @book{wimberly_geographic_2023, title = {Geographic {{Data Science}} with {{R}}: {{Visualizing}} and {{Analyzing Environmental Change}}}, shorttitle = {Geographic {{Data Science}} with {{R}}}, author = {Wimberly, Michael C.}, year = {2023}, publisher = {Chapman \& Hall/CRC}, urldate = {2023-05-06}, abstract = {A book example for a Chapman \& Hall book.} } @book{wise_gis_2001, title = {{{GIS}} Basics}, author = {Wise, Stephen}, year = {2001}, publisher = {CRC Press}, keywords = {nosource} } @book{wood_java_2002, title = {Java Programming for Spatial Sciences}, author = {Wood, Jo}, year = {2002}, publisher = {Taylor \& Francis}, address = {London ; New York}, isbn = {978-0-415-26097-8 978-0-415-26098-5}, lccn = {QA76.73.J38 W6615 2002}, keywords = {Geographic information systems,Java (Computer program language),nosource} } @article{wright_ranger_2017, title = {Ranger: {{A Fast Implementation}} of {{Random Forests}} for {{High Dimensional Data}} in {{C}}++ and {{R}}}, author = {Wright, Marvin N. and Ziegler, Andreas}, year = {2017}, journal = {Journal of Statistical Software}, volume = {77}, number = {1}, pages = {1--17}, doi = {10.18637/jss.v077.i01} } @book{wulf_invention_2015, title = {The Invention of Nature: {{Alexander}} von {{Humboldt}}'s New World}, shorttitle = {The Invention of Nature}, author = {Wulf, Andrea}, year = {2015}, publisher = {Alfred A. Knopf}, address = {New York}, isbn = {978-0-385-35066-2 978-0-345-80629-1}, lccn = {Q143.H9 W85 2015}, keywords = {Germany,Humboldt Alexander von,Naturalists,nosource,Scientists} } @book{xiao_gis_2016, title = {{{GIS Algorithms}}: {{Theory}} and {{Applications}} for {{Geographic Information Science}} \& {{Technology}}}, shorttitle = {{{GIS Algorithms}}}, author = {Xiao, Ningchuan}, year = {2016}, publisher = {SAGE Publications}, address = {London}, doi = {10.4135/9781473921498}, urldate = {2018-05-07}, abstract = {Geographic information systems (GIS) have become increasingly important in helping us understand complex social, economic, and natural dynamics where spatial components play a key role. The critical algorithms used in GIS, however, are notoriously difficult to both teach and understand, in part due to the lack of a coherent representation. GIS Algorithms attempts to address this problem by combining rigorous formal language with example case studies and student exercises. Using Python code throughout, Xiao breaks the subject down into three fundamental areas: {$\quad\bullet\quad$}Geometric Algorithms {$\quad\bullet\quad$}Spatial Indexing {$\quad\bullet\quad$}Spatial Analysis and Modelling With its comprehensive coverage of the many algorithms involved, GIS Algorithms is a key new textbook in this complex and critical area of geography.}, keywords = {nosource} } @book{xie_evolving_2011, title = {Evolving {{Transportation Networks}}}, author = {Xie, Feng and Levinson, David}, year = {2011}, series = {Transportation {{Research}}, {{Economics}} and {{Policy}}}, publisher = {Springer-Verlag}, address = {New York}, urldate = {2019-07-04}, abstract = {Over the last two centuries, the development of modern transportation has significantly transformed human life. The main theme of this book is to understand the complexity of transportation development and model the process of network growth including its determining factors, which may be topological, morphological, temporal, technological, economic, managerial, social or political. Using multidimensional concepts and methods, the authors develop a holistic framework to represent network growth as an open and complex process with models that demonstrate in a scientific way how numerous independent decisions made by entities such as travelers, property owners, developers, and public jurisdictions could result in a coherent network of facilities on the ground. Models are proposed from innovative perspectives including self-organization, degeneration, and sequential connection to interpret the evolutionary growth of transportation networks in explicit consideration of independent economic and regulatory initiatives. Employing these models, the authors survey a series of topics ranging from network hierarchy and topology to first mover advantage. The authors demonstrate, with a wide spectrum of empirical and theoretical evidence, that network growth follows a path that is not only logical in retrospect, but also predictable and manageable from a planning perspective. In the larger scheme of innovative transportation planning, this book provides a re-consideration of conventional planning practice and sets the stage for further development on the theory and practice of the next-generation, evolutionary planning approach in transportation, making it of interest to scholars and practitioners alike in the field of transportation.}, isbn = {978-1-4419-9803-3}, langid = {english}, keywords = {nosource} } @book{zuur_beginners_2017, title = {Beginner's Guide to Spatial, Temporal and Spatial-Temporal Ecological Data Analysis with {{R-INLA}}}, author = {Zuur, Alain F. and Ieno, Elena N. and Saveliev, Anatoly A. and Zuur, Alain F.}, year = {2017}, volume = {1}, publisher = {Highland Statistics Ltd}, address = {Newburgh, United Kingdom}, isbn = {978-0-9571741-9-1}, langid = {english}, keywords = {nosource}, annotation = {OCLC: 993615802} } @book{zuur_mixed_2009, title = {Mixed Effects Models and Extensions in Ecology with {{R}}}, author = {Zuur, Alain and Ieno, Elena N. and Walker, Neil and Saveliev, Anatoly A. and Smith, Graham M.}, year = {2009}, series = {Statistics for {{Biology}} and {{Health}}}, publisher = {Springer-Verlag}, address = {New York}, urldate = {2018-02-07}, isbn = {978-0-387-87457-9}, langid = {english}, keywords = {nosource} } ================================================ FILE: index.Rmd ================================================ --- title: 'Geocomputation with R' author: 'Robin Lovelace, Jakub Nowosad, Jannes Muenchow' date: '`r Sys.Date()`' site: bookdown::bookdown_site output: bookdown::bs4_book documentclass: krantz monofont: "Source Code Pro" monofontoptions: "Scale=0.7" bibliography: - geocompr.bib - packages.bib biblio-style: apalike link-citations: yes colorlinks: yes graphics: yes description: "Geocomputation with R is for people who want to analyze, visualize and model geographic data with open source software. It is based on R, a statistical programming language that has powerful data processing, visualization, and geospatial capabilities. The book equips you with the knowledge and skills to tackle a wide range of issues manifested in geographic data, including those with scientific, societal, and environmental implications. This book will interest people from many backgrounds, especially Geographic Information Systems (GIS) users interested in applying their domain-specific knowledge in a powerful open source language for data science, and R users interested in extending their skills to handle spatial data." github-repo: "geocompx/geocompr" cover-image: "images/cover2.png" url: https://r.geocompx.org/ --- ```{r index-1, echo=FALSE} is_on_ghactions = identical(Sys.getenv("GITHUB_ACTIONS"), "true") is_online = curl::has_internet() is_html = knitr::is_html_output() ``` ```{r, echo = FALSE} # google scholar metadata library(metathis) if (is_html) { meta() |> meta_google_scholar( title = "Geocomputation with R", author = c("Robin Lovelace", "Jakub Nowosad", "Jannes Muenchow"), publication_date = "2019", isbn = "9780203730058" ) } ``` ```{asis index-2, echo=is_html} # Welcome {-} This is the online home of *Geocomputation with R*, a book on geographic data analysis, visualization and modeling. The geocompr ed2 book cover **Note**: The second edition of the book has been published by CRC Press in the [R Series](https://www.routledge.com/Chapman--HallCRC-The-R-Series/book-series/CRCTHERSER). You can buy the book from [CRC Press](https://www.routledge.com/9781032248882), or [Amazon](https://www.amazon.com/Geocomputation-Chapman-Hall-Robin-Lovelace-dp-1032248882/dp/1032248882). The archived **First Edition** is hosted on [bookdown.org](https://bookdown.org/robinlovelace/geocompr/). Inspired by the Free and Open Source Software for Geospatial ([FOSS4G](https://foss4g.org/)) movement, the code and prose underlying this book are open, ensuring that the content is reproducible, transparent, and accessible. Hosting the source code on [GitHub](https://github.com/geocompx/geocompr) allows anyone to interact with the project by opening issues or contributing new content and typo fixes for the benefit of everyone. [![](https://img.shields.io/github/stars/geocompx/geocompr?style=for-the-badge)](https://github.com/geocompx/geocompr) [![](https://img.shields.io/github/contributors/geocompx/geocompr?style=for-the-badge)](https://github.com/geocompx/geocompr/graphs/contributors) The online version of the book is hosted at [r.geocompx.org](https://r.geocompx.org) and kept up-to-date by [GitHub Actions](https://github.com/geocompx/geocompr/actions). Its current 'build status' as follows: [![Actions](https://github.com/geocompx/geocompr/workflows/Render/badge.svg)](https://github.com/geocompx/geocompr/actions) ``` ```{r index-2-2, echo=FALSE, eval=is_html, results="asis"} if (is_on_ghactions){ cat(paste0("This version of the book was built on GH Actions on ", Sys.Date(), ".")) } else { cat(paste0("This version of the book was built on ", Sys.Date(), ".")) } ``` ```{asis index-2-3, echo=is_html} Creative Commons License
This book is licensed to you under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. Creative Commons License
The code samples in this book are licensed under Creative Commons CC0 1.0 Universal (CC0 1.0). ## How to contribute? {-} **bookdown** makes editing a book as easy as editing a wiki, provided you have a GitHub account ([sign-up at github.com](https://github.com/join)). Once logged-in to GitHub, click on the 'Edit this page' icon in the right panel of the book website. This will take you to an editable version of the the source [R Markdown](https://rmarkdown.rstudio.com/) file that generated the page you're on. To raise an issue about the book's content (e.g., code not running) or make a feature request, check-out the [issue tracker](https://github.com/geocompx/geocompr/issues). Maintainers and contributors must follow this repository’s [CODE OF CONDUCT](https://github.com/geocompx/geocompr/blob/main/CODE_OF_CONDUCT.md). ## Reproducibility {-} The quickest way to reproduce the contents of the book if you're new to geographic data in R may be in the web browser, thanks to [Binder](https://mybinder.org/). Clicking on the link below should open a new window containing RStudio Server in your web browser, enabling you to open chapter files and running code chunks to test that the code is reproducible. [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/geocompx/geocompr/main?urlpath=rstudio) If you see something like the image below, congratulations, it worked! You can start exploring Geocomputation with R in a cloud-based environment, noting [mybinder.org user guidelines](https://mybinder.readthedocs.io/en/latest/about/user-guidelines.html)): ``` ```{r index-2-4, echo=FALSE, fig.cap="Screenshot of reproducible code contained in Geocomputation with R running in RStudio Server on a browser served by Binder", eval=is_html} knitr::include_graphics("https://user-images.githubusercontent.com/1825120/134802314-6dd368c7-f5eb-4cd7-b8ff-428dfa93954c.png") ``` ```{asis index-2-5, echo=is_html} To reproduce the code in the book on your own computer, you need a recent version of [R](https://cran.r-project.org/) and up-to-date packages. These can be installed using the [**remotes**](https://github.com/r-lib/remotes) package. ``` ```{r index-3, message=FALSE, eval=FALSE, echo=is_html, results='hide'} install.packages("remotes") install.packages("geocompkg", repos = c("https://geocompr.r-universe.dev", "https://cloud.r-project.org"), dependencies = TRUE, force = TRUE) ``` ```{asis index-3-1a, echo=is_html} After installing the book's dependencies, you can rebuild the book for testing and educational purposes. To do this [download](https://github.com/geocompx/geocompr/archive/refs/heads/main.zip) and unzip or [clone](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) the book's source code. After opening the `geocompr.Rproj` project in [RStudio](https://posit.co/download/rstudio-desktop/#download) (or opening the folder in another IDE such as [VS Code](https://github.com/REditorSupport/vscode-R)), you should be able to reproduce the contents with the following command: ``` ```{r index-3-1, eval=FALSE, echo=is_html} bookdown::serve_book(".") ``` ```{r index-3-2, echo=FALSE, include=FALSE} # is geocompkg installed? geocompkg_is_installed = "geocompkg" %in% installed.packages() if (!geocompkg_is_installed){ message( 'geocompkg not installed, run\nremotes::install_github("geocompx/geocompkg") # to install it' ) } ``` ```{asis index-23, echo=is_html} See the project's [GitHub repo](https://github.com/geocompx/geocompr#reproducing-the-book) for full details on reproducing the book. ``` ```{asis index-22, echo=is_html} ## Getting involved {-} If you find the project of use and interest, you can get involved in many ways, by: - Telling people about it - '[Starring](https://help.github.com/articles/about-stars/)' the [geocompr GitHub repository](https://github.com/geocompx/geocompr) - Communicating about the book online, via the [#geocompr hashtag](https://fosstodon.org/tags/geocompx) on Mastodon (see our [Guestbook at geocompx.org](https://geocompx.org/guestbook/)) or by letting us know of [courses](https://github.com/geocompx/geocompx.org/edit/main/guestbook.qmd) using the book - [Citing](https://github.com/geocompx/geocompr/raw/main/CITATION.bib) and [linking-to](https://r.geocompx.org) it - [Buying](https://www.amazon.com/Geocomputation-R-Robin-Lovelace-dp-0367670577/dp/0367670577) a copy - Reviewing it, on [Amazon](https://www.amazon.com/Geocomputation-Chapman-Hall-Robin-Lovelace/dp/1138304514/), [Goodreads](https://www.goodreads.com/book/show/42780859-geocomputation-with-r) or elsewhere - Asking questions about the content or making suggestion on [GitHub](https://github.com/geocompx/geocompr/issues), [Mastodon](https://fosstodon.org/tags/geocompx) or [Discord](https://discord.com/invite/PMztXYgNxp) - Answering questions, or at least responding to people asking for clarification or reproducible examples to demonstrate their question - Helping people get started with open source software for reproducible research in general, and working with geographic data in R in particular (this can be an excellent way to consolidate and build your own skills) - Supporting community translations - The Spanish version: https://r.geocompx.org/es/ - The French version: https://r.geocompx.org/fr/ - The Japanese version: https://r.geocompx.org/jp/ Further details can be found at [github.com/geocompx/geocompr](https://github.com/geocompx/geocompr#geocomputation-with-r).
The globe icon used in this book was created by [Jean-Marc Viglino](https://github.com/Viglino) and is licensed under [CC-BY 4.0 International](https://github.com/Viglino/font-gis/blob/main/LICENSE-CC-BY.md). The book website is hosted on [Netlify](https://www.netlify.com/). ``` ```{asis index-5a, echo=!is_html} \newpage \vspace*{5cm} \thispagestyle{empty} \begin{center} \Large \emph{For Katy} \end{center} \vspace*{2cm} \begin{center} \Large \emph{Dla mojej rodziny} \end{center} \vspace*{2cm} \begin{center} \Large \emph{F{\"u}r meine Katharina und alle unsere Kinder } \end{center} ``` ```{asis index-22c, echo=is_html} # Foreword (1st Edition) {-} Doing 'spatial' in R has always been about being broad, seeking to provide and integrate tools from geography, geoinformatics, geocomputation and spatial statistics for anyone interested in joining in: joining in asking interesting questions, contributing fruitful research questions, and writing and improving code. That is, doing 'spatial' in R has always included open source code, open data and reproducibility. Doing 'spatial' in R has also sought to be open to interaction with many branches of applied spatial data analysis, and also to implement new advances in data representation and methods of analysis to expose them to cross-disciplinary scrutiny. As this book demonstrates, there are often alternative workflows from similar data to similar results, and we may learn from comparisons with how others create and understand their workflows. This includes learning from similar communities around Open Source GIS and complementary languages such as Python, Java and so on. R's wide range of spatial capabilities would never have evolved without people willing to share what they were creating or adapting. This might include teaching materials, software, research practices (reproducible research, open data), and combinations of these. R users have also benefitted greatly from 'upstream' open source geo libraries such as GDAL, GEOS and PROJ. This book is a clear example that, if you are curious and willing to join in, you can find things that need doing and that match your aptitudes. With advances in data representation and workflow alternatives, and ever increasing numbers of new users often without applied quantitative command line exposure, a book of this kind has really been needed. Despite the effort involved, the authors have supported each other in pressing forward to publication. So, this fresh book is ready to go; its authors have tried it out during many tutorials and workshops, so readers and instructors will be able to benefit from knowing that the contents have been and continue to be tried out on people like them. Engage with the authors and the wider R-spatial community, see value in having more choice in building your workflows and most important, enjoy applying what you learn here to things you care about. Roger Bivand Bergen, September 2018 ``` # Foreword (2nd Edition) {-} Writing books about open source data science software that constantly changes in uncontrolled ways is a brave undertaking: it feels like running a race while someone else constantly moves the finish line. This second edition of _Geocomputation with R_ is timely: it not only catches up with many recent changes, but also embraces new R packages, and new topical developments in the computing landscape. It now includes a chapter on raster-vector interactions, discussing the package **terra** which is replacing package **raster** for raster (and vector) data processing. It also keeps up with the **tmap** package for creating high quality maps, which is completing a full rewrite cycle. Besides updating the contents of this book, the authors have also been very active in helping to streamline and focus those changes in software by extensively testing it, helping improve it, writing issues and pull requests on GitHub, sharing benchmark results, and helping to improve software documentation. The first edition of this book has been a great success. It was the first book to popularize spatial analysis with the **sf** package and **tidyverse**. Its enthusiastic tone reached a wide audience, and helped people at various levels of experience solving new problems and moving to their next level. Being available entirely freely online in addition to the printed volume gave it a large reach, and enabled users to try out the presented methodology on their own datasets. In addition to that, the authors have encouraged the readership to reach out by ways of GitHub issues, social media posts, and discussions in a discord channel. This has led to 75 people contributing to the book's source code in one way or the other, including several providing longer reviews or contributing full sections, including on Cloud-optimized GeoTIFFs, STAC and openEO; the **sfheaders** package; OGC APIs and metadata; and the `CycleHire` shiny app. on Discord, it has led to lively and spontaneous discussions in threads that include topics ranging from highly technical to "look what I built". Beyond this, the authors have initiated the companion volume _Geocomputation with Python_, stressing that geocomputation happens with data science languages, and is by no means restricted to one of them. Geocomputation is on the rise, and as part of fostering a growing geocomputation community, writing books like this one is indispensable. Edzer Pebesma Münster, Germany, May 2024 # Preface {-} ## Who this book is for {-} This book is for people who want to analyze, visualize and model geographic data with open source software. It is based on R, a statistical programming language that has powerful data processing, visualization and geospatial capabilities. The book covers a wide range of topics and will be of interest to a wide range of people from many different backgrounds, especially: - People who have learned spatial analysis skills using a desktop Geographic Information System (GIS), such as [QGIS](https://qgis.org/en/site/), [ArcGIS](http://desktop.arcgis.com/en/arcmap/), [GRASS GIS](https://grass.osgeo.org/) or [SAGA](https://saga-gis.sourceforge.io/en/index.html), who want access to a powerful (geo)statistical and visualization programming language and the benefits of a command line approach [@sherman_desktop_2008]: > With the advent of 'modern' GIS software, most people want to point and click their way through life. That's good, but there is a tremendous amount of flexibility and power waiting for you with the command line. - Graduate students and researchers from fields specializing in geographic data including Geography, Remote Sensing, Planning, GIS and Spatial Data Science - Academics and post-graduate students working with geographic data --- in fields such as Geology, Regional Science, Biology and Ecology, Agricultural Sciences, Archaeology, Epidemiology, Transport Modeling, and broadly defined Data Science --- who require the power and flexibility of R for their research - Applied researchers and analysts in public, private or third-sector organizations who need the reproducibility, speed and flexibility of a command line language such as R in applications dealing with spatial data as diverse as Urban and Transport Planning, Logistics, Geo-marketing (store location analysis) and Emergency Planning The book is designed for intermediate-to-advanced R users interested in geocomputation and R beginners who have prior experience with geographic data. If you are new to both R and geographic data, do not be discouraged: we provide links to further materials and describe the nature of spatial data from a beginner's perspective in Chapter \@ref(spatial-class) and in links provided below. ## How to read this book {-} The book is divided into three parts: 1. Part I: Foundations, aimed at getting you up-to-speed with geographic data in R. 2. Part II: Advanced techniques, including spatial data visualization, bridges to GIS software, programming with spatial data, and statistical learning. 3. Part III: Applications to real-world problems, including transportation, geomarketing and ecological modeling. The chapters get harder from one part to the next. We recommend reading all chapters in Part I in order before tackling the more advanced topics in Part II and Part III. The chapters in Part II and Part III benefit slightly from being read in order, but can be read independently if you are interested in a specific topic. A major barrier to geographical analysis in R is its steep learning curve. The chapters in Part I aim to address this by providing reproducible code on simple datasets that should ease the process of getting started. An important aspect of the book from a teaching/learning perspective is the **exercises** at the end of each chapter. Completing these will develop your skills and equip you with the confidence needed to tackle a range of geospatial problems. Solutions to the exercises can be found in an online booklet that accompanies Geocomputation with R, hosted at [r.geocompx.org/solutions](https://r.geocompx.org/solutions). To learn how this booklet was created, and how to update solutions in files such as [_01-ex.Rmd](https://github.com/geocompx/geocompr/blob/main/_01-ex.Rmd), see our blog post on [Geocomputation with R solutions](https://geocompx.org/post/2022/geocompr-solutions/). More blog posts and examples can be found at [geocompx.org](https://geocompx.org). Impatient readers are welcome to dive straight into the practical examples, starting in Chapter \@ref(spatial-class). However, we recommend reading about the wider context of *Geocomputation with R* in Chapter \@ref(intro) first. If you are new to R, we also recommend learning more about the language before attempting to run the code chunks provided in each chapter (unless you're reading the book for an understanding of the concepts). Fortunately for beginners, R has a supportive community that has developed a wealth of resources that can help. We particularly recommend three tutorials: [R for Data Science](https://r4ds.had.co.nz/) [@grolemund_r_2016] [Efficient R Programming](https://csgillespie.github.io/efficientR/) [@gillespie_efficient_2016], and [An introduction to R](http://colinfay.me/intro-to-r/) [@rcoreteam_introduction_2021]. ## Why R? {-} Although R has a steep learning curve, the command line approach advocated in this book can quickly pay off. As you'll learn in subsequent chapters, R is an effective tool for tackling a wide range of geographic data challenges. We expect that, with practice, R will become the program of choice in your geospatial toolbox for many applications. Typing and executing commands at the command line is, in many cases, faster than pointing-and-clicking around the graphical user interface (GUI) of a desktop GIS. For some applications such as Spatial Statistics and modeling, R may be the *only* realistic way to get the work done. As outlined in Section \@ref(why-use-r-for-geocomputation), there are many reasons for using R for geocomputation: R is well suited to the interactive use required in many geographic data analysis workflows compared with other languages. R excels in the rapidly growing fields of Data Science (which includes data carpentry, statistical learning techniques and data visualization) and Big Data (via efficient interfaces to databases and distributed computing systems). Furthermore, R enables a reproducible workflow: sharing scripts underlying your analysis will allow others to build on your work. To ensure reproducibility in this book, we have made its source code available at [github.com/geocompx/geocompr](https://github.com/geocompx/geocompr#geocomputation-with-r). There you will find script files in the `code/` folder that generate figures: when code generating a figure is not provided in the main text of the book, the name of the script file that generated it is provided in the caption (see for example the caption for Figure \@ref(fig:zones)). Other languages such as Python, Java and C++ can be used for geocomputation. There are excellent resources for learning geocomputation *without R*, as discussed in Section \@ref(software-for-geocomputation). None of these provide the unique combination of package ecosystem, statistical capabilities, and visualization options offered by the R community. Furthermore, by teaching how to use one language (R) in depth, this book will equip you with the concepts and confidence needed to do geocomputation in other languages. ## Real-world impact {-} *Geocomputation with R* will equip you with knowledge and skills to tackle a wide range of issues, including those with scientific, societal and environmental implications, manifested in geographic data. As described in Section \@ref(what-is-geocomputation), geocomputation is not only about using computers to process geographic data, it is also about real-world impact. The wider context and motivations underlying this book are covered in Chapter \@ref(intro). ## Acknowledgments {-} ```{r contrib-preface, include=FALSE} contributors = readr::read_csv("extdata/contributors.csv") c_txt = contributors$name c_url = contributors$link c_rmd = paste0("[", c_txt, "](", c_url, ")") contributors_text = paste0(paste0(c_txt[-length(c_txt)], collapse = ", "), ", and ", c_txt[length(c_txt)]) ``` Many thanks to everyone who contributed directly and indirectly via the code hosting and collaboration site GitHub, including the following people who contributed direct via pull requests: `r contributors_text`. Thanks to Marco Sciaini who created the front cover image for the first edition and to Benjamin Nowak who created the cover image for the second edition. See `code/frontcover.R` and `code/frontcover2.R` for the reproducible code that generated these visualizations. Dozens more people contributed online, by raising and commenting on issues, and by providing feedback via social media. The `#geocompr` and `geocompx` hashtags will live on! We would like to thank John Kimmel and Lara Spieker from CRC Press and Taylor & Francis for taking our ideas from an early book plan into production via four rounds of peer review for each edition. The reviewers deserve special mention here for their detailed feedback and expertise substantially improved the book's structure and content. We thank Patrick Schratz and Alexander Brenning from the University of Jena for fruitful discussions on and contributions to Chapters \@ref(spatial-cv) and \@ref(eco). We thank Emmanuel Blondel from the Food and Agriculture Organization of the United Nations for expert contributions to the section on web services; Michael Sumner for critical contributions to many areas of the book, especially the discussion of algorithms in Chapter 11; Tim Appelhans, David Cooley and Kiranmayi Vadlamudi for key contributions to the visualization chapter (Chapter 9); Marius Appel for his contributions to Chapter 10; and Katy Gregg, who proofread every chapter and greatly improved the readability of the book. Countless others could be mentioned who contributed in myriad ways. The final thank you is for all the software developers who make geocomputation with R possible. Especially, Edzer Pebesma (who created the **sf** package), Robert Hijmans (who created **terra**) and Roger Bivand (who laid the foundations for much R-spatial software) who have made high performance geographic computing possible in R. ================================================ FILE: krantz.cls ================================================ %% This is file `Krantz.cls' %%% Created by Shashi Kumar / ITC [August 2008] \NeedsTeXFormat{LaTeX2e}[1995/12/01] \ProvidesClass{krantz} [2005/09/16 v1.4f Standard LaTeX document class] \newcommand\@ptsize{} \newif\if@restonecol \newif\if@titlepage \@titlepagetrue \newif\if@openright \newif\if@mainmatter \@mainmattertrue \if@compatibility\else \DeclareOption{a4paper} {\setlength\paperheight {297mm}% \setlength\paperwidth {210mm}} \DeclareOption{a5paper} {\setlength\paperheight {210mm}% \setlength\paperwidth {148mm}} \DeclareOption{b5paper} {\setlength\paperheight {250mm}% \setlength\paperwidth {176mm}} \DeclareOption{letterpaper} {\setlength\paperheight {11in}% \setlength\paperwidth {8.5in}} \DeclareOption{legalpaper} {\setlength\paperheight {14in}% \setlength\paperwidth {8.5in}} \DeclareOption{executivepaper} {\setlength\paperheight {10.5in}% \setlength\paperwidth {7.25in}} \DeclareOption{landscape} {\setlength\@tempdima {\paperheight}% \setlength\paperheight {\paperwidth}% \setlength\paperwidth {\@tempdima}} \fi \if@compatibility \renewcommand\@ptsize{0} \else \DeclareOption{10pt}{\renewcommand\@ptsize{0}} \fi \DeclareOption{11pt}{\renewcommand\@ptsize{1}} \DeclareOption{12pt}{\renewcommand\@ptsize{2}} \if@compatibility\else \DeclareOption{oneside}{\@twosidefalse \@mparswitchfalse} \fi \DeclareOption{twoside}{\@twosidetrue \@mparswitchtrue} \DeclareOption{draft}{\setlength\overfullrule{5pt}} \if@compatibility\else \DeclareOption{final}{\setlength\overfullrule{0pt}} \fi \DeclareOption{titlepage}{\@titlepagetrue} \if@compatibility\else \DeclareOption{notitlepage}{\@titlepagefalse} \fi \if@compatibility \@openrighttrue \else \DeclareOption{openright}{\@openrighttrue} \DeclareOption{openany}{\@openrightfalse} \fi \if@compatibility\else \DeclareOption{onecolumn}{\@twocolumnfalse} \fi \DeclareOption{twocolumn}{\@twocolumntrue} \DeclareOption{leqno}{\input{leqno.clo}} \DeclareOption{fleqn}{\input{fleqn.clo}} \DeclareOption{openbib}{% \AtEndOfPackage{% \renewcommand\@openbib@code{% \advance\leftmargin\bibindent \itemindent -\bibindent \listparindent \itemindent \parsep \z@ }% \renewcommand\newblock{\par}}% } %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newif\if@numbysec \DeclareOption{numbysec}{\@numbysectrue} \newif\if@numberinsequence \DeclareOption{numberinsequence}{\@numberinsequencetrue} \newif\if@nocaptionbreak \DeclareOption{NoCaptionBreak}{\@nocaptionbreaktrue} \newif\if@sevenbyten \DeclareOption{sevenbyten}{\@sevenbytentrue} \newif\if@cip \DeclareOption{cip}{\@ciptrue} \newif\if@times \DeclareOption{times}{\@timestrue} \newif\if@chapnumonly \DeclareOption{chapnumonly}{\@chapnumonlytrue} \newif\if@ChapterResetsPage \DeclareOption{ChapterResetsPage}{\@ChapterResetsPagetrue} \newif\if@ChapterTOCs \DeclareOption{ChapterTOCs}{\@ChapterTOCstrue} \newif\if@EOCRefs \DeclareOption{EOCRefs}{\@EOCRefstrue}% \newif\if@SuperscriptCites \DeclareOption{SuperscriptCites}{\@SuperscriptCitestrue}% \newif\if@UnnumberedReferences \DeclareOption{UnnumberedReferences}{\@UnnumberedReferencestrue}% \newif\if@pdf \DeclareOption{pdf}{\@pdftrue} \DeclareOption{krantz1}{\@krantzatrue} \newif\if@krantza \DeclareOption{krantz2}{\@krantzbtrue} \newif\if@krantzb %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ExecuteOptions{letterpaper,10pt,twoside,onecolumn,final,openright} \ProcessOptions %%%%%%%%%%%%%%%%%%% \def\helv@scale{.82} % \DeclareFontFamily{T1}{helvetica}{}% \DeclareFontShape{T1}{helvetica}{m}{n}{<->s*[\helv@scale]phvr8t}{}% \DeclareFontShape{T1}{helvetica}{m}{it}{<->s*[\helv@scale]phvro8t}{}% \DeclareFontShape{T1}{helvetica}{m}{sc}{<->s*[\helv@scale]phvrc8t}{}% \DeclareFontShape{T1}{helvetica}{b}{n}{<->s*[\helv@scale]phvb8t}{}% \DeclareFontShape{T1}{helvetica}{b}{it}{<->s*[\helv@scale]phvbo8t}{}% \DeclareFontShape{T1}{helvetica}{m}{sl}{<->s*[\helv@scale]phvro8t}{}% \DeclareFontShape{T1}{helvetica}{b}{sc}{<->s*[\helv@scale]phvbc8t}{}% \DeclareFontShape{T1}{helvetica}{b}{sl}{<->s*[\helv@scale]phvbo8t}{}% \DeclareFontShape{T1}{helvetica}{bx}{n}{<->s*[\helv@scale]phvb8t}{}% \DeclareFontShape{T1}{helvetica}{bx}{it}{<->s*[\helv@scale]phvbo8t}{}% \DeclareFontShape{T1}{helvetica}{bx}{sc}{<->s*[\helv@scale]phvbc8t}{}% \DeclareFontShape{T1}{helvetica}{bx}{sl}{<->ssub * helvetica/b/it}{}% \DeclareFontFamily{OT1}{helvetica}{}% \DeclareFontShape{OT1}{helvetica}{m}{n}{<->s*[\helv@scale]phvr7t}{}% \DeclareFontShape{OT1}{helvetica}{m}{it}{<->s*[\helv@scale]phvro7t}{}% \DeclareFontShape{OT1}{helvetica}{m}{sc}{<->s*[\helv@scale]phvrc7t}{}% \DeclareFontShape{OT1}{helvetica}{b}{n}{<->s*[\helv@scale]phvb7t}{}% \DeclareFontShape{OT1}{helvetica}{b}{it}{<->s*[\helv@scale]phvbo7t}{}% \DeclareFontShape{OT1}{helvetica}{m}{sl}{<->s*[\helv@scale]phvro7t}{}% \DeclareFontShape{OT1}{helvetica}{b}{sc}{<->s*[\helv@scale]phvbc8t}{}% \DeclareFontShape{OT1}{helvetica}{b}{sl}{<->s*[\helv@scale]phvbo7t}{}% \DeclareFontShape{OT1}{helvetica}{bx}{n}{<->s*[\helv@scale]phvb7t}{}% \DeclareFontShape{OT1}{helvetica}{bx}{it}{<->s*[\helv@scale]phvbo7t}{}% \DeclareFontShape{OT1}{helvetica}{bx}{sc}{<->s*[\helv@scale]phvbc8t}{}% \DeclareFontShape{OT1}{helvetica}{bx}{sl}{<->s*[\helv@scale]phvbo7t}{}% %%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%% Font Defined %%%%%%%%%%%%%%%%% \def\@xipt{11} \def\@xviiipt{18} \def\@xxivpt{24} \newcommand\ContributorAffiliationFont{\reset@font\fontsize{10}{12}\raggedright\selectfont} \newcommand\ContributorNameFont{\reset@font\fontsize{10}{12}\bfseries\raggedright\selectfont} \newcommand\TitlePageTitleFont{\fontsize{24}{28}\slshape\bfseries\selectfont} \newcommand\PageNumFont{\reset@font\fontsize{10}{12}\selectfont} \newcommand\ChapNumFont{\reset@font\fontsize{24}{24}\bfseries\selectfont} \newcommand\ChapTitleFont{\reset@font\fontsize{18}{20}\slshape\selectfont} \newcommand\SectionHeadFont{\fontsize{12}{14}\bfseries\selectfont} \newcommand\SubsectionHeadFont{\fontsize{11}{13}\bfseries\selectfont} \newcommand\SubsubsectionHeadFont{\fontsize{10}{12}\bfseries\selectfont} \newcommand\ParagraphHeadFont{\fontsize{10}{12}\itshape\selectfont} \newcommand\SubParagraphHeadFont{\fontsize{10}{12}\itshape\selectfont} \newcommand\FMHeadFont{\reset@font\fontsize{18}{20}\slshape\bfseries\selectfont} \newcommand\RunningHeadFont{\fontsize{10}{12}\itshape\selectfont} \newcommand\NameFont{\fontsize{10}{12}\itshape\selectfont} \newcommand\AffiliationFont{\fontsize{8}{10}\selectfont} \newcommand\FigCapFont{\fontsize{10}{12}\bfseries\selectfont} \newcommand\FigCapBIFont{\fontsize{10}{12}\bfseries\itshape\selectfont} \newcommand\TableColHeadFont{\fontsize{10}{12}\bfseries\selectfont} \newcommand\TableTitleFont{\fontsize{10}{12}\selectfont} \newcommand\TableNumberFont{\fontsize{11}{13}\bfseries\selectfont} \newcommand\TableBodyFont{\reset@font\fontsize{9}{11}\selectfont} \newcommand\TableSubheadFont{\reset@font\fontsize{9}{11}\selectfont} \newcommand\TableFootnoteFont{\reset@font\fontsize{8}{10}\selectfont} \newcommand\CAPlusOneFont{\fontsize{10}{12}\bfseries\selectfont} \newcommand\CAAPlusOneFont{\fontsize{10}{12}\itshape\selectfont} \newcommand\tocfont{\fontsize{10}{12}\selectfont} \newcommand\extraFont{\fontsize{24}{28}\selectfont} \newcommand\VfFont{\fontsize{10}{12}\selectfont} %%%%%%%%%%%%%%%%% \input{bk1\@ptsize.clo} \setlength\lineskip{1\p@} \setlength\normallineskip{1\p@} \renewcommand\baselinestretch{} \setlength\parskip{0\p@ \@plus \p@} \@lowpenalty 51 \@medpenalty 151 \@highpenalty 301 \@beginparpenalty -\@lowpenalty \@endparpenalty -\@lowpenalty \@itempenalty -\@lowpenalty % \clubpenalty=0 % 'Club line' at bottom of page. \widowpenalty=10000 % 'Widow line' at top of page. \setcounter{topnumber}{2} \renewcommand\topfraction{.7} \setcounter{bottomnumber}{1} \renewcommand\bottomfraction{.3} \setcounter{totalnumber}{3} \renewcommand\textfraction{.2} \renewcommand\floatpagefraction{.5} \setcounter{dbltopnumber}{2} \renewcommand\dbltopfraction{.7} \renewcommand\dblfloatpagefraction{.5} % **************************************** % * PAGE LAYOUT * % **************************************** % % All margin dimensions measured from a point one inch from top and side % of page. % % SIDE MARGINS: % \oddsidemargin 6pc %5pc \evensidemargin 5.7pc %5pc \marginparwidth 4pc \marginparsep 1pc \topmargin 12pt %0pt \headheight 12pt \headsep 12pt \footskip 2pc % % DIMENSION OF TEXT: \newdimen\trimheight \newdimen\trimwidth \newdimen\normaltextheight \newdimen\tempa \newdimen\tempdimen % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Parameter Initializaton %%%%%%%%%%%%%%%%%%%%%%%%%% % \newdimen\htrim \newdimen\vtrimtop \newdimen\vtrimbot \setlength\trimheight{9in} \setlength\trimwidth{6in} % % \if@krantza \textheight = 45pc %\advance\textheight by \topskip \addtolength\textheight{3pt} \textwidth 28pc \addtolength\textwidth{.5pt} \topmargin0in \oddsidemargin1.1875in \evensidemargin1.1875in \htrim.7365in \vtrimtop1.068in \vtrimbot1.068in \hoffset-15pt \voffset39pt \let\normaltextheight\textheight \else\if@krantzb \textheight = 51pc % \advance\textheight by \topskip \textwidth 33pc \topmargin0in \oddsidemargin.5in \evensidemargin.5in \htrim.75in \vtrimtop.8607in \vtrimbot1.027in \hoffset-.1in \voffset-.15in%.04in \let\normaltextheight\textheight \else %%%Uncomment to get 6x9 trim %%%%\textheight = 43pc %%%% %\advance\textheight by \topskip %%%%\addtolength\textheight{3pt} %%%% \textwidth 26pc %%%%\addtolength\textwidth{.5pt} %%%% \topmargin0in %%%% \oddsidemargin1.1875in %%%% \evensidemargin1.1875in %%%% \htrim5.05pc %%%% \vtrimtop7.7pc %%%% \vtrimbot5.44pc %%%%% \hoffset-5pt %%%% \voffset45pt %%%%\let\normaltextheight\textheight \textheight = 45pc %\advance\textheight by \topskip \addtolength\textheight{3pt} \textwidth 28pc \addtolength\textwidth{.5pt} \topmargin0in \oddsidemargin1.1875in \evensidemargin1.1875in \htrim.7365in \vtrimtop1.068in \vtrimbot1.068in \hoffset-15pt \voffset39pt \let\normaltextheight\textheight \fi \fi % \columnsep 1pc \columnseprule 0pt % % FOOTNOTES % \footnotesep 6.65pt \skip\footins 12pt plus 3pt minus 1.5pt % %%%% Trim marks %%%%%%%%%%% \newsavebox\ul@box \newsavebox\ur@box \newsavebox\ll@box \newsavebox\lr@box \def\top@cornermarks{% \hskip-\htrim \vbox to 0\p@{\vskip-\vtrimtop\llap{\copy\ul@box}\vss}% \vbox to 0\p@{\vskip-\vtrimtop\rlap{\hskip\textwidth\hskip2\htrim\copy\ur@box}\vss}% \vbox to 0\p@{\vskip\textheight\vskip\vtrimbot\llap{\copy\ll@box}\vss}% \vbox to 0\p@{\vskip\textheight\vskip\vtrimbot\rlap{\hskip\textwidth\hskip2\htrim\copy\lr@box}\vss}% \hskip\htrim} \def\make@cornermarks{% \sbox\ul@box{\rule{18\p@}{.25\p@}\hskip8\p@\hbox to.25\p@{\vbox to26\p@{\noindent\rule{.25\p@}{18\p@}}}}% \sbox\ur@box{\hbox to.25\p@{\vbox to26\p@{\noindent\rule{.25\p@}{18\p@}}}\hskip8\p@\rule{18\p@}{.25\p@}}% \sbox\ll@box{\rule{18\p@}{.25\p@}\hskip8\p@\lower34\p@\hbox to.25\p@{\vbox to26\p@{\noindent\rule{.25\p@}{18\p@}}}}% \sbox\lr@box{\lower34\p@\hbox to.25\p@{\vbox to26\p@{\noindent\rule{.25\p@}{18\p@}}}\hskip8\p@\rule{18\p@}{.25\p@}}} %%%%%%%%%%%%%%%%%%%% End Trim Marks %%%%%%%%%%%% \def\ps@plain{\let\@mkboth\@gobbletwo \let\@oddhead\top@cornermarks%\@empty \def\@oddfoot{\reset@font\hfil\thepage \hfil}\let\@evenhead\@empty\let\@evenfoot\@oddfoot} \def\even@head{% \top@cornermarks {\@the@page\RunningHeadFont \hfill \if@mainmatter\ifnum\value{chapter}>0 \thechapter\enspace\fi\fi\leftmark }} \def\odd@head{% \top@cornermarks \hfil{\RunningHeadFont \if@mainmatter\ifnum\value{section}>0 \thesection\enspace\fi\fi\rightmark } \hfill \@the@page } \def\@the@page{{\PageNumFont\thepage}} \if@twoside \def\ps@headings{% \let\@mkboth\@gobbletwo \if@pdf \let\@evenhead\@empty \let\@oddhead\@empty \def\@oddfoot{\@cip\hfil}% \def\@evenfoot{\@cip\hfil}% \else \let\@oddfoot\@empty \let\@evenfoot\@empty \let\@evenhead\even@head \def\@oddhead{\RunningHeadFont\if@mainmatter\ifnum\value{section}>0 \enspace\fi\fi\rightmark\hfill\PageNumFont\thepage}%\odd@head \fi } \else \def\ps@headings{\let\@mkboth\@gobbletwo% \if@pdf \let\@evenhead\@empty \let\@oddhead\@empty \def\@oddfoot{\@cip\hfil}% \def\@evenfoot{\@cip\hfil}% \else \let\@oddfoot\@empty \let\@evenfoot\@empty \let\@evenhead\even@head \let\@oddhead\odd@head \fi } \fi \def\ps@myheadings{% \let\@oddfoot\@empty\let\@evenfoot\@empty \def\@evenhead{\thepage\hfil\slshape\leftmark}% \def\@oddhead{{\slshape\rightmark}\hfil\thepage}% \let\@mkboth\@gobbletwo \let\chaptermark\@gobble \let\sectionmark\@gobble } \def\ps@empty{% \let\@mkboth\@gobbletwo \if@pdf \let\@evenhead\@empty \let\@oddhead\@empty \def\@oddfoot{\@cip\hfil}% \def\@evenfoot{\@cip\hfil}% \else \make@cornermarks \let\@oddhead\top@cornermarks \let\@evenhead\top@cornermarks \let\@oddfoot\@empty \let\@evenfoot\@empty \fi } \def\ps@folio{% \let\@mkboth\@gobbletwo \if@pdf \let\@evenhead\@empty \let\@oddhead\@empty \def\@oddfoot{\@cip\hfil}% \def\@evenfoot{\@cip\hfil}% \else \let\@oddhead\top@cornermarks \def\@oddfoot{% \parindent\z@ \baselineskip7\p@ \hbox{% \textwidth\@ciprulewidth \vbox{% \if@cip\rule{\@ciprulewidth}{.25pt}\par \hbox{\vbox{\noindent\copy\@cipboxa\par\noindent\copy\@cipboxb}}\fi}} \hfill\@the@page} \let\@evenhead\top@cornermarks%\odd@head \let\@evenfoot\@oddfoot \fi } \newcommand\HeadingsBookChapter{% \def\chaptermark##1{% \markboth{\@title}{% ##1}}% \def\sectionmark##1{}} \def\HeadingsChapterSection{% \def\chaptermark##1{% \markboth{% ##1}{}}% \def\sectionmark##1{%\pagebreak[3]% \markright{% \thesection\enspace##1}}} \def\pdfon{\@pdftrue} \def\pdfoff{\@pdffalse} \if@pdf \def\@cip{{\fontsize{6\p@}{8\p@}\selectfont\copyright 2001 by CRC Press LLC}} \else \newsavebox\@cipboxa \newsavebox\@cipboxb \newdimen\@ciprulewidth \def\@cip#1#2{% \sbox\@cipboxa{\fontsize{6\p@}{8\p@}\selectfont #1}% \sbox\@cipboxb{\fontsize{6\p@}{8\p@}\selectfont #2}% \@ciprulewidth\wd\@cipboxa \ifnum\@ciprulewidth<\wd\@cipboxb\@ciprulewidth\wd\@cipboxb\fi}% \fi \if@pdf \else \AtBeginDocument{% \@cip{\rule{0pt}{9pt}0-8493-0052-5/00/\$0.00+\$.50}% {\copyright\ \ 2001 by CRC Press LLC}}% \fi \if@titlepage \newcommand\maketitle{\begin{titlepage}% \let\footnotesize\small \let\footnoterule\relax \let \footnote \thanks {\parindent \z@ \raggedright \baselineskip \z@ \lineskip \z@ \parskip \z@ \vbox{ \vskip -7bp {\baselineskip 10bp\lineskip 10bp\NameFont\uppercase{\@author}\par} \vskip 6bp \AffiliationFont \@affiliation \vskip -2bp \crcrule \vskip 22bp {\baselineskip 24bp\lineskip 24bp\TitlePageTitleFont\@title\par}}} \@thanks \vfil\null \end{titlepage}% \setcounter{footnote}{0}% \global\let\thanks\relax \global\let\maketitle\relax \global\let\@thanks\@empty \global\let\@author\@empty \global\let\@date\@empty % \global\let\@title\@empty \global\let\title\relax \global\let\author\relax \global\let\date\relax \global\let\and\relax } \else \newcommand\maketitle{\par \begingroup \renewcommand\thefootnote{\@fnsymbol\c@footnote}% \def\@makefnmark{\rlap{\@textsuperscript{\normalfont\@thefnmark}}}% \long\def\@makefntext##1{\parindent 1em\noindent \hb@xt@1.8em{% \hss\@textsuperscript{\normalfont\@thefnmark}}##1}% \if@twocolumn \ifnum \col@number=\@ne \@maketitle \else \twocolumn[\@maketitle]% \fi \else \newpage \global\@topnum\z@ % Prevents figures from going at top of page. \@maketitle \fi \thispagestyle{empty}\@thanks \endgroup \setcounter{footnote}{0}% \global\let\thanks\relax \global\let\maketitle\relax \global\let\@maketitle\relax \global\let\@thanks\@empty \global\let\@author\@empty \global\let\@date\@empty \global\let\@title\@empty \global\let\title\relax \global\let\author\relax \global\let\date\relax \global\let\and\relax } \def\@maketitle{% \newpage \null \vskip 2em% {\parindent \z@ \raggedright \baselineskip \z@ \lineskip \z@ \parskip \z@ \vbox{ \vskip -7bp {\baselineskip 10bp\lineskip 10bp\NameFont\uppercase{\@author}\par} \vskip 6bp \AffiliationFont \@affiliation \vskip 10bp \crcrule \vskip 26bp {\baselineskip 24bp\lineskip 24bp\TitlePageTitleFont\@title\par}}} \par \vskip 1.5em} \fi %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newcommand*\chaptermark[1]{} \setcounter{secnumdepth}{3} \newcounter {part} \newcounter {chapter} \newcounter {section}[chapter] \newcounter {subsection}[section] \newcounter {subsubsection}[subsection] \newcounter {paragraph}[subsubsection] \newcounter {subparagraph}[paragraph] \renewcommand \thepart {\@Roman\c@part} \renewcommand \thechapter {\@arabic\c@chapter} \renewcommand \thesection {\thechapter.\@arabic\c@section} \renewcommand\thesubsection {\thesection.\@arabic\c@subsection} \renewcommand\thesubsubsection{\thesubsection .\@arabic\c@subsubsection} \renewcommand\theparagraph {\thesubsubsection.\@arabic\c@paragraph} \renewcommand\thesubparagraph {\theparagraph.\@arabic\c@subparagraph} \newcommand\@chapapp{\chaptername} \newcommand\frontmatter{% \cleardoublepage \@mainmatterfalse \pagenumbering{roman}} \newcommand\mainmatter{% \cleardoublepage \@mainmattertrue \pagenumbering{arabic}} \newcommand\backmatter{% \if@openright \cleardoublepage \else \clearpage \fi \@mainmatterfalse} \newcommand\part{\make@cornermarks% \if@openright \cleardoublepage \else \clearpage \fi \thispagestyle{empty}% \if@twocolumn \onecolumn \@tempswatrue \else \@tempswafalse \fi \null\vfil \secdef\@part\@spart} \def\@part[#1]#2{% \ifnum \c@secnumdepth >-2\relax \refstepcounter{part}% \addcontentsline{toc}{part}{\thepart\hspace{1em}#1}% \else \addcontentsline{toc}{part}{#1}% \fi \markboth{}{}% {\centering \interlinepenalty \@M \normalfont \ifnum \c@secnumdepth >-2\relax \huge\bfseries \partname\nobreakspace\thepart \par \vskip 20\p@ \fi \Huge \bfseries #2\par}% \@endpart} \def\@spart#1{% {\centering \interlinepenalty \@M \normalfont \Huge \bfseries #1\par}% \@endpart} \def\@endpart{\vfil\newpage \if@twoside \if@openright \null \thispagestyle{empty}% \newpage \fi \fi \if@tempswa \twocolumn \fi} \if@ChapterTOCs \newwrite\@chaptoc \def\secnumwidth{21pt}\def\subsecnumwidth{30pt}\def\ssubsecnumwidth{36pt}\def\subsubsecnumwidth{66pt}\fi \long\def\@trplarg#1{\@ifnextchar[{\@xtrplarg{#1}}{\@ztrplarg{#1}}} \long\def\@xtrplarg#1[#2]{\@ifnextchar[{#1[#2]}{\@ytrplarg{#1}[{#2}]}} \long\def\@ytrplarg#1[#2]#3{#1[{#2}][{#2}]{#3}} \long\def\@ztrplarg#1#2{#1[{#2}][{#2}]{#2}} \newcommand\chapter{\if@openright\cleardoublepage\else\clearpage\fi \make@cornermarks \cleardoublepage \if@ChapterTOCs\if@filesw\immediate\closeout\@chaptoc\fi\fi \pagestyle{headings}% \thispagestyle{folio}% \if@ChapterResetsPage\global\c@page\@ne\fi \global\@topnum\z@ \gdef\chapterauthor{\@ca}% \gdef\endchapterauthors{\end@cas}% \@afterindentfalse \secdef\@chapter\@schapter %%% \@ifstar{\@schapter}{\@trplarg{\@chapter}} } \def\@chapter[#1]#2{% \ifnum\c@secnumdepth>\m@ne \if@mainmatter \refstepcounter{chapter}% \typeout{\@chapapp\space\thechapter.}% \addcontentsline{toc}{chapter}{\protect\numberline{\thechapter}#1}% \else \addcontentsline{toc}{chapter}{#1}\fi \else \addcontentsline{toc}{chapter}{#1}\fi \chaptermark{% #2}% \addtocontents{lof}{\protect\addvspace{10\p@}}% \addtocontents{lot}{\protect\addvspace{10\p@}}% \if@twocolumn \@topnewpage[\@makechapterhead{#2}]% \else \@makechapterhead{#2}% \@afterheading\fi \if@ChapterTOCs\if@filesw\immediate\openout\@chaptoc\thechapter.toc\fi\fi } \def\@makechapterhead#1{% {\parindent \z@ \raggedright \baselineskip \z@ \lineskip \z@ \parskip \z@ \vbox{ \vskip -2\p@ \ChapNumFont %Remove comment if "Chapter" word required before Number %\if@chapnumonly\else % \@chapapp\ %\fi \thechapter \vskip -15\p@ \chap@rule \vskip 6\p@ {\baselineskip 20\p@\lineskip 20\p@\ChapTitleFont #1\par\vskip-15pt}% \noindent\hbox{\vrule height.5pt width84pt} \vskip28\p@} \if@ChapterTOCs \make@chaptoc \else \fi \vskip 19.3\p@} \def\theequation{\thechapter.\arabic{equation}}}% \def\@schapter#1{\if@twocolumn \@topnewpage[\@makeschapterhead{#1}]% \else \@makeschapterhead{#1}% \addcontentsline{toc}{fm}{#1} \markboth{#1}{#1} \@afterheading \fi} \def\@makeschapterhead#1{% {\parindent \z@ \raggedright \baselineskip 6\p@ \lineskip \z@ \parskip \z@ \vbox{ \vskip 22\p@ \unnumchap@rule \vskip 5\p@ \FMHeadFont #1\par\vskip-12pt \noindent\hbox{\vrule height.5pt width84pt} \vskip 41\p@}}% \def\theequation{\thechapter.\arabic{equation}}} %%Change mydotted also \newdimen\secwd \newdimen\subsecwd \newdimen\subsubsecwd \def\secwd{31pt} \def\subsecwd{36pt} \def\subsubsecwd{46pt} \def\ssubnumberline#1{\@hangfrom{\hbox to \secwd{#1\hfill}}} \def\subnumberline#1{\@hangfrom{\hskip\subsecnumwidth\hbox to \subsecwd{#1\hfill}}} \def\subsubnumberline#1{\@hangfrom{\hskip\subsubsecnumwidth\hbox to \subsubsecwd{#1\hfill}}} \newcommand\section{% \gdef\chapterauthor{\@caplusone}% \gdef\endchapterauthors{\end@casplusone}% \@ifstar{\@ssection}{\@trplarg{\@section}}} \def\@ssection#1{% \if@ChapterTOCs \myaddcontentsline{\@chaptoc}{chapsection}{\string\makebox[\secnumwidth][l]{}#1}\fi \@startsection{section}{1}{\z@}{-30\p@}{6\p@}{\sec@rule\nopagebreak\vskip9.5\p@\nopagebreak\SectionHeadFont}*{#1}} \def\@section[#1][#2]#3{% \if@ChapterTOCs \addtocounter{section}{1}% \myaddcontentsline{\@chaptoc}{chapsection}{\protect\ssubnumberline{\thesection}#1}% \addtocounter{section}{-1}\fi \@startsection{section}{1}{\z@}{-30\p@}{6\p@}{\sec@rule\nopagebreak\vskip9.5\p@\nopagebreak\SectionHeadFont}[#2]{#3}} \def\sectionauthor#1{\hfill{\ChapTOCAuthorFont #1}} \newcommand\subsection{\@ifstar{\@ssubsection}{\@trplarg{\@subsection}}} \def\@ssubsection#1{% \if@ChapterTOCs \myaddcontentsline{\@chaptoc}{chapsubsection}{\string\makebox[\subsecnumwidth][l]{}#1}\fi \@startsection{subsection}{2}{\z@}{-18\p@}{6\p@}{% \SubsectionHeadFont}*{#1}} \def\@subsection[#1][#2]#3{% \if@ChapterTOCs \addtocounter{subsection}{1}% \myaddcontentsline{\@chaptoc}{chapsubsection}{\protect\subnumberline{\thesubsection}#1}% \addtocounter{subsection}{-1}\fi \@startsection{subsection}{2}{\z@}{-18\p@}{6\p@}{% \SubsectionHeadFont}[#2]{#3}} \newcommand\subsubsection{\@ifstar{\@ssubsubsection}{\@trplarg{\@subsubsection}}} \def\@ssubsubsection#1{% \if@ChapterTOCs \myaddcontentsline{\@chaptoc}{chapsubsubsection}{\string\makebox[\subsecnumwidth][l]{}#1}\fi \@startsection{subsubsection}{3}{\z@}{-12\p@}{6\p@}{% \SubsubsectionHeadFont}*{#1}} \def\@subsubsection[#1][#2]#3{% \if@ChapterTOCs \addtocounter{subsubsection}{1}% \myaddcontentsline{\@chaptoc}{chapsubsubsection}{\protect\subsubnumberline{\thesubsubsection}#1}% \addtocounter{subsubsection}{-1}\fi \@startsection{subsubsection}{3}{\z@}{-12\p@}{6\p@}{% \SubsubsectionHeadFont}[#2]{#3}} \newcommand\paragraph{\@startsection{paragraph}{4}{\z@}% {-12\p@}{6\p@}{\ParagraphHeadFont}} \newcommand\subparagraph{\@startsection{subparagraph}{5}{\parindent}% {-12\p@}{6\p@}{\SubParagraphHeadFont}} \if@twocolumn \setlength\leftmargini {2em} \else \setlength\leftmargini {2.5em} \fi \leftmargin \leftmargini \setlength\leftmarginii {2.2em} \setlength\leftmarginiii {1.87em} \setlength\leftmarginiv {1.7em} \if@twocolumn \setlength\leftmarginv {.5em} \setlength\leftmarginvi {.5em} \else \setlength\leftmarginv {1em} \setlength\leftmarginvi {1em} \fi \setlength \labelsep {.5em} \setlength \labelwidth{\leftmargini} \addtolength\labelwidth{-\labelsep} \@beginparpenalty -\@lowpenalty \@endparpenalty -\@lowpenalty \@itempenalty -\@lowpenalty \renewcommand\theenumi{\@arabic\c@enumi} \renewcommand\theenumii{\@alph\c@enumii} \renewcommand\theenumiii{\@roman\c@enumiii} \renewcommand\theenumiv{\@Alph\c@enumiv} \newcommand\labelenumi{\theenumi.} \newcommand\labelenumii{(\theenumii)} \newcommand\labelenumiii{\theenumiii.} \newcommand\labelenumiv{\theenumiv.} \renewcommand\p@enumii{\theenumi} \renewcommand\p@enumiii{\theenumi(\theenumii)} \renewcommand\p@enumiv{\p@enumiii\theenumiii} \newcommand\labelitemi{\textbullet} \newcommand\labelitemii{\normalfont\bfseries \textendash} \newcommand\labelitemiii{\textasteriskcentered} \newcommand\labelitemiv{\textperiodcentered} \newenvironment{description} {\list{}{\labelwidth\z@ \itemindent-\leftmargin \let\makelabel\descriptionlabel}} {\endlist} \newcommand*\descriptionlabel[1]{\hspace\labelsep \normalfont\bfseries #1} \newenvironment{verse} {\let\\\@centercr \list{}{\itemsep \z@ \itemindent -1.5em% \listparindent\itemindent \rightmargin \leftmargin \advance\leftmargin 1.5em}% \item\relax} {\endlist} \newenvironment{quotation} {\list{}{\listparindent 1.5em% \itemindent \listparindent \rightmargin \leftmargin \parsep \z@ \@plus\p@}% \item\relax} {\endlist} \newenvironment{quote} {\list{}{\rightmargin\leftmargin}% \item\relax} {\endlist} \if@compatibility \newenvironment{titlepage} {% \cleardoublepage \if@twocolumn \@restonecoltrue\onecolumn \else \@restonecolfalse\newpage \fi \thispagestyle{empty}% \setcounter{page}\z@ }% {\if@restonecol\twocolumn \else \newpage \fi } \else \newenvironment{titlepage} {% \cleardoublepage \if@twocolumn \@restonecoltrue\onecolumn \else \@restonecolfalse\newpage \fi \thispagestyle{empty}% \setcounter{page}\@ne }% {\if@restonecol\twocolumn \else \newpage \fi \if@twoside\else \setcounter{page}\@ne \fi } \fi \newcommand\appendix{\par \setcounter{chapter}{0}% \setcounter{section}{0}% \gdef\@chapapp{\appendixname}% \gdef\thechapter{\@Alph\c@chapter}} \setlength\arraycolsep{5\p@} \setlength\tabcolsep{6\p@} \setlength\arrayrulewidth{.4\p@} \setlength\doublerulesep{2\p@} \setlength\tabbingsep{\labelsep} \skip\@mpfootins = \skip\footins \setlength\fboxsep{3\p@} \setlength\fboxrule{.4\p@} \@addtoreset {equation}{chapter} \renewcommand\theequation {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@equation} \newcounter{figure}[chapter] \renewcommand \thefigure {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@figure} \def\fps@figure{tbp} \def\ftype@figure{1} \def\ext@figure{lof} \def\fnum@figure{\figurename\nobreakspace\thefigure} \newenvironment{figure} {\@float{figure}} {\end@float} \newenvironment{figure*} {\@dblfloat{figure}} {\end@dblfloat} \newcounter{table}[chapter] \renewcommand \thetable {\ifnum \c@chapter>\z@ \thechapter.\fi \@arabic\c@table} \def\fps@table{tbp} \def\ftype@table{2} \def\ext@table{lot} \def\fnum@table{\tablename\nobreakspace\thetable} \newenvironment{table} {\@float{table}} {\end@float} \newenvironment{table*} {\@dblfloat{table}} {\end@dblfloat} \newlength\abovecaptionskip \newlength\belowcaptionskip \setlength\abovecaptionskip{10\p@} \setlength\belowcaptionskip{0\p@} \long\def\@makecaption#1#2{% \vskip\abovecaptionskip \sbox\@tempboxa{#1: #2}% \ifdim \wd\@tempboxa >\hsize {\FigCapFont #1} #2\par \else \global \@minipagefalse % \hb@xt@\hsize{\hfil\box\@tempboxa\hfil}% {\FigCapFont #1} #2\par \fi \vskip\belowcaptionskip} \DeclareOldFontCommand{\rm}{\normalfont\rmfamily}{\mathrm} \DeclareOldFontCommand{\sf}{\normalfont\sffamily}{\mathsf} \DeclareOldFontCommand{\tt}{\normalfont\ttfamily}{\mathtt} \DeclareOldFontCommand{\bf}{\normalfont\bfseries}{\mathbf} \DeclareOldFontCommand{\it}{\normalfont\itshape}{\mathit} \DeclareOldFontCommand{\sl}{\normalfont\slshape}{\@nomath\sl} \DeclareOldFontCommand{\sc}{\normalfont\scshape}{\@nomath\sc} \DeclareRobustCommand*\cal{\@fontswitch\relax\mathcal} \DeclareRobustCommand*\mit{\@fontswitch\relax\mathnormal} \newcommand\@pnumwidth{1.55em} \newcommand\@tocrmarg{2.55em} \newcommand\@dotsep{4.5} \setcounter{tocdepth}{3} \newcounter{numauthors} \newif\if@break \newif\if@firstauthor \newcommand\tableofcontents{\cleardoublepage\markboth{Contents}{Contents}% \make@cornermarks \gdef\chapterauthor{\@caplusone}% \gdef\endchapterauthors{\end@casplusone}% \if@twocolumn \@restonecoltrue\onecolumn \else \@restonecolfalse \fi {\parindent \z@ \raggedright \baselineskip 6\p@ \lineskip \z@ \parskip \z@ \vbox{ \vskip 22\p@ \unnumchap@rule \vskip 5\p@ \FMHeadFont \contentsname\par\vskip-12pt \noindent\hbox{\vrule height.5pt width84pt} \vskip 41\p@}} \pagestyle{headings}\thispagestyle{folio} {\let\break\space \let\author\toc@author \reset@authors \let\toc@draw\relax \@starttoc{toc} } \if@restonecol\twocolumn\fi } \def\draw@part#1#2{% \addpenalty{-\@highpenalty}% \vskip1em plus\p@ \@tempdima1.5em \begingroup \parindent\z@\rightskip\@pnumwidth \parfillskip-\rightskip \bfseries \leavevmode \advance\leftskip\@tempdima \hskip-\leftskip {#1\hfil}\nobreak \if@pdf \else \hfil\nobreak\hb@xt@\@pnumwidth{\hss #2}% \fi \par \penalty\@highpenalty\endgroup} \let\toc@draw\relax % \def\l@part#1#2{% \toc@draw \gdef\toc@draw{\draw@part{\large #1}{\large #2}}} \newcommand*\l@fm[2]{% \ifnum \c@tocdepth >\m@ne \addpenalty{-\@highpenalty}% \vskip 1.0em \@plus\p@ \setlength\@tempdima{1.5em}% \begingroup \parindent \z@ \rightskip \@pnumwidth \parfillskip -\@pnumwidth \leavevmode \bfseries \advance\leftskip\@tempdima \hskip -\leftskip #1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par \penalty\@highpenalty \endgroup \fi} \newcommand*\l@chapter[2]{% \ifnum \c@tocdepth >\m@ne \addpenalty{-\@highpenalty}% \vskip 1.0em \@plus\p@ \setlength\@tempdima{1.5em}% \begingroup \parindent \z@ \rightskip \@pnumwidth \parfillskip -\@pnumwidth \leavevmode \bfseries \advance\leftskip\@tempdima \hskip -\leftskip #1\nobreak\hfil \nobreak\hb@xt@\@pnumwidth{\hss #2}\par {\it\draw@authors}% \penalty\@highpenalty \endgroup \fi} \def\toc@author#1#2{% \if@firstauthor \@firstauthorfalse \else \ifx\@authors\@empty \xdef\@authors{\last@author}% \else \@cons{\@authors}{, \last@author}\fi\fi \stepcounter{numauthors}% %%%%%%% commented and deleted below the second part to aviod inaccessible error % shashi % September-2008 %% \gdef\last@author{#1 {\rm\fontsize{9\p@}{11\p@}\selectfont #2}} \gdef\last@author{#1} } \def\draw@authors{% \let\@t\@authors \ifx\@t\@empty \let\@t\last@author\fi \ifx\@t\@empty\else \hskip\leftskip \ifx\@authors\@empty \else \@authors \ifnum\c@numauthors>2,\fi \if@break\break\fi \ and \fi \last@author\break\fi \reset@authors} \def\reset@authors{% \gdef\@authors{}% \gdef\last@author{}% \@firstauthortrue \setcounter{numauthors}{0}} \newlength\section@toc@skip \section@toc@skip1.5em \newlength\SectionTOCWidth \SectionTOCWidth2.3em \newcommand*\l@section{\@dottedtocline{1}{1.5em}{2.3em}} \newcommand*\l@subsection{\@dottedtocline{2}{3.8em}{3.2em}} \newcommand*\l@subsubsection{\@dottedtocline{3}{7.0em}{4.1em}} \newlength\subsection@toc@skip \subsection@toc@skip\section@toc@skip \advance\subsection@toc@skip\SectionTOCWidth \newlength\SubSectionTOCWidth \SubSectionTOCWidth3.2em \newlength\subsubsection@toc@skip \subsubsection@toc@skip\subsection@toc@skip \advance\subsubsection@toc@skip\SubSectionTOCWidth \newlength\SubSubSectionTOCWidth \SubSubSectionTOCWidth4.1em \newlength\paragraph@toc@skip \paragraph@toc@skip\subsubsection@toc@skip \advance\paragraph@toc@skip\SubSubSectionTOCWidth \newlength\ParagraphTOCWidth \ParagraphTOCWidth4.1em \def\l@paragraph#1#2{% \toc@draw \gdef\toc@draw{\draw@paragraph{#1}{#2}}} \def\draw@paragraph#1#2{% \@dottedtocline{4}{\paragraph@toc@skip}{\ParagraphTOCWidth}{#1}{{ \tocfont #2}}} \newlength\subparagraph@toc@skip \subparagraph@toc@skip\paragraph@toc@skip \advance\subparagraph@toc@skip\ParagraphTOCWidth \def\l@subparagraph#1#2{% \toc@draw \gdef\toc@draw{\draw@subparagraph{#1}{#2}}} \def\draw@subparagraph#1#2{% \@dottedtocline{5}{\subparagraph@toc@skip}{6em}{#1}{{ \tocfont #2}}} \def\@dottedtocline#1#2#3#4#5{% \ifnum #1>\c@tocdepth \else \vskip \z@ \@plus.2\p@ {\leftskip #2\relax\rightskip\@tocrmarg\parfillskip-\rightskip \parindent #2\relax\@afterindenttrue \interlinepenalty\@M \leavevmode \@tempdima #3\relax \advance\leftskip\@tempdima\null\hskip-\leftskip {#4\hfil}\nobreak \if@pdf \else \leaders\hbox{$\m@th\mkern\@dotsep mu\hbox{.}\mkern\@dotsep mu$}\hfill \nobreak \hb@xt@\@pnumwidth{\hfil\normalfont\normalcolor #5}% \fi \par}\fi} \newcommand\chapterauthors{% \def\break{\string\break\ }% \def\protect##1{\string ##1 }} \def\end@cas{} \def\end@casplusone{\vskip4pt\@doendpe} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\make@chaptoc{% chapter author {\parindent\z@ \newcommand\FolioBoldFont{}% \let\@b\bullet \def\bullet{\raisebox{2pt}{$\scriptscriptstyle\@b$}}% \let\SubsectionItalicFont\it {\rm\fontsize{10\p@}{10\p@}\bfseries\selectfont \ifnum\c@numauthors=1 \chapter@authorone\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationone}\vskip12\p@ \fi \ifnum\c@numauthors=2 \chapter@authorone\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationone}\vskip12\p@ \chapter@authortwo\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationtwo} \fi \ifnum\c@numauthors=3 \chapter@authorone\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationone}\vskip12\p@ \chapter@authortwo\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationtwo}\vskip12\p@ \chapter@authorthree\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationthree} \fi \ifnum\c@numauthors=4 \chapter@authorone\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationone}\vskip12\p@ \chapter@authortwo\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationtwo}\vskip12\p@ \chapter@authorthree\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationthree}\vskip12\p@ \chapter@authorfour\vskip6\p@ {\it\fontsize{10\p@}{10\p@}\selectfont\chapter@affiliationfour} \fi } \gdef\chapter@authorone{}\gdef\chapter@affiliationone{}% \gdef\chapter@authortwo{}\gdef\chapter@affiliationtwo{}% \gdef\chapter@authorthree{}\gdef\chapter@affiliationthree{}% \gdef\chapter@authorfour{}\gdef\chapter@affiliationfour{}% \vskip 14.6\p@ {\leftskip\secnumwidth\def\author##1##2{}\vskip14pt\hbox{\leftskip0pt\SubsectionHeadFont CONTENTS}\vskip6pt\par\@input{\thechapter.toc}\par}% } \reset@authors} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newif\iffinishedfromone \global\finishedfromonefalse % \newif\iffinishedfromtwo \global\finishedfromtwofalse % \newif\iffinishedfromthree \global\finishedfromthreefalse % \newif\iffinishedfromfour \global\finishedfromfourfalse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % \newcommand\singleauthorchapter{\finishedfromonetrue} \newcommand\twoauthorchapter{\finishedfromtwotrue} \newcommand\threeauthorchapter{\finishedfromthreetrue} \newcommand\fourauthorchapter{\finishedfromfourtrue} % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newif\iffinish \global\finishfalse %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newsavebox\@AUonebox \newsavebox\@AUtwobox \newsavebox\@AUthreebox \newsavebox\@AUfourbox % \newsavebox\@AUaffonebox \newsavebox\@AUafftwobox \newsavebox\@AUaffthreebox \newsavebox\@AUafffourbox % \newsavebox\@finalAUboxfromone \newsavebox\@finalAUboxfromtwo \newsavebox\@finalAUboxfromthree \newsavebox\@finalAUboxfromfour %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\@ca#1#2{% \if@filesw% \write\@auxout{% \string\@writefile{toc}{\string\author{#1}{}}% }% \fi %%%%%%%%%%%%%%% \ifnum\c@numauthors>4 \resetcounter{numauthors} \fi \stepcounter{numauthors} %%\the\c@numauthors \ifnum\c@numauthors=1 % \sbox\@AUonebox{\CAPlusOneFont#1} \sbox\@AUaffonebox{\vbox{\hsize\textwidth\CAAPlusOneFont\noindent #2\par}} \sbox\@finalAUboxfromone{\copy\@AUonebox} \def\chapter@authorone{\copy\@finalAUboxfromone} \def\chapter@affiliationone{\copy\@AUaffonebox} \fi \ifnum\c@numauthors=2 \sbox\@AUtwobox{\CAPlusOneFont#1} \sbox\@AUafftwobox{\vbox{\hsize\textwidth\CAAPlusOneFont\noindent #2\par}} \sbox\@finalAUboxfromtwo{\copy\@AUtwobox} \def\chapter@authortwo{\copy\@finalAUboxfromtwo} \def\chapter@affiliationtwo{\copy\@AUafftwobox} \fi \ifnum\c@numauthors=3 \sbox\@AUthreebox{\CAPlusOneFont#1} \sbox\@AUaffthreebox{\vbox{\hsize\textwidth\CAAPlusOneFont\noindent #2\par}} \sbox\@finalAUboxfromthree{\copy\@AUthreebox} \def\chapter@authorthree{\copy\@finalAUboxfromthree} \def\chapter@affiliationthree{\copy\@AUaffthreebox} \fi \ifnum\c@numauthors=4 \sbox\@AUfourbox{\CAPlusOneFont#1} \sbox\@AUafffourbox{\vbox{\hsize\textwidth\CAAPlusOneFont\noindent #2\par}} \sbox\@finalAUboxfromfour{\copy\@AUfourbox} \def\chapter@authorfour{\copy\@finalAUboxfromfour} \def\chapter@affiliationfour{\copy\@AUafffourbox} \fi} \def\@caplusone{\@ifstar{\@scaplusone}{\@ifnextchar[{\@xcaplusone}{\@xcaplusone[]}}} \def\@xcaplusone[#1]#2#3{% \def\@@empty{#1}\ifx\@empty\@@empty\@ca{#2}{#3}\else\@ca{#2}{#1}\fi\@scaplusone{#2}{#3}} \def\@scaplusone#1#2{% \ifhmode\vskip-12pt\fi %%Shashi Commented %%% \noindent\hskip3pc{\CAPlusOneFont\baselineskip14pt #1\def\@t{#2}\ifx\@t\@empty\else,\fi}\hskip6pt{\CAAPlusOneFont #2}\par } \def\chapterauthoronly#1#2{\@ca{#1}{}\@scaplusone{#1}{#2}} \def\myaddcontentsline#1#2#3{% \if@filesw \begingroup \let\label\@gobble\let\index\@gobble\let\glossary\@gobble \def\break{\ }% \def\protect##1{\string ##1 }% \@temptokena{\thepage}% \edef\@tempa{\write#1{\string\chapcontentsline{#2}{\string\raggedright\space #3}{\the\@temptokena}}}\@tempa \if@nobreak\ifvmode\nobreak\fi\fi \endgroup \fi} \def\chapcontentsline#1{\csname l@#1\endcsname} \def\l@chapsection{\@mydottedtocline{1}{\z@}{6pt}} \def\l@chapsubsection{\@mydottedtocline{2}{\secnumwidth}{6pt}} \def\l@chapsubsubsection{\@mydottedtocline{3}{\subsecnumwidth}{36pt}} \newcount\c@chaptocdepth \setcounter{chaptocdepth}{3} \def\@mytocline#1#2#3#4#5{% \ifnum #1>\c@chaptocdepth \else \vskip 2pt plus.2\p@ \ifnum #1=1\ifnum\c@chaptocdepth>1\addvspace{12pt}\fi\fi {\leftskip #2\relax \interlinepenalty\@M \leavevmode \@tempdima #3\relax \rightskip\z@ \vbox{\ChapTOCFont #4\nobreak}% \par}\fi} \def\@mydottedtocline#1#2#3#4#5{% \ifnum #1>\c@chaptocdepth \else \fontsize{10}{12}\selectfont {\leftskip #2\relax \rightskip \@tocrmarg \parfillskip -\rightskip \interlinepenalty\@M \leavevmode \def\@dotsep{1.2}% \@tempdima #3\relax \rightskip\z@ \if@sevenbyten \hangindent\secnumwidth\hsize372pt\else\hangindent\secnumwidth\hsize312pt\fi #4 \if@pdf \hfill \else \nobreak\leaders\hbox{$\m@th\mkern\@dotsep mu.\mkern\@dotsep mu$}\hfill\nobreak \hbox to24\p@{\hfil #5}\fi \par}\fi} \newcommand\listoffigures{% \if@twocolumn \@restonecoltrue\onecolumn \else \@restonecolfalse \fi \chapter*{\listfigurename}% \@mkboth{\MakeUppercase\listfigurename}% {\MakeUppercase\listfigurename}% \@starttoc{lof}% \if@restonecol\twocolumn\fi } \newcommand*\l@figure{\@dottedtocline{1}{1.5em}{2.3em}} \newcommand\listoftables{% \if@twocolumn \@restonecoltrue\onecolumn \else \@restonecolfalse \fi \chapter*{\listtablename}% \@mkboth{% \MakeUppercase\listtablename}% {\MakeUppercase\listtablename}% \@starttoc{lot}% \if@restonecol\twocolumn\fi } \let\l@table\l@figure \newdimen\bibindent \setlength\bibindent{1.5em} \newenvironment{thebibliography}[1] {\chapter*{\bibname}% \@mkboth{\MakeUppercase\bibname}{\MakeUppercase\bibname}% \list{\@biblabel{\@arabic\c@enumiv}}% {\settowidth\labelwidth{\@biblabel{#1}}% \leftmargin\labelwidth \advance\leftmargin\labelsep \@openbib@code \usecounter{enumiv}% \let\p@enumiv\@empty \renewcommand\theenumiv{\@arabic\c@enumiv}}% \sloppy \clubpenalty4000 \@clubpenalty \clubpenalty \widowpenalty4000% \sfcode`\.\@m} {\def\@noitemerr {\@latex@warning{Empty `thebibliography' environment}}% \endlist} \newcommand\newblock{\hskip .11em\@plus.33em\@minus.07em} \let\@openbib@code\@empty \newcommand\indexname{Index} \newenvironment{theindex} {\cleardoublepage\if@twocolumn \@restonecolfalse \else \@restonecoltrue \fi \twocolumn[\@makeschapterhead{\indexname}]% \@mkboth{\MakeUppercase\indexname}% {\MakeUppercase\indexname}% \pagestyle{headings} \addcontentsline{toc}{chapter}{\indexname} % there seems to be a weird bug in krantz.cls that prevents the very _last_ item % of \addcontentsline from being added to TOC, so I have to add an empty entry \addcontentsline{toc}{section}{} \thispagestyle{folio}\parindent\z@\markboth{\indexname}{\indexname} \parskip\z@ \@plus .3\p@\relax\raggedright \columnseprule \z@ \columnsep 35\p@ \let\item\@idxitem} {\if@restonecol\onecolumn\else\clearpage\fi} \newcommand\@idxitem{\par\hangindent 40\p@} \newcommand\subitem{\@idxitem \hspace*{20\p@}} \newcommand\subsubitem{\@idxitem \hspace*{30\p@}} \newcommand\indexspace{\par \vskip 10\p@ \@plus5\p@ \@minus3\p@\relax} \renewcommand\footnoterule{% \kern-3\p@ \hrule\@width.4\columnwidth \kern2.6\p@} \@addtoreset{footnote}{chapter} \newcommand\@makefntext[1]{% \parindent 1em% \noindent \hb@xt@1.8em{\hss\@makefnmark}#1} \newcommand\contentsname{Contents} \newcommand\listfigurename{List of Figures} \newcommand\listtablename{List of Tables} \newcommand\bibname{Bibliography} \newcommand\figurename{FIGURE} \newcommand\tablename{TABLE} \newcommand\partname{Part} \newcommand\chaptername{Chapter} \newcommand\appendixname{Appendix} \def\today{\ifcase\month\or January\or February\or March\or April\or May\or June\or July\or August\or September\or October\or November\or December\fi \space\number\day, \number\year} \setlength\columnsep{10\p@} \setlength\columnseprule{0\p@} \pagestyle{headings} \pagenumbering{arabic} \if@twoside \else \raggedbottom \fi \if@twocolumn \twocolumn \sloppy \flushbottom \else \onecolumn \fi \newcommand\unnumcrcrule{\hbox to\textwidth{\rlap{\rule[-3.5\p@]{84\p@}{4\p@}}}} \newcommand\unnumchap@rule{\unnumcrcrule} \newcommand\crcrule{\hbox to\textwidth{\rlap{\rule[-3.5\p@]{84\p@}{4\p@}}\rule{\textwidth}{.5\p@}}} \newcommand\chap@rule{\crcrule} \newcommand\sec@rule{\crcrule} \def\@affiliate[#1]{\gdef\@affiliation{#1}} \def\@affiliation{} \def\def@theequation{% \if@numberinsequence \def\theequation{% \if@numbysec\thesection\else\thechapter\fi.% \@arabic\c@shared}% \else \def\theequation{% \if@numbysec\thesection\else\thechapter\fi.% \@arabic\c@equation}\fi} \def\affiliation#1{{\AffiliationFont\noindent #1\vskip 36bp}} \newbox\tempbox \newdimen\nomenwidth \newenvironment{symbollist}[1]{% \addvspace{12pt} \setbox\tempbox\hbox{#1\hskip1em}% \global\nomenwidth\wd\tempbox \noindent{\SectionHeadFont Symbol Description}\vskip6pt \begin{multicols}{2}}{% \end{multicols}\par\addvspace{12pt}} \def\symbolentry#1#2{\par\noindent\@hangfrom{\hbox to \nomenwidth{#1\hss}}#2\par} \tabcolsep 5pt \arrayrulewidth .5pt \doublerulesep 1pt \newif\if@tablerules\@tablerulestrue \newif\if@centertable\@centertabletrue \newif\if@centertabletitle\@centertabletitletrue \newbox\@tablebox \newbox\@tabletitlebox \newdimen\@tablewidth \newdimen\@tabletitlewidth \newdimen\max@tablewidth \newcommand\automaticrules{\@tablerulestrue} \newcommand\noautomaticrules{\@tablerulesfalse} \def\thetable{% \thechapter.% \@arabic\c@table} \def\thesubtable{% \thechapter.% \@arabic\c@table\alph{subtable}} \def\resettableletter{\setcounter{subtable}{0}} \def\@Tabletitle{} \newcommand\tabletitle{\@ifnextchar[{\@xtabletitle}{\@tabletitlewidth\z@\@ytabletitle}} \def\@@tabletitle{} \newif\ifshorttabletitle \global\shorttabletitlefalse \def\@xtabletitle[#1]#2{% \gdef\@@tabletitle{#1}% \gdef\@tabletitle{#2}% \let\@Tabletitle\@TableTitle \refstepcounter{table}% {\let\footnotemark\@empty \let\footnote\@gobble \addcontentsline{\ext@table}{table}{\protect\numberline{\thetable}{\@@tabletitle}}}} \long\def\@ytabletitle#1{% \def\@tabletitle{#1}% \let\@Tabletitle\@TableTitle \refstepcounter{table}% {\let\footnotemark\@empty \let\footnote\@gobble \addcontentsline{\ext@table}{table}{\protect\numberline{\thetable}{\@tabletitle}}}} \def\tabletitlelet{\@ifnextchar[{\@xtabletitlelet}{\@tabletitlewidth\z@\@ytabletitlelet}} \def\@xtabletitlelet[#1]{\@tabletitlewidth#1\@ytabletitlelet} \long\def\@ytabletitlelet#1{% \def\@tabletitle{#1}% \let\@Tabletitle\@TableTitle \ifnum\c@subtable=0\stepcounter{table}\fi \let\@currentlabel\thesubtable {\let\footnotemark\@empty \let\footnote\@gobble \addcontentsline{\ext@table}{table}{\protect\numberline{\thetable}{\@tabletitle}}}} \def\@TableTitle{% \noindent {% \vbox{{\TableNumberFont TABLE\ \thetable}}\par\TableTitleFont\@tabletitle}} \def\table{% \@float{table}} \@namedef{table*}{% \long\def\caption##1{\tabletitle{##1}\@TableTitle\par}% \@dblfloat{table}} \def\endtabular{\crcr\egroup\egroup $\egroup} \expandafter \let \csname endtabular*\endcsname = \endtabular \def\tabular{\let\@halignto\@empty\@tabular} \@namedef{tabular*}#1{% \setlength\dimen@{#1}% \edef\@halignto{to\the\dimen@}\@tabular} \def\tch#1{\TableColHeadFont #1\llstrut\hfill} \def\tsh#1{\TableSubheadFont #1\hfill} \newcommand\llstrut{\rule[-6pt]{0pt}{14pt}} \newcommand\flstrut{\rule{0pt}{10pt}} \newcommand\tabletitlestrut{\rule{0pt}{20pt}} \def\Boxhead#1{\par\addvspace{3pt plus2pt}\noindent{\centering\bfseries#1\par}\vskip3pt} \newbox\tempbox% \newdimen\tempdimen% % \newenvironment{shortbox}{\par\addvspace{12pt plus2pt}% \if@krantza \setbox\tempbox\vbox\bgroup\hsize27pc% \else\if@krantzb \setbox\tempbox\vbox\bgroup\hsize32pc% \else \setbox\tempbox\vbox\bgroup\hsize25pc% \fi\fi }{% \egroup% \noindent\fboxsep6pt\fboxrule.5pt\hspace*{0pt}\fbox{\box\tempbox} \par\addvspace{12pt plus2pt}}% % \def\grayink{\special{color cmyk 0 0 0 0.2}} \def\blackink{\special{color cmyk 0 0 0 1.0}} % \def\whiteink{\special{color cmyk 0 0 0 0}} % 0% \newenvironment{shadebox}{% \setbox\tempbox\hbox\bgroup\vbox\bgroup\leftskip12pt\rightskip\leftskip\vspace*{12pt}}{\par\addvspace{-6pt} \egroup\egroup\par\addvspace{15pt} \tempdimen\ht\tempbox \advance\tempdimen by 1pc \noindent{\hbox to \wd\tempbox{\vbox to \ht\tempbox{\hsize\textwidth{\special{color push}\grayink\noindent\vrule height\tempdimen width\textwidth \special{color pop}\blackink}}}}% \llap{\unhbox\tempbox}\par\addvspace{20pt}} %%%%%%%%%% Note %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \newbox\tempbox \newdimen\notewidth \newenvironment{notelist}[1]{% \addvspace{6pt} \setbox\tempbox\hbox{#1\hskip.57em}% \global\notewidth\wd\tempbox }{% \par\addvspace{6pt}} \def\notes#1#2{\par\noindent\@hangfrom{\hbox to \notewidth{\bf #1\hss}}#2\par} %%%%%%%%%%%%%%%% wherelist %%%%%%%%%%%%%%%% \newbox\wherebox \newdimen\wherewidth \newenvironment{wherelist}[1]{\leftskip10pt% \addvspace{6pt} \setbox\wherebox\hbox{#1\hskip1em}% \global\wherewidth\wd\wherebox \noindent\hspace*{-14pt} where }{% \par\addvspace{6pt}} \def\whereentry#1#2#3{\par\noindent\@hangfrom{\hbox to \wherewidth{#1\hss}#2\hskip6pt}#3\par} %%%%%%%%%%%% \newenvironment{unnumlist}{% \ifnum \@enumdepth >3 \@toodeep\else \advance\@enumdepth\@ne \list{}{% \leftmargini27.5pt \leftmarginii17.5pt\leftmarginiv17.5pt \advance\leftmargin-.2em \advance\leftmarginii.2em \advance\leftmarginiii.1em \advance\leftmarginiv.2em \def\makelabel##1{\hss\llap{##1}}} \fi% }{% \endlist} % \newenvironment{extract}{% \par\addvspace{11.5pt minus2pt}% \leftskip2em\rightskip\leftskip \noindent\ignorespaces }{% \par\addvspace{11.5pt minus2pt}% \@endparenv} % % \def\VA#1#2{\addvspace{12pt}\raggedleft #1\rightskip3em\par #2\rightskip3em} % \newenvironment{VF}{\VfFont% \par\addvspace{12pt minus2pt}% \noindent{\vrule height2pt width\textwidth}\par\vskip7.3pt \leftskip3em\rightskip\leftskip \noindent\ignorespaces }{% \par\vskip6pt\leftskip0pt\noindent{{\vrule height2pt width\textwidth}}\par\addvspace{12pt minus2pt}% \@endparenv} % \def\VTA#1#2{\addvspace{12pt}\raggedleft #1\rightskip3em\par {\it #2}\rightskip3em} % % \def\VT{\par\addvspace{3.5pt}\noindent} \def\VH#1{{\normalfont\fontsize{12.5}{14.5}\itshape\centering\selectfont #1\par}\addvspace{5.5pt}} % \newenvironment{VT1}{\VfFont% \par\addvspace{12pt minus2pt}% \noindent{\vrule height2pt width\textwidth}\par\vskip7.5pt \leftskip3em\rightskip\leftskip \parindent0pt \noindent\ignorespaces }{% \par\vskip6pt\leftskip0pt\noindent{{\vrule height2pt width\textwidth}}\par\addvspace{10pt minus2pt}% \@endparenv} % %%%%%%%%%%%% Glossary %%%%%%%%%%%%%%%%%%%%%%% \newenvironment{Glossary} {\list{}{\labelwidth\z@\leftmargin18pt \itemindent-18pt \let\makelabel\glosslabel}} {\endlist} \newcommand\glosslabel[1]{\hspace\labelsep\normalfont\bfseries #1:} %%%%%%%%%%%% \newif\iffnalpha \global\fnalphafalse \newskip\listtextleftmargin\listtextleftmargin 20pt%24pt \newskip\listtextleftmarginii\listtextleftmarginii0pt% 24pt \newskip\listtextleftmarginiii\listtextleftmarginiii0pt% 24pt \newskip\listtextrightmargin\listtextrightmargin12pt%.5pc \newskip\listlabelleftskip \listlabelleftskip4pt%3.3pt \newskip\listlabelleftskipii \listlabelleftskipii0pt%3.3pt \newskip\listlabelleftskipiii \listlabelleftskipiii0pt%3.3pt \newskip\abovelistskipi\abovelistskipi6pt plus2pt \newskip\belowlistskipi\belowlistskipi6pt plus2pt \newskip\abovelistskipii\abovelistskipii0pt plus2pt \newskip\belowlistskipii\belowlistskipii0pt plus2pt \newskip\abovelistskipiii\abovelistskipiii0pt plus2pt \newskip\belowlistskipiii\belowlistskipiii0pt plus2pt \newskip\labelsepi \labelsepi6pt \newskip\labelsepii \labelsepii6pt \newskip\labelsepiii \labelsepiii6pt%\z@ \newskip\itemsepi \itemsepi0pt%10pt \newskip\itemsepii \itemsepii0pt \newskip\itemsepiii \itemsepiii0pt \newdimen\enumdimwd \newif\iflabelrightalign\labelrightaligntrue \newdimen\enumdim% % \def\enummax#1{% \labelsep\csname labelsep\romannumeral\the\@enumdepth\endcsname \ifdim\listtextleftmargin>\z@\labelsepi0pt\fi \ifdim\listtextleftmarginii>\z@\labelsepii0pt\fi \ifdim\listtextleftmarginiii>\z@\labelsepiii0pt\fi \setbox\tempbox\hbox{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname#1\hskip\labelsep}% \enumdim\wd\tempbox \setbox\tempbox\hbox{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname#1}% \enumdimwd\wd\tempbox \expandafter\global\csname leftmargin\romannumeral\the\@enumdepth\endcsname\enumdim \ifdim\listtextleftmargin>\z@ \leftmargini\listtextleftmargin \ifdim\listlabelleftskip>\z@ \advance\leftmargini-\listlabelleftskip \fi \fi \ifdim\listtextleftmarginii>\z@ \leftmarginii\listtextleftmarginii \ifdim\listlabelleftskipii>\z@ \advance\leftmarginii-\listlabelleftskipii \fi \fi \ifdim\listtextleftmarginiii>\z@ \leftmarginiii\listtextleftmarginiii \ifdim\listlabelleftskipiii>\z@ \advance\leftmarginiii-\listlabelleftskipiii \fi \fi } % \enummax{1.} % \def\enumerate{\@ifnextchar[{\@enumerate}{\@enumerate[\csname label\@enumctr\endcsname]}}%% % \def\@enumerate[#1]{\par \ifnum \@enumdepth >3 \@toodeep \else \advance\@enumdepth\@ne \edef\@enumctr{enum\romannumeral\the\@enumdepth}% \setcounter{\@enumctr}{1}\enummax{#1}% \list {\csname label\@enumctr\endcsname}{\usecounter{\@enumctr}% \topsep\csname abovelistskip\romannumeral\the\@enumdepth\endcsname \itemsep\csname itemsep\romannumeral\the\@enumdepth\endcsname \ifnum \@enumdepth=1 \leftmargin32.7pt \rightmargin\listtextrightmargin \advance\rightmargin\rightskip \advance\leftmargin\leftskip \tempdimen\leftmargini \advance\tempdimen-\labelsep %%%%%%%%%%% \iffnalpha \def\makelabel##1{{\hskip\listlabelleftskip{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname{\iflabelrightalign\hss\fi\textlistlabel##1}}}}% \global\fnalphafalse \else \def\makelabel##1{\hbox to \tempdimen{\hskip\listlabelleftskip{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname\hbox to \enumdimwd{\iflabelrightalign\hss\fi\textlistlabel##1}}\blackink}}% \fi %%%%%%%%%%%%%%%%%%%%%%%%%%% \else \ifnum \@enumdepth=2 \tempdimen\leftmarginii \advance\tempdimen-\labelsep \def\makelabel##1{\hbox to \tempdimen{\hskip\listlabelleftskipii{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname\hbox to \enumdimwd{\iflabelrightalign\hss\fi##1}\blackink}}}% \else \ifnum \@enumdepth=3 \tempdimen\leftmarginiii \advance\tempdimen-\labelsep \def\makelabel##1{\hbox to \tempdimen{\hskip\listlabelleftskipiii{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname\hbox to \enumdimwd{\iflabelrightalign\hss\fi##1}\blackink}}}% \else \def\makelabel##1{\hss\llap{\csname listdevicefont\romannumeral\the\@enumdepth\endcsname##1}}% \fi \fi \fi} \fi} % \def\endenumerate{\@topsepadd\csname belowlistskip\romannumeral\the\@enumdepth\endcsname\endlist}% % \def\textlistlabel{} %%%%%%%%%%%%%%%%%%%%%%%%%%% \newdimen\concolwidth \newbox\stempbox \def\contributor#1#2#3{\addvspace{10pt}{% \setbox\stempbox\hbox{\ContributorAffiliationFont #2} \concolwidth\wd\stempbox \noindent{\ContributorNameFont #1}\par \ifdim\concolwidth>\columnwidth \vspace*{3pt} \else \fi \noindent{\vbox{\hangindent12pt\ContributorAffiliationFont #2}}\vskip-1\p@ \noindent{\vbox{\hangindent12pt\ContributorAffiliationFont #3}}}} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \def\cleardoublepage{\clearpage\if@twoside \ifodd\c@page\else \hbox{}\thispagestyle{empty}\newpage\if@twocolumn\hbox{}\newpage\fi\fi\fi} \frenchspacing \tolerance=5000 \raggedbottom %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \@centertabletitlefalse \HeadingsChapterSection \endinput %% %% End of file `krantz.cls'. ================================================ FILE: makefile ================================================ html: Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::bs4_book", clean = TRUE)' cp -fvr style/style.css _book/ cp -fvr _redirects _book/ # cp -fvr images _book/ cp -fvr _main* _book/ html2: Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::gitbook", clean = FALSE)' cp -fvr style/style.css _book/ # cp -fvr images _book/ cp -fvr _main* _book/ build: ## Make Build make html Rscript -e 'browseURL("_book/index.html")' pdf: ## Render book in pdf Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::pdf_book")' md: ## Generate Markdown Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::pdf_book", clean = FALSE)' install: ## Perform install Rscript -e 'remotes::install_github("geocompx/geocompr")' deploy: ## Perform deployment Rscript -e 'bookdown::publish_book(render = "local", account = "robinlovelace")' clean: Rscript -e "bookdown::clean_book(TRUE)" rm -fvr *.log Rplots.pdf _bookdown_files land.sqlite3 cleaner: make clean && rm -fvr rsconnect rm -frv *.aux *.out *.toc # Latex output rm -fvr *.html # rogue html files rm -fvr *utf8.md # rogue md files .PHONY: help help: SHELL := /bin/sh help: ## List available commands and their usage @awk 'BEGIN {FS = ":.*?##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[0-9a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } ' $(MAKEFILE_LIST) ================================================ FILE: misc/our-impact.csv ================================================ url,date,type,description,comments,state,creator https://github.com/edzer/sfr/issues/53,2016-11-06,enhancement,st_write arguments,22,closed,Nowosad https://github.com/edzer/sfr/issues/64,2016-11-07,enhancement,Set CRS in a pipe - idea,8,closed,Nowosad https://github.com/mtennekes/tmap/issues/108,2017-04-07,bug,World data plotting error,4,closed,Nowosad https://github.com/mtennekes/tmap/issues/109,2017-04-08,enhancement,Problem - temporal changes plot,7,closed,Nowosad https://github.com/edzer/sfr/issues/306,2017-04-17,bug,Misleading warning,5,closed,Nowosad https://github.com/ropensci/unconf17/issues/54,2017-04-23,question,"""Tidier"" raster package",16,closed,Nowosad https://github.com/mtennekes/tmap/issues/116,2017-05-29,bug,Plots do not work in view mode if all values are NA,2,closed,Robinlovelace https://github.com/edzer/sfr/issues/372,2017-06-02,bug,Joins between sf objects and data.frames,3,closed,Nowosad https://github.com/edzer/sfr/issues/376,2017-06-06,bug,Additional args to FUN in st_join() don't work,6,closed,Robinlovelace https://github.com/edzer/sfr/issues/378,2017-06-08,bug,tidyr::unite error,3,closed,Nowosad https://github.com/edzer/sfr/issues/379,2017-06-09,bug,Behaviour of the `head` function,4,closed,Nowosad https://github.com/tidyverse/dplyr/issues/2833,2017-06-02,bug,Joins between data.frames and sf objects,3,closed,Nowosad https://github.com/edzer/sfr/issues/383,2017-06-14,question,Simple features types,2,closed,Nowosad https://github.com/edzer/sfr/issues/382,2017-06-13,bug,Combine polygons by attribute,6,closed,Nowosad https://github.com/edzer/sfr/issues/396,2017-06-23,bug,Graticules in `geom_sf`,12,closed,Nowosad https://github.com/tidyverse/ggplot2/issues/2200,2017-07-12,bug,`geom_sf()` return values of latitude and longitude for data in a projected coordinate system,8,closed,Nowosad https://github.com/tidyverse/ggplot2/issues/2199,2017-07-11,bug,Errors when plotting sfs (without crs - may be issue),6,closed,Robinlovelace https://github.com/r-spatial/sf/issues/429,2017-07-18,issue,User friendliness of st_join() + aggregate/summarise,27,closed,Robinlovelace https://github.com/oswaldosantos/ggsn/issues/16,2017-06-15,enhancement,Add a support for `sf` objects,6,closed,Nowosad https://github.com/r-spatial/sf/issues/454,2017-08-03,bug,st_join with st_relate doesn't work,1,closed,Nowosad https://github.com/r-spatial/sf/issues/452,2017-08-03,bug,aggregate.sf does not return an object with the same length as by,5,closed,Robinlovelace https://github.com/jannes-m/RQGIS/issues/70,2017-05-29,enhancement,Function idea - a qgis style file creator (.qml),4,closed,Nowosad https://github.com/ropenscilabs/skimr/issues/88,2017-06-07,enhancement,skim of `sf` objects,25,closed,Nowosad https://github.com/r-spatial/lwgeom/issues/6,2017-11-16,bug,Problem with st_transform_proj,3,closed,Nowosad https://github.com/ateucher/rmapshaper/issues/64,2017-11-22,bug,Error related to st_read,5,closed,Robinlovelace https://github.com/hunzikp/velox/issues/7,2017-07-31,enhancement,Add a support for the `sf` objects,1,closed,Nowosad https://github.com/ropensci/osmdata/issues/104,2017-11-07,bug,Allow getbb to return an sf_polygon,1,closed,Robinlovelace https://github.com/r-spatial/sf/issues/576,2017-12-01,bug,tidyr::unite - wrong column name,0,closed,Nowosad https://github.com/rocker-org/geospatial/issues/6,2017-12-04,enhancement,Add tmap,4,closed,Robinlovelace https://github.com/r-spatial/sf/issues/581,2017-12-03,bug,Simplified polygons plotting,1,closed,Nowosad https://github.com/mtennekes/tmap/issues/159,2017-12-17,bug,Polygons plotting error,2,closed,Nowosad https://github.com/ropensci/osmdata/issues/117,2017-12-18,bug,"getbb fails with unhelpful error message for Bristol, Connecticut",4,closed,Robinlovelace https://github.com/r-spatial/sf/issues/622,2018-01-21,bug,objects do not print with new geometry column names,11,closed,Robinlovelace https://github.com/mtennekes/tmap/issues/170,2018-02-10,bug,tmap 2.0: tm_polygons error,0,closed,Nowosad https://github.com/mtennekes/tmap/issues/176,2018-02-22,bug,Mix of raster and points results in error - tmap 2.0,0,closed,Nowosad https://github.com/mtennekes/tmap/issues/177,2018-02-26,bug,Plotting error - tmap 2.0,5,closed,Nowosad https://github.com/r-spatial/sf/issues/685,2018-03-23,bug,sf 0.6-1 (CRAN): plot error,6,closed,Nowosad https://github.com/mtennekes/tmap/issues/171,2018-02-11,enhancement,Function's names - idea,5,closed,Nowosad https://github.com/mtennekes/tmap/issues/178,2018-03-01,enhancement,Set default tmap options for duration of session,12,open,Robinlovelace https://github.com/mtennekes/tmap/issues/184,2018-04-09,bug,tm_borders fails when in view mode,0,closed,Robinlovelace https://github.com/ropenscilabs/rnaturalearth/issues/16,2017-04-01,enhancement,returnclass='sf' as default,0,open,Nowosad https://github.com/mtennekes/tmap/issues/126,2017-07-12,bug,Problem with plots of the multipoint type,5,open,Nowosad https://github.com/r-spatial/mapedit/issues/63,2017-08-22,bug,mapedit does not seem to save the end result after editing,19,open,Robinlovelace https://github.com/jannes-m/RQGIS/issues/99,2018-03-17,bug,v.rast.stats doesn't add a new column,2,open,Nowosad https://r-forge.r-project.org/tracker/index.php?func=detail&aid=6570&group_id=294&atid=1189,2018-03-18,bug,Extract values from raster objects using lines doesn't return data.frames,NA,NA,NA https://github.com/mtennekes/tmap/issues/181,2018-03-29,enhancement,The default value of `auto.palette.mapping` - question,4,open,Nowosad https://github.com/sjewo/cartogram/issues/8,2018-04-03,enhancement,The **sf** support,3,open,Nowosad https://github.com/ateucher/rmapshaper/issues/75,2018-04-05,bug,rmapshaper does not work when a column has the `units` class,1,open,Nowosad https://github.com/mtennekes/tmap/issues/183,2018-04-07,enhancement,Question - bivariate map,5,open,Nowosad http://spatial.nhh.no/misc/?C=M;O=D,2017-05-01,Encouraged the publication and dissemination of previously lost slides on the R/GIS 'bridge',documentation,NA,NA,NA https://github.com/r-spatial/sf/pull/415,2017-07-12,pr,accepted to **sf** improving documentation of units,NA,NA,NA NA,2017-07-24,PR,improve documentation of aggregate.sf **sf**,NA,NA,NA https://github.com/r-spatial/sf/pull/445,2017-07-12,pr,accepted to **sf** exporting and documenting `as_Spatial()`,NA,NA,NA https://github.com/r-spatial/sf/pull/453,2017-08-28,pr,accepted to **sf** updating the behaviour of `aggregate.sf`,NA,NA,NA https://github.com/r-spatial/sf/issues/470,2017-08-25,question,writing speed sqlite and GPKG compared to shapefile,28,closed,mkschneider https://github.com/r-spatial/sf/pull/671,2018-03-14,documentation,for aggregate.sf,NA,NA,NA https://github.com/ateucher/rmapshaper/issues/76,2018-04-14,NA,Inconsistent results with `sys=FALSE` and `sys=TRUE`.,18,open,Nowosad https://github.com/r-spatial/sf/issues/704,2018-04-15,issue,sf seems to reset mfrow,2,closed,Robinlovelace https://github.com/sjewo/cartogram/pull/9,2018-04-18,pr,sf performance improvements,NA,NA,NA https://github.com/mtennekes/tmap/issues/186,2018-04-23,issue,The tmap_arrange example fails,0,open,Nowosad https://github.com/mtennekes/tmap/issues/190,2018-04-25,issue,NAs in a facet map,0,open,Nowosad https://github.com/mtennekes/tmap/issues/191,2018-04-29,issue,The output map with a raster layer has smaller margins.,0,open,Nowosad https://github.com/jbaileyh/geogrid/pull/25,2018-04-30,pr,accepted to **geogrid** sf support by objects conversion,NA,NA,NA https://github.com/mtennekes/tmap/issues/196,2018-05-11,issue,Line plot fails when in view mode,0,closed,Robinlovelace https://github.com/mtennekes/tmap/issues/202,2018-05-20,issue,Possibility of saving the output of `tmap_arrange()` to a file,0,open,Nowosad https://github.com/r-spatial/sf/issues/755,2018-05-25,issue,Problem with plotting `sf_POINT`,0,open,Nowosad https://github.com/mtennekes/tmap/issues/204,2018-05-26,issue,Categorical raster plot,0,open,Nowosad https://github.com/mtennekes/tmap/issues/206,2018-05-26,issue,How to create a tmap style?,0,open,Nowosad https://github.com/mtennekes/tmap/issues/215,2018-06-09,issue,Plot of the GEOMETRY geometry type,0,open,Nowosad https://github.com/mtennekes/tmap/issues/228,2018-07-10,issue,non-standard projections do not work,0,open,Nowosad https://github.com/r-lib/pkgdown/issues/783,2018-08-13,issue,pkgdown consumed a huge amount (10+GB) of RAM on build_site(),5,open,Robinlovelace https://github.com/jbaileyh/geogrid/pull/28,2018-09-02,pr,a set of improvemments to **geogrid**,NA,NA,NA https://github.com/ateucher/rmapshaper/pull/80,2018-09-06,pr,rmapshaper units support,NA,NA,NA ================================================ FILE: misc/our-style.md ================================================ # Code Code lives in the `code` directory in files named according to the chapter they are in, e.g. `01-venn.R`. The code does not have to be self-standing: it can depend on code run previously in the chapter. - `library(package)` - library without quotes - `=` - assignment operator (instead of `<-`) - ` = ` , ` > `, etc. - spaces around operators - `"text"` - double quotes for character values - `for(i in 1:9) {print(i)}` - use space separation for curly brackets - When indenting your code, use two spaces (tab in RStudio) - When using pipes, pipe the first object (`x %>% fun() %>% ...` not `fun(x) %>% ...`) - Scripts should (see example below): - state their aim and (if appropriate) a date and a table of contents - be sectioned with the help of `---`, `===`, and `###` e.g. as acheived by pressing `Ctl-Shift-R` ``` # Filename: filename.R (2018-02-06) # Aim: What should the script achieve # # Author(s): Robin Lovelace, Jakub Nowosad, Jannes Muenchow # #********************************************************** # CONTENTS------------------------------------------------- #********************************************************** # # 1. ATTACH PACKAGES AND DATA # 2. DATA EXPLORATION # #********************************************************** # 1 ATTACH PACKAGES AND DATA------------------------------- #********************************************************** # attach packages library(sf) # attach data nc = st_read(system.file("shape/nc.shp", package = "sf")) #********************************************************** # 2 DATA EXPLORATION--------------------------------------- #********************************************************** ``` # Comments Comment your code unless obvious because the aim is teaching. Use capital first letter for full-line comment. ```r # Create object x x = 1:9 ``` Do not capitalise comment for end-of-line comment ```r y = x^2 # square of x ``` # Text - Use one line per sentence during development for ease of tracking changes. - Leave a single empty line space before and after each code chunk. - Format content as follows: - **package_name** - `class_of_object` - `function_name()` - Spelling: use `en-us` # Captions Captions should not contain any markdown characters, e.g. `*` or `*`. References in captions also should be avoided. # Figures Names of the figures should contain a chapter number, e.g. `04-world-map.png` or `11-population-animation.gif`. # File names - Minimize capitalization: use `file-name.rds` not `file-name.Rds` - `-` not `_`: use `file-name.rds` not `file_name.rds` # References References are added using the markdown syntax [@refname] from the .bib files in this repo. The package **citr** can be used to automate citation search and entry. Use Zotero to add references to the geocompr at [zotero.org](https://www.zotero.org/groups/418217/energy-and-transport/items/collectionKey/9K6FRP6N/) rather than changing .bib files directly. The citation key format used is `[auth:lower]_[veryshorttitle:lower]_[year]` using [zotero-better-bibtex](https://github.com/retorquere/zotero-better-bibtex). ================================================ FILE: packages.bib ================================================ @Manual{R-bookdown, title = {bookdown: Authoring Books and Technical Documents with R Markdown}, author = {Yihui Xie}, year = {2023}, note = {R package version 0.36, https://pkgs.rstudio.com/bookdown/}, url = {https://github.com/rstudio/bookdown}, } @Manual{R-cartogram, title = {cartogram: Create Cartograms with R}, author = {Sebastian Jeworutzki}, year = {2023}, note = {R package version 0.3.0}, url = {https://github.com/sjewo/cartogram}, } @Manual{R-crsuggest, title = {crsuggest: Obtain Suggested Coordinate Reference System Information for Spatial Data}, author = {Kyle Walker}, year = {2022}, note = {R package version 0.4}, url = {https://CRAN.R-project.org/package=crsuggest}, } @Manual{R-dismo, title = {dismo: Species Distribution Modeling}, author = {Robert J. Hijmans and Steven Phillips and John Leathwick and Jane Elith}, year = {2023}, note = {R package version 1.3-14}, url = {https://rspatial.org/raster/sdm/}, } @Manual{R-dplyr, title = {dplyr: A Grammar of Data Manipulation}, author = {Hadley Wickham and Romain François and Lionel Henry and Kirill Müller and Davis Vaughan}, year = {2023}, note = {R package version 1.1.3}, url = {https://dplyr.tidyverse.org}, } @Manual{R-geodata, title = {geodata: Download Geographic Data}, author = {Robert J. Hijmans}, year = {2023}, note = {R package version 0.5-9}, url = {https://CRAN.R-project.org/package=geodata}, } @Manual{R-geosphere, title = {geosphere: Spherical Trigonometry}, author = {Robert J. Hijmans}, year = {2022}, note = {R package version 1.5-18}, url = {https://CRAN.R-project.org/package=geosphere}, } @Manual{R-ggmap, title = {ggmap: Spatial Visualization with ggplot2}, author = {David Kahle and Hadley Wickham and Scott Jackson}, year = {2023}, note = {R package version 3.0.2}, url = {https://github.com/dkahle/ggmap}, } @Manual{R-ggplot2, title = {ggplot2: Create Elegant Data Visualisations Using the Grammar of Graphics}, author = {Hadley Wickham and Winston Chang and Lionel Henry and Thomas Lin Pedersen and Kohske Takahashi and Claus Wilke and Kara Woo and Hiroaki Yutani and Dewey Dunnington}, year = {2023}, note = {R package version 3.4.4, https://github.com/tidyverse/ggplot2}, url = {https://ggplot2.tidyverse.org}, } @Manual{R-gstat, title = {gstat: Spatial and Spatio-Temporal Geostatistical Modelling, Prediction and Simulation}, author = {Edzer Pebesma and Benedikt Graeler}, year = {2023}, note = {R package version 2.1-1}, url = {https://github.com/r-spatial/gstat/}, } @Manual{R-historydata, title = {historydata: Data Sets for Historians}, author = {Lincoln Mullen}, year = {2014}, note = {R package version 0.1}, url = {https://github.com/ropensci/historydata}, } @Manual{R-htmlwidgets, title = {htmlwidgets: HTML Widgets for R}, author = {Ramnath Vaidyanathan and Yihui Xie and JJ Allaire and Joe Cheng and Carson Sievert and Kenton Russell}, year = {2023}, note = {R package version 1.6.2}, url = {https://github.com/ramnathv/htmlwidgets}, } @Manual{R-kableExtra, title = {kableExtra: Construct Complex Table with kable and Pipe Syntax}, author = {Hao Zhu}, year = {2021}, note = {R package version 1.3.4, https://github.com/haozhu233/kableExtra}, url = {http://haozhu233.github.io/kableExtra/}, } @Manual{R-knitr, title = {knitr: A General-Purpose Package for Dynamic Report Generation in R}, author = {Yihui Xie}, year = {2023}, note = {R package version 1.45}, url = {https://yihui.org/knitr/}, } @Manual{R-latticeExtra, title = {latticeExtra: Extra Graphical Utilities Based on Lattice}, author = {Deepayan Sarkar and Felix Andrews}, year = {2022}, note = {R package version 0.6-30}, url = {http://latticeextra.r-forge.r-project.org/}, } @Manual{R-leaflet, title = {leaflet: Create Interactive Web Maps with the JavaScript Leaflet Library}, author = {Joe Cheng and Barret Schloerke and Bhaskar Karambelkar and Yihui Xie}, year = {2023}, note = {R package version 2.2.0.9000, https://github.com/rstudio/leaflet}, url = {https://rstudio.github.io/leaflet/}, } @Manual{R-link2GI, title = {link2GI: Linking Geographic Information Systems, Remote Sensing and Other Command Line Tools}, author = {Chris Reudenbach}, year = {2023}, note = {R package version 0.5-3, https://r-spatial.github.io/link2GI/}, url = {https://github.com/r-spatial/link2GI/}, } @Manual{R-lwgeom, title = {lwgeom: Bindings to Selected liblwgeom Functions for Simple Features}, author = {Edzer Pebesma}, year = {2023}, note = {R package version 0.2-13}, url = {https://github.com/r-spatial/lwgeom/}, } @Manual{R-mapview, title = {mapview: Interactive Viewing of Spatial Data in R}, author = {Tim Appelhans and Florian Detsch and Christoph Reudenbach and Stefan Woellauer}, year = {2023}, note = {R package version 2.11.2}, url = {https://github.com/r-spatial/mapview}, } @Manual{R-microbenchmark, title = {microbenchmark: Accurate Timing Functions}, author = {Olaf Mersmann}, year = {2023}, note = {R package version 1.4.10}, url = {https://github.com/joshuaulrich/microbenchmark/}, } @Manual{R-mlr, title = {mlr: Machine Learning in R}, author = {Bernd Bischl and Michel Lang and Lars Kotthoff and Patrick Schratz and Julia Schiffner and Jakob Richter and Zachary Jones and Giuseppe Casalicchio and Mason Gallo}, year = {2022}, note = {R package version 2.19.1}, url = {https://mlr.mlr-org.com}, } @Manual{R-osmdata, title = {osmdata: Import OpenStreetMap Data as Simple Features or Spatial Objects}, author = {Mark Padgham and Bob Rudis and Robin Lovelace and Maëlle Salmon and Joan Maspons}, year = {2023}, note = {R package version 0.2.5}, url = {https://docs.ropensci.org/osmdata/}, } @Manual{R-pROC, title = {pROC: Display and Analyze ROC Curves}, author = {Xavier Robin and Natacha Turck and Alexandre Hainard and Natalia Tiberti and Frédérique Lisacek and Jean-Charles Sanchez and Markus Müller}, year = {2023}, note = {R package version 1.18.4}, url = {http://expasy.org/tools/pROC/}, } @Manual{R-qgisprocess, title = {R package qgisprocess: use QGIS processing algorithms}, author = {Dewey Dunnington and Floris Vanderhaeghe and Jan Caha and Jannes Muenchow}, year = {2024}, note = {R package version 0.3.0}, url = {https://r-spatial.github.io/qgisprocess/}, keywords = {R; package; QGIS}, } @Manual{R-ranger, title = {ranger: A Fast Implementation of Random Forests}, author = {Marvin N. Wright and Stefan Wager and Philipp Probst}, year = {2023}, note = {R package version 0.15.1}, url = {https://github.com/imbs-hl/ranger}, } @Manual{R-raster, title = {raster: Geographic Data Analysis and Modeling}, author = {Robert J. Hijmans}, year = {2023}, note = {R package version 3.6-26}, url = {https://rspatial.org/raster}, } @Manual{R-rcartocolor, title = {rcartocolor: CARTOColors Palettes}, author = {Jakub Nowosad}, year = {2023}, note = {R package version 2.1.1}, url = {https://github.com/Nowosad/rcartocolor}, } @Manual{R-rgdal, title = {rgdal: Bindings for the Geospatial Data Abstraction Library}, author = {Roger Bivand and Tim Keitt and Barry Rowlingson}, year = {2023}, note = {R package version 1.6-7}, url = {http://rgdal.r-forge.r-project.org}, } @Manual{R-rgeos, title = {rgeos: Interface to Geometry Engine - Open Source (GEOS)}, author = {Roger Bivand and Colin Rundel}, year = {2023}, note = {R package version 0.6-4}, url = {https://r-forge.r-project.org/projects/rgeos/}, } @Manual{R-rgrass7, title = {rgrass7: Interface Between GRASS 7 Geographical Information System and R}, author = {Roger Bivand}, year = {2017}, note = {R package version 0.1-10}, url = {https://CRAN.R-project.org/package=rgrass7}, } @Manual{R-rgrass, title = {rgrass: Interface Between GRASS Geographical Information System and R}, author = {Roger Bivand}, year = {2023}, note = {R package version 0.3-9}, url = {https://rsbivand.github.io/rgrass/}, } @Manual{R-rmapshaper, title = {rmapshaper: Client for mapshaper for Geospatial Operations}, author = {Andy Teucher and Kenton Russell}, year = {2023}, note = {R package version 0.5.0}, url = {https://github.com/ateucher/rmapshaper}, } @Manual{R-rmarkdown, title = {rmarkdown: Dynamic Documents for R}, author = {JJ Allaire and Yihui Xie and Christophe Dervieux and Jonathan McPherson and Javier Luraschi and Kevin Ushey and Aron Atkins and Hadley Wickham and Joe Cheng and Winston Chang and Richard Iannone}, year = {2023}, note = {R package version 2.25, https://pkgs.rstudio.com/rmarkdown/}, url = {https://github.com/rstudio/rmarkdown}, } @Manual{R-rnaturalearth, title = {rnaturalearth: World Map Data from Natural Earth}, author = {Philippe Massicotte and Andy South}, year = {2023}, note = {R package version 0.3.4, https://github.com/ropensci/rnaturalearth}, url = {https://docs.ropensci.org/rnaturalearth/}, } @Manual{R-rnaturalearthdata, title = {rnaturalearthdata: World Vector Map Data from Natural Earth Used in rnaturalearth}, author = {Andy South}, year = {2017}, note = {R package version 0.1.0}, url = {https://github.com/ropenscilabs/rnaturalearthdata}, } @Manual{R-RPostgreSQL, title = {RPostgreSQL: R Interface to the PostgreSQL Database System}, author = {Joe Conway and Dirk Eddelbuettel and Tomoaki Nishiyama and Sameer Kumar Prayaga and Neil Tiffin}, year = {2023}, note = {R package version 0.7-5, https://cran.r-project.org/package=DBI}, url = {https://github.com/tomoakin/RPostgreSQL}, } @Manual{R-RQGIS, title = {RQGIS: Integrating R with QGIS}, author = {Jannes Muenchow and Patrick Schratz}, year = {2018}, note = {R package version 1.0.3}, url = {https://CRAN.R-project.org/package=RQGIS}, } @Manual{R-RSAGA, title = {RSAGA: SAGA Geoprocessing and Terrain Analysis}, author = {Alexander Brenning and Donovan Bangs and Marc Becker}, year = {2022}, note = {R package version 1.4.0}, url = {https://github.com/r-spatial/RSAGA}, } @Manual{R-Rsagacmd, title = {Rsagacmd: Linking R with the Open-Source 'SAGA-GIS' Software}, author = {Steven Pawley}, year = {2023}, note = {R package version 0.4.2}, url = {https://github.com/r-spatial/RSAGA}, } @Manual{R-sf, title = {sf: Simple Features for R}, author = {Edzer Pebesma}, year = {2023}, note = {R package version 1.0-14}, url = {https://r-spatial.github.io/sf/}, } @Manual{R-sfnetworks, title = {sfnetworks: Tidy Geospatial Networks}, author = {Lucas {van der Meer} and Lorena Abad and Andrea Gilardi and Robin Lovelace}, year = {2023}, note = {R package version 0.6.3}, url = {https://CRAN.R-project.org/package=sfnetworks}, } @Manual{R-sp, title = {sp: Classes and Methods for Spatial Data}, author = {Edzer Pebesma and Roger Bivand}, year = {2023}, note = {R package version 2.1-1}, url = {https://github.com/edzer/sp/}, } @Manual{R-spData, title = {spData: Datasets for Spatial Analysis}, author = {Roger Bivand and Jakub Nowosad and Robin Lovelace}, year = {2023}, note = {R package version 2.3.0}, url = {https://jakubnowosad.com/spData/}, } @Manual{R-spDataLarge, title = {spDataLarge: Large datasets for spatial analysis}, author = {Jakub Nowosad and Robin Lovelace}, year = {2023}, note = {R package version 2.0.9}, url = {https://github.com/Nowosad/spData}, } @Manual{R-stplanr, title = {stplanr: Sustainable Transport Planning}, author = {Robin Lovelace and Richard Ellison and Malcolm Morgan}, year = {2023}, note = {R package version 1.1.2, https://docs.ropensci.org/stplanr/}, url = {https://github.com/ropensci/stplanr}, } @Manual{R-tabularaster, title = {tabularaster: Tidy Tools for Raster Data}, author = {Michael D. Sumner}, year = {2023}, note = {R package version 0.7.2}, url = {https://github.com/hypertidy/tabularaster}, } @Manual{R-terra, title = {terra: Spatial Data Analysis}, author = {Robert J. Hijmans}, year = {2023}, note = {R package version 1.7-55}, url = {https://rspatial.org/}, } @Manual{R-tidyverse, title = {tidyverse: Easily Install and Load the Tidyverse}, author = {Hadley Wickham}, year = {2023}, note = {R package version 2.0.0, https://github.com/tidyverse/tidyverse}, url = {https://tidyverse.tidyverse.org}, } @Manual{R-tmap, title = {tmap: Thematic Maps}, author = {Martijn Tennekes}, year = {2023}, note = {R package version 3.99.9000}, url = {https://github.com/r-tmap/tmap}, } @Manual{R-tmaptools, title = {tmaptools: Thematic Map Tools}, author = {Martijn Tennekes}, year = {2021}, note = {R package version 3.1-1}, url = {https://github.com/mtennekes/tmaptools}, } @Manual{R-tree, title = {tree: Classification and Regression Trees}, author = {Brian Ripley}, year = {2023}, note = {R package version 1.0-43}, url = {https://CRAN.R-project.org/package=tree}, } @Manual{R-vegan, title = {vegan: Community Ecology Package}, author = {Jari Oksanen and Gavin L. Simpson and F. Guillaume Blanchet and Roeland Kindt and Pierre Legendre and Peter R. Minchin and R.B. O'Hara and Peter Solymos and M. Henry H. Stevens and Eduard Szoecs and Helene Wagner and Matt Barbour and Michael Bedward and Ben Bolker and Daniel Borcard and Gustavo Carvalho and Michael Chirico and Miquel {De Caceres} and Sebastien Durand and Heloisa Beatriz Antoniazi Evangelista and Rich FitzJohn and Michael Friendly and Brendan Furneaux and Geoffrey Hannigan and Mark O. Hill and Leo Lahti and Dan McGlinn and Marie-Helene Ouellette and Eduardo {Ribeiro Cunha} and Tyler Smith and Adrian Stier and Cajo J.F. {Ter Braak} and James Weedon}, year = {2022}, note = {R package version 2.6-4}, url = {https://github.com/vegandevs/vegan}, } @Manual{R-z22, title = {{z22: Official Gridded Data from the German Census 2022}}, author = {Jonas Lieth}, year = {2026}, note = {R package version 1.1.2}, doi = {10.32614/CRAN.package.z22}, url = {https://github.com/jslth/z22/}, } @Book{bookdown2016, title = {bookdown: Authoring Books and Technical Documents with {R} Markdown}, author = {Yihui Xie}, publisher = {Chapman and Hall/CRC}, address = {Boca Raton, Florida}, year = {2016}, isbn = {978-1138700109}, url = {https://bookdown.org/yihui/bookdown}, } @Article{ggmap2013, author = {David Kahle and Hadley Wickham}, title = {ggmap: Spatial Visualization with ggplot2}, journal = {The R Journal}, year = {2013}, volume = {5}, number = {1}, pages = {144--161}, url = {https://journal.r-project.org/archive/2013-1/kahle-wickham.pdf}, } @Book{ggplot22016, author = {Hadley Wickham}, title = {ggplot2: Elegant Graphics for Data Analysis}, publisher = {Springer-Verlag New York}, year = {2016}, isbn = {978-3-319-24277-4}, url = {https://ggplot2.tidyverse.org}, } @Article{gstat2004, title = {Multivariable geostatistics in {S}: the gstat package}, author = {Edzer J. Pebesma}, journal = {Computers & Geosciences}, year = {2004}, volume = {30}, pages = {683-691}, } @Article{gstat2016, title = {Spatio-Temporal Interpolation using gstat}, author = {Benedikt Gräler and Edzer Pebesma and Gerard Heuvelink}, year = {2016}, journal = {The R Journal}, volume = {8}, issue = {1}, pages = {204-218}, url = {https://journal.r-project.org/archive/2016/RJ-2016-014/index.html}, } @Article{kernlab2004, title = {kernlab -- An {S4} Package for Kernel Methods in {R}}, author = {Alexandros Karatzoglou and Alex Smola and Kurt Hornik and Achim Zeileis}, journal = {Journal of Statistical Software}, year = {2004}, volume = {11}, number = {9}, pages = {1--20}, doi = {10.18637/jss.v011.i09}, } @Book{knitr2015, title = {Dynamic Documents with {R} and knitr}, author = {Yihui Xie}, publisher = {Chapman and Hall/CRC}, address = {Boca Raton, Florida}, year = {2015}, edition = {2nd}, note = {ISBN 978-1498716963}, url = {https://yihui.org/knitr/}, } @InCollection{knitr2014, booktitle = {Implementing Reproducible Computational Research}, editor = {Victoria Stodden and Friedrich Leisch and Roger D. Peng}, title = {knitr: A Comprehensive Tool for Reproducible Research in {R}}, author = {Yihui Xie}, publisher = {Chapman and Hall/CRC}, year = {2014}, note = {ISBN 978-1466561595}, } @Article{mlr, title = {{mlr}: Machine Learning in R}, author = {Bernd Bischl and Michel Lang and Lars Kotthoff and Julia Schiffner and Jakob Richter and Erich Studerus and Giuseppe Casalicchio and Zachary M. Jones}, journal = {Journal of Machine Learning Research}, year = {2016}, volume = {17}, number = {170}, pages = {1-5}, url = {https://jmlr.org/papers/v17/15-066.html}, } @Article{automatic, title = {Automatic model selection for high-dimensional survival analysis}, author = {Michel Lang and Helena Kotthaus and Peter Marwedel and Claus Weihs and Joerg Rahnenfuehrer and Bernd Bischl}, journal = {Journal of Statistical Computation and Simulation}, year = {2014}, volume = {85}, number = {1}, pages = {62-76}, publisher = {Taylor & Francis}, } @InCollection{bischl2016class, title = {On Class Imbalance Correction for Classification Algorithms in Credit Scoring}, author = {Bernd Bischl and Tobias Kuehn and Gero Szepannek}, booktitle = {Operations Research Proceedings 2014}, pages = {37-43}, year = {2016}, publisher = {Springer}, } @Article{mlrmbo, title = {mlrMBO: A Modular Framework for Model-Based Optimization of Expensive Black-Box Functions}, author = {Bernd Bischl and Jakob Richter and Jakob Bossek and Daniel Horn and Janek Thomas and Michel Lang}, journal = {arXiv preprint arXiv:1703.03373}, year = {2017}, } @Article{multilabel, title = {Multilabel Classification with R Package mlr}, author = {Philipp Probst and Quay Au and Giuseppe Casalicchio and Clemens Stachl and Bernd Bischl}, journal = {arXiv preprint arXiv:1703.08991}, year = {2017}, } @Article{openml, title = {OpenML: An R package to connect to the machine learning platform OpenML}, author = {Giuseppe Casalicchio and Jakob Bossek and Michel Lang and Dominik Kirchhoff and Pascal Kerschke and Benjamin Hofner and Heidi Seibold and Joaquin Vanschoren and Bernd Bischl}, journal = {Computational Statistics}, pages = {1-15}, year = {2017}, publisher = {Springer}, } @Article{osmdata2017, title = {osmdata}, author = {{Mark Padgham} and {Bob Rudis} and {Robin Lovelace} and {Maëlle Salmon}}, journal = {Journal of Open Source Software}, year = {2017}, volume = {2}, number = {14}, pages = {305}, month = {jun}, publisher = {The Open Journal}, url = {https://joss.theoj.org/papers/10.21105/joss.00305}, doi = {10.21105/joss.00305}, } @Article{pROC2011, title = {pROC: an open-source package for R and S+ to analyze and compare ROC curves}, author = {Xavier Robin and Natacha Turck and Alexandre Hainard and Natalia Tiberti and Frédérique Lisacek and Jean-Charles Sanchez and Markus Müller}, year = {2011}, journal = {BMC Bioinformatics}, volume = {12}, pages = {77}, } @Article{ranger2017, title = {{ranger}: A Fast Implementation of Random Forests for High Dimensional Data in {C++} and {R}}, author = {Marvin N. Wright and Andreas Ziegler}, journal = {Journal of Statistical Software}, year = {2017}, volume = {77}, number = {1}, pages = {1--17}, doi = {10.18637/jss.v077.i01}, } @Book{rmarkdown2018, title = {R Markdown: The Definitive Guide}, author = {Yihui Xie and J.J. Allaire and Garrett Grolemund}, publisher = {Chapman and Hall/CRC}, address = {Boca Raton, Florida}, year = {2018}, isbn = {9781138359338}, url = {https://bookdown.org/yihui/rmarkdown}, } @Book{rmarkdown2020, title = {R Markdown Cookbook}, author = {Yihui Xie and Christophe Dervieux and Emily Riederer}, publisher = {Chapman and Hall/CRC}, address = {Boca Raton, Florida}, year = {2020}, isbn = {9780367563837}, url = {https://bookdown.org/yihui/rmarkdown-cookbook}, } @Book{sf2023, author = {Edzer Pebesma and Roger Bivand}, title = {{Spatial Data Science: With applications in R}}, year = {2023}, publisher = {{Chapman and Hall/CRC}}, url = {https://r-spatial.org/book/}, doi = {10.1201/9780429459016}, } @Article{sf2018, author = {Edzer Pebesma}, title = {{Simple Features for R: Standardized Support for Spatial Vector Data}}, year = {2018}, journal = {{The R Journal}}, doi = {10.32614/RJ-2018-009}, url = {https://doi.org/10.32614/RJ-2018-009}, pages = {439--446}, volume = {10}, number = {1}, } @Article{sp2005, author = {Edzer J. Pebesma and Roger Bivand}, title = {Classes and methods for spatial data in {R}}, journal = {R News}, year = {2005}, volume = {5}, number = {2}, pages = {9--13}, month = {November}, url = {https://CRAN.R-project.org/doc/Rnews/}, } @Book{sp2013, author = {Roger S. Bivand and Edzer Pebesma and Virgilio Gomez-Rubio}, title = {Applied spatial data analysis with {R}, Second edition}, year = {2013}, publisher = {Springer, NY}, url = {https://asdar-book.org/}, } @Article{stplanr2018, author = {{Robin Lovelace} and {Richard Ellison}}, title = {stplanr: A Package for Transport Planning}, year = {2018}, volume = {10}, number = {2}, journal = {{The R Journal}}, pages = {10}, doi = {10.32614/RJ-2018-053}, } @Article{tidyverse2019, title = {Welcome to the {tidyverse}}, author = {Hadley Wickham and Mara Averick and Jennifer Bryan and Winston Chang and Lucy D'Agostino McGowan and Romain François and Garrett Grolemund and Alex Hayes and Lionel Henry and Jim Hester and Max Kuhn and Thomas Lin Pedersen and Evan Miller and Stephan Milton Bache and Kirill Müller and Jeroen Ooms and David Robinson and Dana Paige Seidel and Vitalie Spinu and Kohske Takahashi and Davis Vaughan and Claus Wilke and Kara Woo and Hiroaki Yutani}, year = {2019}, journal = {Journal of Open Source Software}, volume = {4}, number = {43}, pages = {1686}, doi = {10.21105/joss.01686}, } @Article{tmap2018, title = {{tmap}: Thematic Maps in {R}}, author = {Martijn Tennekes}, journal = {Journal of Statistical Software}, year = {2018}, volume = {84}, number = {6}, pages = {1--39}, doi = {10.18637/jss.v084.i06}, } ================================================ FILE: references.Rmd ================================================ `r if (knitr:::is_html_output()) ' # References {-} '` ================================================ FILE: style/after_body.tex ================================================ \backmatter \printindex ================================================ FILE: style/before_body.tex ================================================ %\cleardoublepage\newpage\thispagestyle{empty}\null %\cleardoublepage\newpage\thispagestyle{empty}\null %\cleardoublepage\newpage \thispagestyle{empty} \begin{center} % \includegraphics{images/dedication.pdf} \end{center} \setlength{\abovedisplayskip}{-5pt} \setlength{\abovedisplayshortskip}{-5pt} ================================================ FILE: style/ga.html ================================================ ================================================ FILE: style/preamble.tex ================================================ \usepackage{booktabs} \usepackage{longtable} \usepackage[bf,singlelinecheck=off]{caption} \captionsetup[table]{labelsep=space} \captionsetup[figure]{labelsep=space} \usepackage[scale=.7]{sourcecodepro} % not in https://github.com/yihui/bookdown-crc/blob/master/latex/preamble.tex \usepackage{tabu} \usepackage{graphicx} %%% \usepackage{framed,color} \definecolor{shadecolor}{RGB}{255,255,255} \definecolor{shadecolor2}{RGB}{236,240,249} \renewcommand{\textfraction}{0.05} \renewcommand{\topfraction}{0.8} \renewcommand{\bottomfraction}{0.8} \renewcommand{\floatpagefraction}{0.75} \renewenvironment{quote}{\begin{VF}}{\end{VF}} \usepackage{hyperref} % \let\oldhref\href % \renewcommand{\href}[2]{#2\footnote{\url{#1}}} % code blocks style (e.g., background) \makeatletter \newenvironment{kframe}{% \medskip{} \setlength{\fboxsep}{.8em} \def\at@end@of@kframe{}% \ifinner\ifhmode% \def\at@end@of@kframe{\end{minipage}}% \begin{minipage}{\columnwidth}% \fi\fi% \def\FrameCommand##1{\hskip\@totalleftmargin \hskip-\fboxsep \colorbox{shadecolor}{##1}\hskip-\fboxsep % There is no \\@totalrightmargin, so: \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% \MakeFramed {\advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize \@setminipage}}% {\par\unskip\endMakeFramed% \at@end@of@kframe} \makeatother \renewenvironment{Shaded}{\begin{kframe}}{\end{kframe}} % text blocks style (e.g., background) \makeatletter \newenvironment{kframe2}{% \medskip{} \setlength{\fboxsep}{.8em} \def\at@end@of@kframe2{}% \ifinner\ifhmode% \def\at@end@of@kframe2{\end{minipage}}% \begin{minipage}{\columnwidth}% \fi\fi% \def\FrameCommand##1{\hskip\@totalleftmargin \hskip-\fboxsep \colorbox{shadecolor2}{##1}\hskip-\fboxsep % There is no \\@totalrightmargin, so: \hskip-\linewidth \hskip-\@totalleftmargin \hskip\columnwidth}% \MakeFramed {\advance\hsize-\width \@totalleftmargin\z@ \linewidth\hsize \@setminipage}}% {\par\unskip\endMakeFramed% \at@end@of@kframe2} \makeatother % not in https://github.com/yihui/bookdown-crc/blob/master/latex/preamble.tex \newenvironment{rmdblock}[1] { \begin{itemize} \renewcommand{\labelitemi}{ \raisebox{-.7\height}[0pt][0pt]{ {\setkeys{Gin}{width=3em,keepaspectratio}\includegraphics{images/#1}} } } \setlength{\fboxsep}{1em} \begin{kframe2} \item } { \end{kframe2} \end{itemize} } \newenvironment{rmdnote} {\begin{rmdblock}{globe}} {\end{rmdblock}} %%% \usepackage{makeidx} \makeindex \urlstyle{tt} \usepackage{amsthm} \makeatletter \def\thm@space@setup{% \thm@preskip=8pt plus 2pt minus 4pt \thm@postskip=\thm@preskip } \makeatother \frontmatter ================================================ FILE: style/style.css ================================================ div.rmdnote, div.rstudio-tip, div.rmdwarning { padding: 1em; margin: 1em 0; padding-left: 100px; background-size: 70px; background-repeat: no-repeat; background-position: 15px 1.5em; min-height: 120px; background-color: #ecf0f9; max-width: 100%; font-size: 0.85em; letter-spacing: 1pt; } div.rmdnote { background-image: url("../images/globe.png"); } div.sourceCode { font-size: 1.0rem; }