diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..b49bee1 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,42 @@ +on: + push: + branches: + [main] +name: Render +permissions: + contents: write + pages: write +jobs: + bookdown: + name: GH-Pages + runs-on: ubuntu-latest + container: ghcr.io/geocompx/docker:minimal + defaults: + run: + shell: bash -l {0} + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + - name: Render + run: | + Rscript -e 'install.packages("remotes")' + Rscript -e 'remotes::install_cran(c("tinytable", "viridis", "rmapshaper"))' + Rscript -e 'remotes::install_github("r-tmap/tmap", dependencies = TRUE, force = TRUE)' + # quarto render + # ls docs + - name: Render Quarto Project + uses: quarto-dev/quarto-actions/render@v2 + # - name: Publish to GitHub Pages (and render) + # uses: quarto-dev/quarto-actions/publish@v2 + # with: + # target: gh-pages + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # this secret is always available for github actions + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs + publish_branch: gh-pages + commit_message: 'Deploy commit: ${{ github.event.head_commit.message }}' \ No newline at end of file diff --git a/.github/workflows/master.yaml b/.github/workflows/master.yaml deleted file mode 100644 index efad9e5..0000000 --- a/.github/workflows/master.yaml +++ /dev/null @@ -1,98 +0,0 @@ -on: - push: - branches: - master - -name: Render-Book-from-master - -jobs: - bookdown: - name: Render-Book - runs-on: ubuntu-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - services: - postgres: - image: kartoza/postgis - ports: - - 5432:5432 - env: - POSTGRES_USER: root - POSTGRES_PASSWORD: root - POSTGRES_DB: postgis - POSTGRES_MULTIPLE_EXTENSIONS: postgis,hstore,postgis_topology - DEFAULT_ENCODING: "UTF8" - options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@master - with: - r-version: 'release' - - - uses: r-lib/actions/setup-pandoc@master - - - name: Query dependencies - run: | - install.packages(c('remotes', 'rcmdcheck')) - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Restore R package cache - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install system dependencies - #if: runner.os == 'Linux' - #env: - # RHUB_PLATFORM: linux-x86_64-ubuntu-gcc - run: | - #Rscript -e "remotes::install_github('r-hub/sysreqs')" - #sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") - #sudo -s eval "$sysreqs" - # install spatial dependencies - sudo add-apt-repository ppa:ubuntugis/ubuntugis-unstable - sudo apt update - sudo apt install \ - libudunits2-dev \ - libgdal-dev \ - libgeos-dev \ - libproj-dev \ - libv8-dev \ - libjq-dev \ - libprotobuf-dev \ - protobuf-compiler \ - optipng #\ - #libharfbuzz-dev \ - #libfribidi-dev - # install database dependencies - sudo apt-get -y install bash-completion wget - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list - sudo apt-get update - sudo apt-get -y install postgresql-client-12 - - - name: Install dependencies - run: | - install.packages("remotes") - remotes::install_deps(dependencies = TRUE) - webshot::install_phantomjs() - shell: Rscript {0} - - - name: Render Book - run: | - Rscript -e 'bookdown::render_book("index.Rmd")' - cp -r widgets _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 }}' diff --git a/.gitignore b/.gitignore index 2437dc8..4b6463e 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,9 @@ rsconnect README_files/figure-html/ man *_files -bookdown* !ga.html *.utf8.md unnamed-chunk* inst/doc + +/.quarto/ diff --git a/03-nutshell.Rmd b/03-nutshell.Rmd deleted file mode 100644 index 5af4031..0000000 --- a/03-nutshell.Rmd +++ /dev/null @@ -1,231 +0,0 @@ -# **tmap** in a nutshell {#nutshell} - -The **tmap** package allows the creation of thematic maps with great flexibility. -It accepts spatial data in various formats shape objects (section \@ref(shape-objects)) -Next, the data can be used to create simple, quick maps (section \@ref(quick-maps)) and more complex and expandable maps (section \@ref(regular-maps)). -These maps can be presented in two modes - as a static map and an interactive one. -Additionally, **tmap** makes it possible to create small multiples map (section \@ref(sm-section)) and map animations (section \@ref(ani-section)). - -## Shape objects - -As we established in chapter \@ref(geodata), spatial data comes in various file formats related to two main data models - vector and raster. -There are also several spatial object classes in R, for example, `sf` from the **sf** package for vector data and `stars` from **stars** for raster data and spatial data cubes. -Additionally, packages such as **sp**, **raster**, or **terra** offer their own classes, and this abundance of spatial object classes can be generally overwhelming. -Gladly, **tmap** can work with all of the above objects - it treats all supported spatial data classes as so-called *shape objects*. - -For example, we read the `ei_points.gpkg` file contains several points on Easter Island into a new object, `ei_points`, and select only points with the `type` of `"volcano"`. -The `volcanoes` object is a *shape object*. - -```{r, message=FALSE} -library(tmap) -library(sf) -ei_points = read_sf("data/easter_island/ei_points.gpkg") -volcanoes = subset(ei_points, type == "volcano") -``` - - - -Spatial data, no matter the class, usually stores two interrelated sets of information - about the locations/geometries and their associated values/attributes. -Visualization of the attributes only can be done with regular plotting functions (e.g., `plot()`, `hist()`, `barplot()`) or dedicated packages, such as **ggplot2** . -On the other hand, **tmap** is suitable when our goal is to visualize spatial geometries only or spatial geometries together with their attributes. - -## Quick maps - -The **tmap** package offers a distinction between quick and regular maps. -The first approach, using the `qtm()` function, could be handy for data exploration. -It works even if we just provide any *shape object* - in that case, only the geometry is plotted. -Figure \@ref(fig:qtm):A shows a visualization of the geometries from the `volcanoes`. - -```{r, eval=FALSE} -qtm(volcanoes) -``` - -The `qtm()` function allows to customize many map elements for the provided *shape object*. -For example, we can change the shapes of the points in `volcanoes`, make their sizes related to the the `"elevation"` argument, and add a title (Figure \@ref(fig:qtm):B). - -```{r, eval=FALSE} -qtm(volcanoes, symbols.shape = 24, symbols.size = "elevation", title = "Volcanoes") -``` - -(ref:qtm) Two maps created with `qtm()`: (A) by providing only a shape object, (B) by providing a shape object and some other arguments. - -```{r qtm, fig.cap="(ref:qtm)", echo=FALSE} -tmq1 = qtm(volcanoes) + tm_layout(title = "A") -tmq2 = qtm(volcanoes, symbols.shape = 24, symbols.size = "elevation", title = "Volcanoes") + - tm_layout(title = "B Volcanoes") -tmap_arrange(tmq1, tmq2) -``` - -The `qtm()` function offers similar flexibility to the regular map approach. -However, it only supports one shape object. - -## Regular maps - -Therefore, for most applications, we recommend using the regular map approach. -This approach operates on many functions that start with `tm_`. -The first element always is `tm_shape()`, which specified the input shape object. -Next, map layers, additional map elements, and overall layout can be customized. - - - -The last example in \@ref(quick-maps) can be reproduced with the regular map approach using the following code: - -```{r, eval=FALSE} -tm_shape(volcanoes) + - tm_symbols(shape = 24, size = "elevation") + - tm_layout(title = "volcanoes") -``` - -Here, we specify the input data (our shape object) with `tm_shape()`, aesthetics (also known as *visual variables*) of map layers with `tm_symbols()`, and the map title with `tm_layout()`. - -The **tmap** package has a number of possible map layers, but the most prominent ones are `tm_polygons()`, `tm_symbols()`, `tm_lines()`, `tm_raster()`, and `tm_text()` (chapter \@ref(layers)). -Overall, most visual variables of map layers can be assigned in two main ways. -First, they accept a fixed, constant value, for instance, `shape = 24`, which sets the symbols' shapes to triangles. -Second, it is also possible to provide a variable name, for example `size = "elevation"`. -This plots each point with a size based on the `elevation` attribute from the `volcanoes` object and automatically adds a related map legend. - -The `tm_shape()` function and one or more following map layers create a *group* together. -In other words, map layers are related only to the preceding `tm_shape()` call. -One map can have several *groups*. -Let's see how many *groups* work by reading some additional datasets - the `ei_elev` raster with elevation data for Easter Island, the `ei_borders` polygon with the island outline, and the `ei_roads` lines contains a road network for this island. - - -```{r, message=FALSE} -library(sf) -library(stars) -ei_elev = read_stars("data/easter_island/ei_elev.tif") -ei_borders = read_sf("data/easter_island/ei_border.gpkg") -ei_roads = read_sf("data/easter_island/ei_roads.gpkg") -``` - -Look at the following example and try to guess how many *groups* it has, and how many layers exist for each *group* (Figure \@ref(fig:rmap1)). - -(ref:rmap1) Example of a map with four groups of map layers: an elevation layer, island borders layer, roads layer, and volcanoes layer. - -```{r rmap1, warning=FALSE, fig.height=9, fig.asp=0.73, fig.cap="(ref:rmap1)"} -tm_shape(ei_elev) + - tm_raster(style = "cont", palette = "-RdYlGn", - title = "Elevation (m asl)") + - tm_shape(ei_borders) + - tm_borders() + - tm_shape(ei_roads) + - tm_lines(lwd = "strokelwd", legend.lwd.show = FALSE) + - tm_shape(volcanoes) + - tm_symbols(shape = 24, size = "elevation", - title.size = "Volcanoes (m asl)") + - tm_layout(main.title = "Easter Island", - bg.color = "lightblue") -``` - -The correct answer is four groups, all with just one layer. -Each *group* is put on the top of the previous one - the **tmap** uses a layered approach. -The first *group* represents elevation data with a continuous color scale style, a color palette called `"RdYlGn"`, and a legend title. -The second *group* shows the borders of Easter Island with the default aesthetics, while the third *group* presents the road network (the `ei_roads` object), with each line's width based on the values from the `"strokelwd"` column, but with a legend hidden. -The last *group* is similar to our previous example with fixed symbol shapes and sizes related to the `"elevation"` attribute, but also with the legend title instead of the map title. -Additionally, we use the `tm_layout()` function to add a map title. - - -Often, maps also have additional map elements, such as graticule lines, north arrow, scale bar, or map credits (Figure \@ref(fig:rmap2)). -They help map readers to understand data location or its size, and provide some ancillary information. -The **tmap** package offers a set of functions for additional map elements. -The `tm_graticules()` function draws latitude and longitude graticules and adds their labels. -It also uses the layered approach, and thus, the lines will be drawn either below or above the shape objects, depending on the position of this function in the code. -In our example below, `tm_graticules()` is used after all of the map groups, and that is why the graticule lines are put on the top of the spatial data. -We can also use `tm_compass()` to create a north arrow, `tm_scale_bar()` to add a scale bar, and `tm_credits()` to add a text annotation representing credits or acknowledgments. -The location of all these three elements on the map is, by default, automatically determined. -It, however, can be adjusted with the `position` argument - see an example of its use in the `tm_compass()` function below. -Moreover, it is possible to add any type of manual legend with `tm_add_legend()`. -It includes simple legends, such as the `"Roads"` legend below that is only represented by a single black line, but more complex legends with several elements are also possible. - - -```{r, warning=FALSE} -my_map = tm_shape(ei_elev) + - tm_raster(style = "cont", palette = "-RdYlGn", - title = "Elevation (m asl)") + - tm_shape(ei_borders) + - tm_borders() + - tm_shape(ei_roads) + - tm_lines(lwd = "strokelwd", legend.lwd.show = FALSE) + - tm_shape(volcanoes) + - tm_symbols(shape = 24, size = "elevation", - title.size = "Volcanoes (m asl)") + - tm_graticules() + - tm_compass(position = c("right", "top")) + - tm_scale_bar() + - tm_credits("Author, 2021") + - tm_add_legend(type = "line", col = "black", title = "Roads") + - tm_layout(main.title = "Easter Island", - bg.color = "lightblue") -``` - -Maps created with **tmap** can be saved as an R object. -This is a useful feature, which allows to use one map in a few places in a code, modify existing **tmap** objects, or save these objects to files. - -(ref:rmap2) Example of a map with four groups of map layers and additional map elements, such as graticule lines, north arrow, scale bar, and text annotation. It also has a manually added legend. - -```{r rmap2, fig.height=9, fig.asp=0.73, fig.cap="(ref:rmap2)"} -my_map -``` - - - -## Map modes - -Each map created with **tmap** can be viewed in one of two modes: `"plot"` and `"view"`. -The `"plot"` mode is used by default and creates static maps similar to those shown before in this chapter. - -This mode supports almost all of **tmap**'s features, and it is recommended, for example, for scientific publications or printing. - -The second mode, `"view"`, allows creating interactive maps. -They can be zoomed in and out or panned, allow for changing background tiles, or click on map objects to get some additional information. -This mode has, however, some constraints and limitations comparing to `"plot"`, for example -the legend cannot be fully customized, and some additional map elements are not supported. - -The **tmap** package allows using both modes on the same code. -Therefore, there is no need to create two separate maps for static and interactive use. -The `tmap_mode()` function can be used to switch from one mode to the other^[Map modes can be also changed globally using `tmap_options()` or switched using `ttm()`.]. - -```{r} -tmap_mode("view") -``` - -The above line of code just changes the mode - it does not return anything except the message. -Now, if we want to use this mode, we need to either write a new **tmap** code provide some existing **tmap** object, such as `my_map`. - -```{r, eval=FALSE} -my_map -``` - -As a result, we get several messages and an interactive map. -These messages inform us that some map elements specified in the previous code are not possible in the `"view"` mode. -It includes the text annotation ("Credits"), the north arrow ("Compass"), and some legend options. - -```{r imap1, echo=FALSE, fig.cap='Map from the previous figure shown using the interactive ("view") mode.', cache=FALSE, message=FALSE} -view_map(my_map, "imap1") -``` - -Our main result is the interactive map (Figure \@ref(fig:imap1)). - -It shows our spatial data using similar aesthetics to Figure \@ref(fig:rmap2), but allows us to zoom in and out or move the map. -We also can select a background tile or click on any line and point to get some information. - -To go back to the `"plot"` mode, we need to use the `tmap_mode()` function again - map not shown: - -```{r, fig.show='hide'} -tmap_mode("plot") -my_map -``` - -More information about the interactive `"view"` mode and how to customize its outputs is in chapter \@ref(interactive). - -## Small multiples {#sm-section} - - - -Chapter \@ref(multiples) - -## Animations {#ani-section} - - - -Chapter \@ref(animations) diff --git a/04-tm-shape.Rmd b/04-tm-shape.Rmd deleted file mode 100644 index 08e3a4e..0000000 --- a/04-tm-shape.Rmd +++ /dev/null @@ -1,279 +0,0 @@ -# Specifying spatial data {#tmshape} - -In order to plot spatial data, at least two aspects need to be specified: the spatial data object itself, and the plotting method(s). -We will cover the former in this chapter. -The latter will be discussed in the next chapters. - -## Shapes and layers - -As described in Chapter \@ref(geodata), shape objects can be vector or raster data. -We recommend `sf` objects for vector data and `stars` objects for raster data^[However, **tmap** also accepts other spatial objects, e.g., of `sp`, `raster`, and `terra` classes.]. - -```{r, echo=TRUE, warning=FALSE, message=FALSE} -library(tmap) -library(dplyr) -library(sf) -library(stars) -worldelevation = read_stars("data/worldelevation.tif") -worldvector = read_sf("data/worldvector.gpkg") -worldcities = read_sf("data/worldcities.gpkg") -``` - -In **tmap**, a shape object needs to be defined with the function `tm_shape()`. -When multiple shape objects are used, each has to be defined in a separate `tm_shape()` call. -This is illustrated in the following example (Figure \@ref(fig:tmshape1)). - -```{r tmshape1, echo=TRUE, warning=FALSE, fig.cap="A map representing three shapes (worldelevation, worldvector, and worldcities) using four layers."} -tm_shape(worldelevation) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) + -tm_shape(worldvector) + - tm_borders() + -tm_shape(worldcities) + - tm_dots() + - tm_text("name") -``` - -In this example, we use three shapes: `worldelevation` which is a `stars` object containing an attribute called `"worldelevation.tif"`, `worldvector` which is an `sf` object with country borders, and `worldcities`, which is an `sf` object that contains metropolitan areas of at least 20 million inhabitants. - -Each `tm_shape()` function call is succeeded by one or more layer functions. -In the example these are `tm_raster()`, `tm_borders()`, `tm_dots()` and `tm_text()`. -We will describe layer functions in detail in the next chapter. -For this chapter, it is sufficient to know that each layer function call defines how the spatial data specified with `tm_shape()` is plotted. - -Shape objects can be used to plot multiple layers. -In the example, shape `worldcities` is used for two layers, `tm_dots()` and `tm_text()`. - - - -## Shapes hierarchy - -The order of the `tm_shape()` functions' calls is crucial. -The first `tm_shape()`, known as the main shape, is not only shown below the following *shapes*, but also sets the projection and extent of the whole map. -In Figure \@ref(fig:tmshape1), the `worldelevation` object was used as the first *shape*, and thus the whole map has the projection and extent of this object. - -However, we can quickly change the main *shape* with the `is.master` argument. - -In the following example, we set the `worldcities` object as the main *shape*, which limits the output map to the point locations in `worldcities` (Figure \@ref(fig:tmshape2))^[We will show how to adjust margins and text locations later in the book]. - -```{r tmshape2, fig.asp=0.35, fig.cap="A map representing three shapes (worldelevation, worldvector, and worldcities) using four layers and zoomed into the locations in the worldcities object."} -tm_shape(worldelevation) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) + -tm_shape(worldvector) + - tm_borders() + -tm_shape(worldcities, is.master = TRUE) + - tm_dots() + - tm_text("name") -``` - - - -## Map projection -\index{map projection} - -As we mentioned in the previous section, created maps use the projection from the main *shape*. -However, we often want to create a map with a different projection, for example to preserve a specific map property (Chapter \@ref(crs)). -We can do this in two ways. -The first way to use a different projection on a map is to reproject the main data before plotting, as shown in Section \@ref(crs-in-r). -The second way is to specify the map projection using the `projection` argument of `tm_shape()`. -This argument expects either some `crs` object or a CRS code. -In the next example, we set `projection` to `8857`. -This number represents EPSG 8857 of a projection called [Equal Earth](http://equal-earth.com/index.html) [@savric_equal_2019]. -The Equal Earth projection is an equal-area pseudocylindrical projection for world maps similar to the non-equal-area Robinson projection (Figure \@ref(fig:crs-robin)). - -Reprojections of vector data are usually straightforward because each spatial coordinate is reprojected individually. -However, reprojecting of raster data is more complex and requires using one of two approaches. -The first approach (`raster.warp = TRUE`) applies raster warping, which is a name for two separate spatial operations: creation of a new regular raster object and computation of new pixel values through resampling (for more details read Chapter 6 of @lovelace2019geocomputation). -This is the default option in **tmap**, however, it has some limitations. - -Figure \@ref(fig:tm-map-proj):A shows the world elevation raster reprojected to Equal Earth. -Some of you can quickly noticed that certain areas, such as parts of Antarctica, New Zealand, Alaska, and the Kamchatka Peninsula, are presented twice: with one version being largely distorted. -Another limitation of `raster.warp = TRUE` is the use of the nearest neighbor resampling only - while it can be a proper method to use for categorical rasters, it can have some unintended consequences for continuous rasters (such as the `"worldelevation.tif"` data). - -```{r, warning=FALSE, eval=FALSE} -tm_shape(worldelevation, projection = 8857) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) -``` - -The second approach (`raster.warp = FALSE`) computes new coordinates for each raster cell keeping all of the original values and results in a curvilinear grid. -This calculation could deform the shapes of original grid cells, and usually curvilinear grids take a longer time to plot^[For more details of the first approach see `?stars::st_warp()` and of the second approach see `?stars::st_transform()`.]. - -Figure \@ref(fig:tm-map-proj):B shows an example of the second approach, which gave a better result in this case without any spurious lands. -However, creation of the B map takes about ten times longer than the A map. - -```{r, warning=FALSE, eval=FALSE} -tm_shape(worldelevation, projection = 8857, raster.warp = FALSE) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) -``` - -```{r tm-map-proj, echo=FALSE, warning=FALSE, fig.cap="Two elevation maps in the Equal Earth projection: (A) created using raster.warp = TRUE, (B) created using raster.warp = FALSE."} -tmp1 = tm_shape(worldelevation, projection = 8857) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) + - tm_layout(title = "A") -tmp2 = tm_shape(worldelevation, projection = 8857, raster.warp = FALSE) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) + - tm_layout(title = "B") -tmap_arrange(tmp1, tmp2) -``` - -```{r, echo=FALSE, eval=FALSE} -bench::mark(print(tmp1), print(tmp2), check = FALSE) -# 2.28s vs 24.52s -``` - - - - -## Map extent - -Another important aspect of mapping, besides projection, is its extent - a portion of the area shown in a map. - -This is not an issue when the extent of our spatial data is the same as we want to show on a map. -However, what should we do when the spatial data contains a larger region than we want to present? - -Again, we could take two routes. -The first one is to preprocess our data before mapping - this can be done with vector clipping (e.g., `st_intersection()`) and raster cropping (e.g., `st_crop()`). -We would recommend this approach if you plan to work on the smaller data in the other parts of the project. -The second route is to specify the map extent in **tmap**. - -**tmap** allows specifying map extent using three approaches. -The first one is to specify minimum and maximum coordinates in the x and y directions that we want to represent. -This can be done with a numeric vector of four values in the order of minimum x, minimum y, maximum x, and maximum y, where all of the coordinates need to be specified in the input data units^[This can also be done with the object of class `st_bbox` or a 2 by 2 matrix. -In the following example, we limit our map extent to the rectangular area between x from -15 to 45 and y from 35 to 65 (Figure \@ref(fig:tbbox1)). - - -```{r tbbox1, warning=FALSE, fig.cap="Global elevation data limited to the extent of the specified minimum and maximum coordinates."} -tm_shape(worldelevation, bbox = c(-15, 35, 45, 65)) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) -``` - -The second approach allows setting the map extent based on a search query. -In the code below, we limit the map extent to the area of `"Europe"` (Figure \@ref(fig:tbbox2)). -This approach uses the OpenStreetMap tool called Nominatim to automatically generate minimum and maximum coordinates in the x and y directions based on the provided query. - -```{r tbbox2, warning=FALSE, fig.cap="Global elevation data limited to the extent specified with the 'Europe' query."} -tm_shape(worldelevation, bbox = "Europe") + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) -``` - -In the last approach, the map extent is based on another existing spatial object. -Figure \@ref(fig:tbbox3) shows the elevation raster data (`worldelevation`) limited to the edge coordinates from `worldcities`. - -```{r tbbox3, fig.asp=0.35, warning=FALSE, fig.cap="Global elevation data limited to the extent of the other spatial object."} -tm_shape(worldelevation, bbox = worldcities) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) -``` - - - - - -## Data simplification - -Geometries in spatial vector data consists of sets of coordinates (Section \@ref(vector-data-model)). -Spatial vector objects grow larger with more features to present and more details to show, and this also has an impact on time to render a map. - -Figure \@ref(fig:vectordown):A shows a map of countries from the `worldvector` object. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons() -``` - -This level of detail can be good for some maps, but sometimes the number of details can make reading the map harder. -To show a simplified (smoother) version of vector data, we can use the `simplify` argument of `tm_shape()`^[Vector data simplification requires the **rmapshaper** package to be installed.]. -It expects a numeric value from 0 to 1 - a proportion of vertices in the data to retain. -In the example below, we set `simplify` to 0.05, which keeps 5% of vertices (Figure \@ref(fig:vectordown):B). - -```{r, eval=FALSE} -tm_shape(worldvector, simplify = 0.05) + - tm_polygons() -``` - -The process of simplification can also be more controlled. -By default, the underlining algorithm (called the Visvalingam method, learn more at https://bost.ocks.org/mike/simplify/), removes small features, such as islands in our case. -This could have far-reaching consequences - in the process of simplification, we could remove some countries! -To prevent the deletion of small features, we also need to set `keep.units` to `TRUE`. - -```{r, eval=FALSE} -tm_shape(worldvector, simplify = 0.05, keep.units = TRUE) + - tm_polygons() -``` - -Figure \@ref(fig:vectordown):C shows the result of such an operation. -Now, our map contains all of the countries from the original data, but in a simplified form. -`keep.units = TRUE`, however, does not keep all of the subfeatures. -In the case of one country consisting of many small polygons, only one is sure to be retained. -For example, look at New Zealand, which is now only represented by Te Waipounamu (the South Island). -To keep all of the spatial geometries (even the smallest of islands), we should also specify `keep.subunits` to `TRUE`. - -```{r, eval=FALSE} -tm_shape(worldvector, simplify = 0.05, keep.units = TRUE, keep.subunits = TRUE) + - tm_polygons() -``` - -Figure \@ref(fig:vectordown):D contains a simplified map, where each spatial geometry of the original map still exists, but in a less detailed form. - -(ref:vectordown) A map of world's countries based on: (A) original data, (B) simplified data with 5% of vertices kept, (C) simplified data with 5% of vertices, and all features kept, (D) simplified data with 5% of vertices, all features, and all polygons kept. - -```{r vectordown, echo=FALSE, message=FALSE, fig.cap="(ref:vectordown)"} -tms1 = tm_shape(worldvector) + - tm_polygons() + - tm_layout(title = "A") -tms2 = tm_shape(worldvector, simplify = 0.05) + - tm_polygons() + - tm_layout(title = "B") -tms3 = tm_shape(worldvector, simplify = 0.05, keep.units = TRUE) + - tm_polygons() + - tm_layout(title = "C") -tms4 = tm_shape(worldvector, simplify = 0.05, keep.units = TRUE, keep.subunits = TRUE) + - tm_polygons() + - tm_layout(title = "D") -tmap_arrange(tms1, tms2, tms3, tms4) -``` - -All of the about vector simplification functions use the `ms_simplify()` from the **rmapshaper** package . -Therefore, you can customize the data simplification even further using other arguments of `ms_simplify()` (except for the arguments `input`, `keep`, `keep_shapes`, and `explode`). - - - -Raster data is represented by a grid of cells (Section \@ref(raster-data-model)), and the number of cells impacts the time to render a map. -Rasters with hundreds of cells will be plotted quickly, while rasters with hundreds of millions or billions of cells will take a lot of time (and RAM) to be shown. - -Therefore, the **tmap** package downsamples large rasters by default to be below 10,000,000 cells in the plot mode and 1,000,000 cells in the view mode. - -This values can be adjusted with the `max.raster` argument of `tmap_options()`, which expects a named vector with two elements - `plot` and `view`. - - - (Figure \@ref(fig:rasterdown):A). - -```{r, eval=FALSE} -tmap_options(max.raster = c(plot = 5000, view = 2000)) -tm_shape(worldelevation) + - tm_raster("worldelevation.tif") -``` - -Raster downsampling can be also disabled with the `raster.downsample` argument of `tm_shape()` (Figure \@ref(fig:rasterdown):B). - -```{r, eval=FALSE} -tm_shape(worldelevation, raster.downsample = FALSE) + - tm_raster("worldelevation.tif") -``` - -```{r rasterdown, echo=FALSE, fig.cap="(A) A raster map with the decreased resolution, (B) a raster map in the original resolution.", message=FALSE} -tmap_options(max.raster = c(plot = 5000, view = 2000)) -tmr1 = tm_shape(worldelevation) + - tm_raster("worldelevation.tif") + - tm_layout(title = "A") -tmr2 = tm_shape(worldelevation, raster.downsample = FALSE) + - tm_raster("worldelevation.tif") + - tm_layout(title = "B") -tmap_arrange(tmr1, tmr2) -``` - -Any **tmap** options can be reset (set to default) with `tmap_options_reset()` (We explain `tmap_options()` in details in Chapter \@ref(options)). - -```{r, message=FALSE} -tmap_options_reset() -``` - - diff --git a/05-layers.Rmd b/05-layers.Rmd deleted file mode 100644 index c33ca02..0000000 --- a/05-layers.Rmd +++ /dev/null @@ -1,526 +0,0 @@ -# Layers {#layers} - - - -```{r, echo=FALSE} -layers_basic_df = tibble::tribble( - ~Function, ~Element, ~Geometry, - "tm_polygons()", "polygons (borders and fill)", "polygons", - "tm_symbols()", "symbols", "points, polygons, and lines", - "tm_lines()", "lines", "lines", - "tm_raster()", "raster", "raster", - "tm_text()", "text", "points, polygons, and lines", - "tm_basemap()", "tile" , "", - "tm_tiles()", "tile", "" -) -layers_extended_df = tibble::tribble( - ~Function, ~Element, ~Geometry, - "tm_borders()", "polygons (borders)", "polygons", - "tm_fill()", "polygons (fill)", "polygons", - "tm_bubbles()", "bubbles", "points, polygons, and lines", - "tm_dots()", "dots", "points, polygons, and lines", - "tm_markers()", "marker symbols", "points, polygons, and lines", - "tm_square()", "squares", "points, polygons, and lines", - "tm_iso()", "lines with text labels", "lines", - "tm_rgb()/tm_rgba()", "raster (RGB image)", "raster" -) -layers_df = rbind(layers_basic_df, - layers_extended_df) -``` - -\@ref(tab:layers-table) - -```{r layers-table, echo=FALSE, warning=FALSE, message=FALSE} -library(magrittr) -library(kableExtra) -options(kableExtra.html.bsTable = TRUE) -knitr::kable(layers_df, - caption = "Map layers.", - caption.short = "Map layers.", - booktabs = TRUE) %>% - kableExtra::kable_styling("striped", - latex_options = "striped", - full_width = FALSE) %>% - kableExtra::column_spec(1, bold = TRUE, monospace = TRUE) %>% - kableExtra::pack_rows("Basic functions", 1, 7) %>% - kableExtra::pack_rows("Derived functions", 8, 15) -``` - - - - - -In this chapter, we focus on what map layers are available in **tmap** and how they differ. -Chapter \@ref(visual-variables), on the other hand, is all about how to present information given in variables using colors, sizes, and shapes. - - - - -## Polygons - - - -```{r, warning=FALSE, message=FALSE} -library(tmap) -library(sf) -ei_borders = read_sf("data/easter_island/ei_border.gpkg") -``` - -The main function to visualize polygons is `tm_polygons()`. -By default, it plots areas of polygons in light gray (`gray85`) and polygons borders in slightly dark gray (`gray40`). - - -```{r, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons() -``` - -Both, colors of areas (polygons' fillings) and colors of borders can be modified using the `col` and `border.col` arguments (Figure \@ref(fig:tmpolygonsder):A). - -```{r, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons(col = "lightblue", - border.col = "black", lwd = 0.5, lty = "dashed") -``` - -In fact, `tm_polygons()` is a combination of two separate functions - `tm_fill()` and `tm_borders()`. -The `tm_fill()` function fills polygons with a fixed color or a color palette representing a selected variable (Figure \@ref(fig:tmpolygonsder):B). - -```{r, eval=FALSE} -tm_shape(ei_borders) + - tm_fill(col = "lightblue") -``` - -The `tm_borders()` function draws the borders of the polygons only (Figure \@ref(fig:tmpolygonsder):C). -It allows to change the colors of borders, their widths, or the lines type. - -```{r, eval=FALSE} -tm_shape(ei_borders) + - tm_borders(col = "black", lwd = 0.5, lty = "dashed") -``` - -Notice that we have used the `col` argument in `tm_borders()`, but `border.col` in `tm_polygons()`. -This is necessary to distinguish between the setting of the fillings color and the borders' color. - -(ref:tmpolygonsder) Example of a map created with: (A) `tm_polygons()`, (B) `tm_fill()`, (C) `tm_borders()`. - -```{r tmpolygonsder, warning=FALSE, fig.cap="(ref:tmpolygonsder)", echo=FALSE, fig.asp=0.4} -tmpa0 = tm_shape(ei_borders) + - tm_polygons(col = "lightblue", - border.col = "black", lwd = 0.5, lty = "dashed") + - tm_layout(title = "A") -tmpa1 = tm_shape(ei_borders) + - tm_fill(col = "lightblue") + - tm_layout(title = "B") -tmpa2 = tm_shape(ei_borders) + - tm_borders(col = "black", lwd = 0.5, lty = "dashed") + - tm_layout(title = "C") -tmap_arrange(tmpa0, tmpa1, tmpa2, nrow = 1) -``` - -More information on colors, and how they can be applied and modified is explained in detail in Chapter \@ref(colors). - -## Symbols - -```{r} -ei_points = read_sf("data/easter_island/ei_points.gpkg") -volcanos = subset(ei_points, type == "volcano") -``` - -Symbols are a very flexible layer type. -They are usually used to represent point data, but can be also used for lines and polygons. -In the latter cases, they are located in centroid coordinates of each feature. -Their flexibility is also related to the ways symbols can be visualized - it is possible to show values of a given variable by colors of symbols, their sizes, or shapes (more about that is explained in Chapter \@ref(visual-variables)). - -The `tm_symbols()` is the main function in **tmap** allowing to use and modify symbol elements (Figure \@ref(fig:tmsymbols1)). -By default, this function draws a gray circle symbol with a black border for each element of an input feature. - -```{r tmsymbols1, warning=FALSE, fig.cap="A map showing the default tmap symbols.", echo=FALSE, asp=0.25} -tm_shape(volcanos) + - tm_symbols() -``` - -In the above example, each symbol is related to one feature (row) in the `volcanos` object. -However, in a case when we provide multi-element features (such as MULTIPOINT; section \@ref(vector-data-model)), each multi-element object is first split into a number of single-element features and then plotted. - -The `tm_symbols()` is a very flexible function with a large number of arguments. -While this allows adjusting its results to almost any need, it also makes this function complicated. -Therefore, four additional layers are implemented in **tmap**: `tm_squares()`, `tm_bubbles()`, `tm_dots()`, `tm_markers()`. -All of them use `tm_symbols()`, but with different default values. - -`tm_squares()` uses square symbols (`shape = 22`) instead of circles (`shapes = 21`) (Figure \@ref(fig:tmsymbols2):A). - - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_squares() -``` - - -(Figure \@ref(fig:tmsymbols2):B) - - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_bubbles() -``` - -The main role of `tm_dots()` is to present many locations at the same time. -To do this, this layer has a small size value (`0.02`) at the default (Figure \@ref(fig:tmsymbols2):C). - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_dots() -``` - -The last additional layer is `tm_markers()`, which uses a marker icon by default (Figure \@ref(fig:tmsymbols2):D). - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_markers() -``` - -(ref:tmsymbols2) Maps showing default visualizations using: (A) tm_squares(), (B) tm_bubbles(), (C) tm_dots(), (D) tm_markers(). - -```{r tmsymbols2, warning=FALSE, fig.cap="(ref:tmsymbols2)", echo=FALSE, fig.asp=0.69} -tm_ma1 = tm_shape(volcanos) + - tm_squares() + - tm_layout(title = "A") -tm_ma2 = tm_shape(volcanos) + - tm_bubbles() + - tm_layout(title = "B") -tm_ma3 = tm_shape(volcanos) + - tm_dots() + - tm_layout(title = "C") -tm_ma4 = tm_shape(volcanos) + - tm_markers() + - tm_layout(title = "D") -tmap_arrange(tm_ma1, tm_ma2, tm_ma3, tm_ma4, ncol = 2) -``` - -## Lines - -```{r} -ei_roads = read_sf("data/easter_island/ei_roads.gpkg") -``` - -The `tm_lines()` function allows to visualize different types of line data (Figure \@ref(fig:tmlines)). - -(ref:tmlines) Example of a map created with tm_lines(). - -```{r tmlines, fig.cap="(ref:tmlines)"} -tm_shape(ei_roads) + - tm_lines() -``` - -Lines can be presented using different colors, widths, or types (Chapter \@ref(visual-variables)). -This allows to show a hierarchy (for example, increased line widths for higher capacity roads) or distinguish between types of objects (for example, blue rivers comparing to gray roads). - -## Text - -Text labels are often an integral part of many maps. -They can serve several functions, from naming features, indicating relations between them, or representing a given variable's values. -The main function to create text labels is `tm_text()`, which adds a label to each spatial feature (Figure \@ref(fig:tmtext)). - -(ref:tmtext) Example of a map created with tm_text(). - -```{r tmtext, fig.cap="(ref:tmtext)", fig.asp=0.35} -tm_shape(volcanos) + - tm_text(text = "name", size = "elevation") + - tm_layout(legend.outside = TRUE) -``` -```{r, echo=FALSE, eval=FALSE} -tm_shape(ei_roads) + - tm_lines(lwd = "strokelwd") + - tm_text("name", remove.overlap = TRUE, along.lines = TRUE) -``` - -We can adjust colors (`col`) and sizes (`size`; Section \@ref(sizes)) of labels either by providing a single value or a name of a data variable. -Text labels can be modified with a set of unique arguments, including `case` (`"upper"` or `"lower"`), `shadow` (`TRUE` or `FALSE`), `fontface` and `fontfamily`. - - -Text labels can be added to spatial (multi-)points, (multi-)lines, and (multi-)polygons, and each of the cases is quite different. -The simplest case is for POINT data, for which each text label will be located precisely in coordinates of the given points (Figure \@ref(fig:tmtext)). -However, how to add text labels to multipoints, lines, multilines, polygons, or multipolygons? -Should each label correspond to one spatial feature, or should every sub-feature have their own label? -Where should the labels be placed for lines or polygons - in the center of a line and centroid of a polygon or somewhat different? - - - -```{r, echo=FALSE, eval=FALSE} -metro3 = metro2 %>% - dplyr::mutate(g = rep(1:5, 6)) %>% - dplyr::group_by(g) %>% - dplyr::summarize() -tm_shape(metro3) + - tm_text(text = "g", size = "g") + - tm_layout(legend.outside = TRUE) -``` - - -```{r, warning=FALSE} -# x2 = x %>% -# dplyr::group_by(region_un) %>% -# dplyr::summarise() -# tm_shape(x2) + -# tm_polygons() + -# tm_text("region_un") -``` - - - - - - - - -Text labels are also often presented together with lines (Section \@ref(lines)). -One example is an isopleth - a line drawn on a map through all points having the same value of a given variable, such as atmospheric pressure or elevation. -Isopleths can be created with the `tm_iso()` function. - -```{r} -# to improve -library(stars) -ei_elev = read_stars("data/easter_island/ei_elev.tif") -ei_elev_raster = as(ei_elev, "Raster") -elev_isopleths = raster::rasterToContour(ei_elev_raster) - -tm_shape(elev_isopleths) + - tm_iso() -``` - -```{r} -hs = raster::hillShade(slope = raster::terrain(ei_elev_raster, "slope"), - aspect = raster::terrain(ei_elev_raster, "aspect")) - -tm_shape(hs) + - tm_grid() + - tm_raster(palette = gray(0:100 / 100), n = 100, legend.show = FALSE) + - tm_shape(ei_elev) + - tm_raster(alpha = 0.5, palette = terrain.colors(25), - legend.show = FALSE) + - tm_shape(elev_isopleths) + - tm_lines(col = "white") + - tm_text("level", col = "white") -``` - - - - - - - - - -## Raster {#raster-layer} - -```{r, message=FALSE} -library(stars) -ei_elev = read_stars("data/easter_island/ei_elev.tif") -ei_geomorphons = read_stars("data/easter_island/ei_geomorphons.tif") -``` - - -Visualization of raster data depends on the raster type (continuous or categorical), its resolution, and the number of layers. - -Figure \@ref(fig:rasterdown) shows two simple example of continuous and categorical raster visualization created with `tm_raster()`. -This function attempts to recognize the type of a given raster - when the input raster is continuous then the pretty style is used. -However, the `"cont"` style often better represent phenomena that progressively vary in space (Figure \@ref(fig:rastertype):A). - -```{r, eval=FALSE} -tm_shape(ei_elev) + - tm_raster(title = "Elevation (m asl):", style = "cont", palette = "viridis") -``` - -On the other hand, when the given raster is categorical, then `tm_raster` uses `style = "cat"` (Figure \@ref(fig:rastertype):A). -We can also adjust the legend title, used colors, and many more, in a similar fashion as in the previously mentioned layers. - -```{r, eval=FALSE} -tm_shape(ei_geomorphons) + - tm_raster(title = "Geomorphons:") -``` - -```{r rastertype, echo=FALSE, fig.cap="Examples of (A) continuous raster maps, and (B) categorical raster maps."} -tmrt1 = tm_shape(ei_elev) + - tm_raster(title = "Elevation (m asl):", style = "cont", palette = "viridis") + - tm_layout(title = "A") -tmrt2 = tm_shape(ei_geomorphons) + - tm_raster(title = "Geomorphons:", style = "cat") + - tm_layout(title = "B") -tmap_arrange(tmrt1, tmrt2) -``` - -The above examples used a raster with one layer only. -However, rasters can have many layers, either represented by dimensions or attributes. -By default, **tmap** shows all of the layers, where each raster has its own legend. - -```{r, results='hide', message=FALSE, fig.show='hide'} -raster2 = c(ei_elev, ei_geomorphons) -tm_shape(raster2) + - tm_raster() -``` - -We can modify their arrangement with `tm_facets()` (Figure \@ref(fig:tmrasterml)). - -```{r tmrasterml, fig.cap="A map created from a multilayered raster.", message=FALSE} -tm_shape(raster2) + - tm_raster() + - tm_facets(ncol = 1) + - tm_layout(panel.labels = c("Elevation", "Geomorphons")) -``` - -If you want to learn more - we focus on how to specify and modify facets (also known as small multiples) in Chapter \@ref(multiples) and how to modify map layout in Chapter \@ref(layout). - -```{r} -#to replace later -library(stars) -landsat = read_stars(system.file("raster/landsat.tif", package = "spDataLarge")) -``` - -The `landsat` object contains four bands (blue, green, red, and near-infrared) of the Landsat 8 image for the area of Zion National Park taken on 18th of August 2015. -We can plot all of the bands independently or as a combination of three bands. -This combination is known as a color composite image, and we can create such images with the `tm_rgb()` function (Figure \@ref(fig:tmrgbs)). - -Standard composite image (true color composite) uses the visible red, green, and blue bands to represent the data in natural colors. -We can specify which band in `landsat` relates to red (third band), green (second band), and blue (first band) color in `tm_rgb`. -Also, by default, this function expects values from 0 to 255; however, our values are in a different scale, with the maximum value of 31961. -Therefore, to create a map, we can set `max.value` to our dataset's maximum value. -The result is a true color composite, with green colors representing forests and other types of vegetation, and yellow color showing bare areas (Figure \@ref(fig:tmrgbs):A). - -```{r, eval=FALSE} -tm_shape(landsat) + - tm_rgb(r = 3, g = 2, b = 1, - max.value = 31961) -``` - -True color images are straightforward to interpret and understand, but they make subtle differences in features challenging to recognize. -However, nothing stops us from using the above tools to integrate different bands to create so called false color composites. -Various band combinations emphasize some spatial characteristics, such as water, agriculture, etc., and allows us to visualize wavelengths that our eyes can not see. - -Figure \@ref(fig:tmrgbs):B shows a composite of near-infrared, red, and green bands, highlighting vegetation with a bright red color. - -```{r, eval=FALSE} -tm_shape(landsat) + - tm_rgb(r = 4, g = 3, b = 2, - max.value = 31961) -``` - -```{r tmrgbs, fig.cap="Two color composite images: (A) true color composite, (B) false color composite.", message=FALSE, echo=FALSE} -tmrgb1 = tm_shape(landsat) + - tm_rgb(r = 3, g = 2, b = 1, - max.value = 32000) + - tm_layout(title = "A") -tmrgb2 = tm_shape(landsat) + - tm_rgb(r = 4, g = 3, b = 2, - max.value = 32000) + - tm_layout(title = "B") -tmap_arrange(tmrgb1, tmrgb2, asp = NA) -``` - - - - - -## Tile - - - - -Tile layers can be used for two purposes: either as a basemap or an overlay layer. -By default, three basemaps are used in the interactive mode (`tmap_mode("view")`): -`"Esri.WorldGrayCanvas"`, `"OpenStreetMap"`, and `"Esri.WorldTopoMap"`. -However, we can change the basemaps with a vector with the names of the tile layers' providers (Figure \@ref(fig:tmbasemap1)). - -```{r, eval=FALSE} -tmap_mode("view") -tm_basemap(c(StreetMap = "OpenStreetMap", - TopoMap = "OpenTopoMap")) + - tm_shape(volcanos, is.master = TRUE) + - tm_dots(col = "red", group = "Volcanos") -``` - -```{r tmbasemap1, fig.cap="OpenStreetMap tile layer used as a base map with the red dots representing volcanos on Easter Island.", cache = FALSE, eval=TRUE, echo=FALSE, message=FALSE} -tmap_mode("view") -tmbasemap1 = tm_basemap(c(StreetMap = "OpenStreetMap", - TopoMap = "OpenTopoMap")) + - tm_shape(volcanos, is.master = TRUE) + - tm_dots(col = "red", group = "Volcanos") -view_map(tmbasemap1, "tmbasemap1") -``` - -In the above code, we made two basemaps available - `"OpenStreetMap"` and `"OpenTopoMap"`, and for the map legend purpose, we renamed them as `StreetMap` and `TopoMap`. -A complete list of available basemaps is in the `leaflet::providers` object and on the https://leaflet-extras.github.io/leaflet-providers/preview/ website^[Additional details can be found in the `leaflet::providers.details` object]. - - - - - -The `tm_basemap(NULL)` function allows to disable basemaps entirely. - -The `tm_tiles()` function, on the other hand, draws the tile layer on the top (as overlay layer) of the previous `tm_` layer. -In the next example, we put the vector `"Stamen.TonerHybrid"` tiles on top of the previously set basemaps, but below the dots layer (Figure \@ref(fig:tmtiles1)). - -```{r, eval=FALSE} -tm_basemap(c(StreetMap = "OpenStreetMap", - TopoMap = "OpenTopoMap")) + - tm_tiles(c(TonerHybrid = "Stamen.TonerHybrid")) + - tm_shape(volcanos, is.master = TRUE) + - tm_dots(col = "red", group = "Volcanos") -``` - -```{r tmtiles1, fig.cap="OpenStreetMap tile layer used as a base map with dashed lines representing island coastline and the red dots representing volcanos on Easter Island.", cache = FALSE, eval=TRUE, echo=FALSE, message=FALSE} -tmtiles1 = tm_basemap(c(StreetMap = "OpenStreetMap", - TopoMap = "OpenTopoMap")) + - tm_tiles(c(TonerHybrid = "Stamen.TonerHybrid")) + - tm_shape(volcanos, is.master = TRUE) + - tm_dots(col = "red", group = "Volcanos") -view_map(tmtiles1, "tmtiles1") -``` - -Tile layers are usually created to be used interactively. -We can see it, for example, by their number of details varying depending on the zoom level we set. -That being said, many people find them useful also for static maps, and several packages and functions were created to allow downloading tile layers and using them for static maps. -It includes packages, such as **ceramic**, **mapmisc**, or **maptiles**. - - - - -Here, we focus on **maptiles**. -This package has one main function called `get_tiles()` that expects a spatial object with our area of interest and downloads a spatial data representing our tiles. - -The `get_tiles()` function also allows us to select one of many map tiles providers and decide on the zoom level we want to use from 0 to 20. -A complete list of available providers and some [information about zoom levels](https://wiki.openstreetmap.org/wiki/Zoom_levels) are in the help file of this function - `?get_tiles()`. -Different map tiles providers offer unique map styles, while zoom levels relate to different levels of detail -- the larger level, the more details we will get. -In some cases, also the `crop` argument set to `TRUE` can be useful - it returns a tile cropped to the area of interest. - -```{r, message=FALSE, cache=FALSE} -library(maptiles) -ei_tiles = get_tiles(ei_borders, provider = "Stamen.Toner", zoom = 12, crop = TRUE) -``` - -In the above example, we downloaded the data for the area of `ei_borders` from the `"Stamen.Toner"` provider using the zoom of level 12, and we cropped the tile to the extent of the island area. -The result is a spatial object with four layers, in which three first layers represent the visible red, green, and blue bands (section \@ref(raster-layer)). -This object's structure allows us to create a **tmap** map with the `tm_rgb()` function. - -When using map tiles, we should also consider adding their attribution to the map. -Attribution for each provider can be obtained using the `get_credit()` function by specifying the provider name, for example `get_credit("Stamen.Toner")`. - -The code below plots the `"Stamen.Toner"` tiles in the background, adds the island outline in light blue color, and puts attribution text in the bottom right corner of the map (Figure \@ref(fig:stiles)). - -```{r, echo=FALSE, cache=FALSE, message=FALSE} -library(tmap) -library(sf) -ei_borders = read_sf("data/easter_island/ei_border.gpkg") -``` - -(ref:stiles) Example of a static map using a downloaded `"Stamen.Toner"` tile layer. - -```{r stiles, cache=FALSE, fig.cap="(ref:stiles) "} -tmap_mode("plot") -tm_shape(ei_tiles) + - tm_rgb() + - tm_shape(ei_borders) + - tm_borders(lwd = 5, col = "lightblue") + - tm_credits(get_credit("Stamen.Toner"), - bg.color = "white") -``` diff --git a/05b-visual-variables.Rmd b/05b-visual-variables.Rmd deleted file mode 100644 index cffd14a..0000000 --- a/05b-visual-variables.Rmd +++ /dev/null @@ -1,872 +0,0 @@ - -# Visual variables - - - -Visual variables are methods to translate information given in variables into many types of visualizations, including maps. -Basic visual variables are color, size, and shape^[Other visual variables include position, orientation, and texture.]. -All of them can influence our perception and understanding of the presented information, therefore it is worth to understand when and how they can be used. - - - - -```{r visual-variables, echo=FALSE, warning=FALSE, fig.asp = .5, fig.cap="Basic visual variables and their representations on maps", message=FALSE} -source("code/visual_variables.R") -visual_variables() -``` - -The use of visual variables on maps depends on two main things: (a) type of the presented variable, and (b) type of the map layer. -Figure \@ref(fig:visual-variables) shows examples of different visual variables. -Color is the most universal visual variable. -It can represent both qualitative (categorical) and quantitative (numerical) variables, and also we can color symbols, lines, or polygon fillings (sections \@ref(color-palettes) and \@ref(color-scale-styles)). -Sizes, on the other hand, should focus on quantitative variables. -Small symbols could represent low values of a given variable, and the higher the value, the larger the symbol. -Quantitative values of line data can be shown with the widths of the lines (section \@ref(sizes)). -The use of shapes usually should be limited to qualitative variables, and different shapes can represent different categories of points (section \@ref(shapes)). - -Similarly, qualitative variables in lines can be presented by different line types. -Values of polygons usually cannot be represented by either shapes or sizes, as these two features are connected to the geometries of the objects. - - - - -## Colors - -\index{colors} -Colors, along with sizes and shapes, are the most often used to express values of attributes or their properties. -Proper use of colors draws the attention of viewers and has a positive impact on the clarity of the presented information. -On the other hand, poor decisions about colors can lead to misinterpretation of the map. -Section \@ref(color-palettes) explains how colors are represented in R, how to decide which colors to use, and how to set different colors on maps. -Section \@ref(color-scale-styles) focuses on how to specify color breaks and which types of scales styles are appropriate in different cases. - -### Color palettes - -\index{color palettes} - - - - - -\index{colors} -\index{hexadecimal form} -Colors in R are created based either on the color name or its hexadecimal form. -R understands 657 built-in color names, such as `"red"`, `"lightblue"` or `"gray90"`, that are available using the `colors()` function. - - -Hexadecimal form, on the other hand, can represent 16,777,216 unique colors. -It consists of six-digits prefixed by the `#` (hash) symbol, where red, green, and blue values are each represented by two characters. -In hexadecimal form, `00` is interpreted as `0.0` which means a lack of a particular color and `FF` means `1.0` and shows that the given color has maximal intensity. -For example, `#000000` represents black color, `#FFFFFF` white color, and `#00FF00` green color. - - -Using a single color we are able to draw points, lines, polygon borders, or their areas. -In that scenario, all of the elements will have the same color. -However, often we want to represent different values in our data using different colors. -This is a role for color palettes. -A color palette is a set of colors used to distinguish the values of variables on maps. - -\index{color palettes} -Color palettes in R are usually stored as a vector of either color names or hexadecimal representations. -For example, `c("red", "green", "blue")` or `c("#66C2A5", "#FC8D62", "#8DA0CB")`. -It allows every one of us to create our own color palettes. -However, the decision on how to decide which colors to use is not straightforward, and usually requires thinking about several aspects. - -\index{color properties} -Firstly, what kind of variable we want to show? - -Is it a categorical variable where each value represents a group or a numerical variable in which values have order? - -The variable type impacts how it should be presented on the map. -For categorical variables, each color usually should receive the same perceptual weight, which is done by using colors with the same brightness, but different hue. -On the other hand, for numerical variables, we should easily understand which colors represent lower and which represent higher values. -This is done by manipulating colorfulness and brightness. -For example, low values could be presented by a blue color with low colorfulness and high brightness, and with growing values, colorfulness increases and brightness decreases. - -\index{color perception} -Next consideration is related to how people perceive some colors. -Usually, we want them to be able to preliminary understand which values the colors represent without looking at the legend -- colors should be intuitive. -For example, in the case of categorical variables representing land use, we usually want to use some type of blue color for rivers, green for trees, and white for ice. -This idea also extends to numerical variables, where we should think about the association between colors and cultural values. -The blue color is usually connected to cold temperature, while the red color is hot or can represent danger or something not good. -However, we need to be aware that the connection between colors and cultural values varied between cultures. - - -\index{color blindness} -Another thing to consider is to use a color palette that is accessible for people with color vision deficiencies (color blindness). - -There are several types of color blindness, with the red-green color blindness (*deuteranomaly*) being the most common. -It is estimated that up to about 8% of the male population and about 0.5% of the female population in some regions of the world is color blind [@birch_worldwide_2012;@sharpe_opsin_1999]. - - - - -The relation between the selected color palette and other map elements or the map background should be also taken into a consideration. -For example, using a bright or dark background color on a map has an impact on how people will perceive different color palettes. - - - - - - - - - -\index{color palettes} -Generally, color palettes can be divided into three main types (Figure \@ref(fig:palette-types)): - -- **Categorical** (also known as Qualitative) - used for presenting categorical information, for example, categories or groups. -Every color in this type of palettes should receive the same perceptual weight, and the order of colors is meaningless. -Categorical color palettes are usually limited to dozen or so different colors, as our eyes have problems with distinguishing a large number of different hues. -Their use includes, for example, regions of the world or land cover categories. -- **Sequential** - used for presenting continuous variables, in which order matters. -Colors in this palette type changes from low to high (or vice versa), which is usually underlined by luminance differences (light-dark contrasts). -Sequential palettes can be found in maps of GDP, population density, elevation, and many others. -- **Diverging** - used for presenting continuous variables, but where colors diverge from a central neutral value to two extremes. -Therefore, in sense, they consist of two sequential palettes that meet in the midpoint value. -Examples of diverging palettes include maps where a certain temperature or median value of household income is use as the midpoint. -It can also be used on maps to represent difference or change as well. - - - -```{r palette-types, fig.cap="Examples of three main types of color palettes: categorical, sequential, and diverging", echo=FALSE, fig.asp=0.5} -# y - a named list of palettes -source("code/palette_figures.R") -p_cat = hcl.colors(7, "Set3") -p_seq = rev(hcl.colors(7, "YlOrBr")) -p_div = hcl.colors(7, "RdYlGn") - -p_cat2 = tmaptools::get_brewer_pal("Set2", n = 7, plot = FALSE) -p_seq2 = rev(hcl.colors(7, "viridis")) -p_div2 = hcl.colors(7, "BrBG") - -y = list(Categorical = list(Set3 = p_cat, Set2 = p_cat2), - Sequential = list(YlOrBr = p_seq, viridis = p_seq2), - Diverging = list(RdYlGn = p_div, BrBG = p_div2)) -plot_palette_types(y) -``` - - -\index{color palettes} -Gladly, a lot of work has been put on creating color palettes that are grounded in the research of perception and design. -Currently, [several dozens of R packages](https://github.com/EmilHvitfeldt/r-color-palettes -) contain hundreds of color palettes. -The most popular among them are **RColorBrewer** [@R-RColorBrewer] and **viridis** [@R-viridis]. -**RColorBrewer** builds upon a set of perceptually ordered color palettes [@harrower_colorbrewer_2003] and the associated website at https://colorbrewer2.org. -The website not only presents all of the available color palettes, but also allow to filter them based on their properties, such as being colorblind safe or print-friendly. -The **viridis** package has five color palettes are perceptually-uniform and suitable for people with color blindness. -Four palettes is this package ("viridis", "magma", "plasma", and "inferno") are derived from the work on the color palettes for [the matplotlib Python library](http://bids.github.io/colormap/). -The last one, "cividis", is based on the work of @nunez_optimizing_2018. - -```{r} -RColorBrewer::brewer.pal(7, "RdBu") -viridis::viridis(7) -``` - -\index{color palettes} -In the last few years, the **grDevices** package that is an internal part of R, have received several improvements over color palette handling.^[Learn more about them at https://developer.r-project.org/Blog/public/2019/04/01/hcl-based-color-palettes-in-grdevices/ and https://developer.r-project.org/Blog/public/2019/11/21/a-new-palette-for-r/index.html.] -It includes creation of `hcl.colors()` and `palette.colors()`. -The `hcl.colors()` function [incorporates color palettes from several R packages](http://colorspace.r-forge.r-project.org/articles/approximations.html), including **RColorBrewer**, **viridis**, **rcartocolor** [@carto_cartocolors_2019;@R-rcartocolor], and **scico** [@crameri_geodynamic_2018;@R-scico]. -You can get the list of available palette names for `hcl.colors()` using the `hcl.pals()` function and visualize all of the palettes with `colorspace::hcl_palettes(plot = TRUE)`. -The `palette.colors()` function adds [several palettes for categorical data](https://developer.r-project.org/Blog/public/2019/11/21/a-new-palette-for-r/index.html). -It includes `"Okabe-Ito"` [suited for color vision deficiencies](https://jfly.uni-koeln.de/color/) or `"Polychrome 36"` that has 36 unique colors [@coombes_polychrome_2019]. -You can find the available names of the palettes for this function using `palette.pals()` - -```{r} -grDevices::hcl.colors(7, "Oslo") -grDevices::palette.colors(7, "Okabe-Ito") -``` - -\index{color palettes!rainbow} -One of the most widely used color palettes is "rainbow" (the `rainbow()` function in R). -It was inspired by colors of rainbows - a set of seven colors going from red to violet. -However, this palette has a number of disadvantages, including irregular changes in brightness affecting its interpretation or being unsuitable for people with color vision deficiencies [@borland_rainbow_2007;@stauffer_somewhere_2015;@quinan_examining_2019]. -Depending on a given situation, there are many palettes better suited for visualization than "rainbow", including sequential `"viridis"` and `"ag_Sunset"` or diverging `"Purple-Green"` and `"Fall"`. -All of them can be created with the `grDevices::hcl.colors()` function. -More examples showing alternatives to the "rainbow" palette are in the documentation of the **colorspace** package at -https://colorspace.r-forge.r-project.org/articles/endrainbow.html [@R-colorspace]. - -```{r, echo=FALSE, warning=FALSE, message=FALSE} -library(tmap) -library(sf) -worldvector = read_sf("data/worldvector.gpkg") -``` - - -By default, the **tmap** package attempts to identify the type of the used variable. -Based on the result, it selects one of the build-in palettes: categorical `"Set3"`, sequential `"YlOrBr"`, or diverging `"RdYlGn"` (Figure \@ref(fig:tmpals)). - - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons("life_expectancy") -``` - -It also offers three main ways to specify color palettes using the `palette` argument: (1) a vector of colors, (2) a palette function, or (3) one of the build-in names (Figure \@ref(fig:tmpals)). -A vector of colors can be specified using color names or hexadecimal representations (Figure \@ref(fig:tmpals)). -Importantly, the length of the provided vector does not need to be equal to the number of colors in the map legend. -**tmap** automatically interpolates new colors in the case when a smaller number of colors is provided. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = c("yellow", "darkgreen")) -``` - -Another approach is to provide the output of a palette function (Figure \@ref(fig:tmpals)). -In the example below, we derived seven colors from `"ag_GrnYl"` palette. -This palette goes from green colors to yellow ones, however, we wanted to reverse the order of this palette. -Thus, we also used the `rev()` function here. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = rev(hcl.colors(7, "ag_GrnYl"))) -``` - -The last approach is to use one of the names of color palettes build-in in **tmap** (Figure \@ref(fig:tmpals)). -In this example, we used the `"YlGn"` palette that goes from yellow to green. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = "YlGn") -``` - -You can find all of the named color palettes using an interactive app with `tmaptools::palette_explorer()`. -It is also possible to reverse the order of any named color palette by using the `-` prefix. -Therefore, `"-YlGn"` will return a palette going from green to yellow. - -(ref:tmpals) Examples of four ways of specifying color palettes: (A) default sequential color palette, (B) palette created based on provided vector of colors, (C) palette created using the `hcl.colors()` function, and (D) one of the build-in palettes. - -```{r tmpals, warning=FALSE, fig.cap="(ref:tmpals)", echo=FALSE} -tm_pal1 = tm_shape(worldvector) + - tm_polygons("life_expectancy") + - tm_layout(title = "A") -tm_pal2 = tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = c("yellow", "darkgreen")) + - tm_layout(title = "B") -tm_pal3 = tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = rev(hcl.colors(7, "ag_GrnYl"))) + - tm_layout(title = "C") -tm_pal4 = tm_shape(worldvector) + - tm_polygons("life_expectancy", palette = "YlGn") + - tm_layout(title = "D") -tmap_arrange(tm_pal1, tm_pal2, tm_pal3, tm_pal4, - ncol = 2) -``` - - - -The default color palette for positive numerical variables is `"YlOrBr"` as seen in Figure \@ref(fig:tmmidpoint):A. -On the other hand, when the given variable has both negative and positive values, then **tmap** uses the `"RdYlGn"` color palette, with red colors below the midpoint value, yellow color around the midpoint value, and green colors above the midpoint value. -The use of diverging color palettes can be adjusted using the `midpoint` argument. -It has a value of 0 as the default, however, it is possible to change it to any other value. -For example, we want to create a map that shows countries with life expectancy below and above the median life expectancy of about 73 years. -To do that, we just need to set the `midpoint` argument to this value (Figure \@ref(fig:tmmidpoint):B). - - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "life_expectancy", midpoint = 73) -``` - -```{r tmmidpoint, warning=FALSE, fig.cap="Examples of (A) a map with the default sequential color palette and (B) a map with the diverging color palette around the midpoint value of 73.", fig.asp=NA, fig.height=2, echo=FALSE} -# mean(x$lifeExp, na.rm = TRUE) -tm_mp1 = tm_shape(worldvector) + - tm_polygons(col = "life_expectancy") + - tm_layout(title = "A") -tm_mp2 = tm_shape(worldvector) + - tm_polygons(col = "life_expectancy", midpoint = 71) + - tm_layout(title = "B") -tmap_arrange(tm_mp1, tm_mp2, ncol = 2) -``` - -Now the countries with low life expectancy are presented with red colors, yellow areas represent countries with life expectancy around the median value (the `midpoint` in our case), and the countries with high life expectancy are represented by green colors. - -The above examples all contain several polygons with missing values of a given variable. -Objects with missing values are, by default, represented by gray color and a related legend label *Missing*. -However, it is possible to change this color with the `colorNA` argument and its label with `textNA`. - -**tmap** has a special way to set colors for categorical maps manually. -It works by providing a named vector to the `palette` argument. -In this vector, names of the categories from the categorical variable are the vector names, and specified colors are the vector values. -You can see it in the example below, where we plot the `"region_un"` categorical variable (Figure \@ref(fig:tmcatpals)). -Each category in this variable (e.g., `"Africa"`) has a new, connected to it color (e.g., `"#11467b"`). - - -```{r tmcatpals, warning=FALSE, fig.cap="An example of a categorical map with manually selected colors.", fig.asp=NA, fig.height=2} -tm_shape(worldvector) + - tm_polygons("wb_region", - palette = c( - "Latin America & Caribbean" = "#11467b", - "Europe & Central Asia" = "#ffd14d", - "Middle East & North Africa" = "#86909a", - "Sub-Saharan Africa" = "#14909a", - "East Asia & Pacific" = "#7fbee9", - "South Asia" = "#df5454", - "North America" = "#7b1072") - ) -``` - -```{r, echo=FALSE, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "life_expectancy") -tm_shape(worldvector) + - tm_polygons(col = "life_expectancy", alpha = 0.5) -tm_shape(worldvector) + - tm_polygons(col = "life_expectancy", colorNA = "purple", textNA = "I do not know!") -tm_shape(worldvector) + - tm_polygons(col = "life_expectancy", contrast = c(0.4, 1)) -``` - -\index{color palettes!transparency} -Finally, visualized colors can be additionally modified. -It includes setting the `alpha` argument that represents the transparency of the used colors. -By default, the colors are not transparent at all as the value of `alpha` is 1. -However, we can decrease this value to 0 - total transparency. -The `alpha` argument is useful in two ways: one - it allows us to see-through some large objects (e.g., some points below the polygons or a hillshade map behind the colored raster of elevation), second - it makes colors more subtle. - - - - - - - - - - -### Color scale styles - - - - -\index{Color scale styles} -`tm_polygons()` accepts three ways of specifying the fill color with the `col` argument^[To see and compare examples of every color scale style from **tmap** visit https://geocompr.github.io/post/2019/tmap-color-scales/.]. -The first one is to fill all polygons with the same color. -This happens when we provide a single color value, either as a color name or its hexadecimal form (section \@ref(color-palettes)) (Figure \@ref(fig:colorscales1)). - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "lightblue") -``` - -```{r colorscales1, warning=FALSE, fig.cap="Example of a map with all polygons filled with the same color.", echo=FALSE, fig.asp=NA, fig.height=2} -tm_one = tm_shape(worldvector) + - tm_polygons(col = "lightblue") -tm_one -``` - -\index{Color scale styles} -\index{Categorical maps} -\index{Discrete maps} -\index{Continuous maps} -The second way of specifying the fill color is to provide a name of the column (variable) we want to visualize. -**tmap** behaves differently depending on the input variable type, but always automatically adds a map legend. -In general, a categorical map is created when the provided variable contains characters, factors, or is of the logical type. -However, when the provided variable is numerical, then it is possible to create either a discrete or a continuous map. - -\index{Categorical maps} -An example of a categorical map can be seen in Figure \@ref(fig:colorscales2). -We created it by providing a character variable's name, `"wb_region"`, in the `col` argument^[The `tm_polygons(col = "region_un", style = "cat")` code is run automatically in this case.]. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "wb_region") -``` - -```{r colorscales2, warning=FALSE, fig.asp=NA, fig.height=2, message=FALSE, echo=FALSE, fig.cap="Example of a map in which polygons are colored based on the values of a categorical variable."} -tm_cat = tm_shape(worldvector) + - tm_polygons(col = "wb_region") -tm_cat -``` - -It is possible to change the names of legend labels with the `labels` argument. -However, to change the order of legend labels, we need to provide an ordered factor variable's name instead of a character one. -As mentioned in the section \@ref(color-palettes), we can also change the used color palette with the `palette` argument. - -```{r, eval=FALSE, echo=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "wb_region", labels = as.character(1:7)) -``` - - -\index{Discrete maps} -Discrete maps, on the other hand, represent continuous numerical variables using discrete class intervals. -In other words, values are divided into several groups based on their properties. -Several approaches can be used to convert continuous variables to discrete ones, and each of them could result in different groups of values. -**tmap** has 14 different methods to create discrete maps that can be specified with the `style` argument. -Most of them (except `"log10_pretty"`) use the **classInt** package [@R-classInt] in the background, therefore some additional information can be found in the `?classIntervals` function's documentation. - -By default, the `"pretty"` style is used (Figure \@ref(fig:discrete-methods):A). -This style creates breaks that are whole numbers and spaces them evenly ^[For more information visit the `?pretty()` function documentation]. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap") -``` - -It is also possible to indicate the desired number of classes using the `n` argument, when the `"pretty"` style is used. -While not every `n` is possible depending on the input values, **tmap** will try to create a number of classes as close to possible to the preferred one. - -The next approach is to manually select the limits of each break with the `breaks` function (Figure \@ref(fig:discrete-methods):B). -This can be useful when we have some pre-defined breaks, or when we want to compare values between several maps. -It expects threshold values for each break, therefore, if we want to have three breaks, we need to provide four thresholds. -Additionally, we can add a label to each break with the `labels` argument. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - breaks = c(0, 10000, 30000, 111000), - labels = c("low", "medium", "high")) -``` - - - -Another approach is to create breaks automatically using one of many existing classification methods. -Three basic methods are `"equal"`, `"sd"`, and `"quantile"` styles. -Let's consider a variable with 100 observations ranging from 0 to 10. -The `"equal"` style divides the range of values into *n* equal-sized intervals. -This style works well when the values change fairly continuously and do not contain any outliers. -In **tmap**, we can specify the number of classes with the `n` argument or the number of classes will be computed automatically . -For example, when we set `n` to 4, then our breaks will represent four classes ranging from 0 to 2.5, 2.5 to 5, 5 to 7.5, and 7.5 to 10. -The `"sd"` style represents how much values of a given variable varies from its mean, with each interval having a constant width of the standard deviation. -This style is used when it is vital to show how values relate to the mean. -The `"quantile"` style creates several classes with exactly the same number of objects (e.g., spatial features), but having intervals of various lengths. -This method has an advantage or not having any empty classes or classes with too few or too many values. -However, the resulting intervals from the `"quantile"` style can often be misleading, with very different values located in the same class. - -To create classes that, on the one hand, contain similar values, and on the other hand, are different from the other classes, we can use some optimization method. -The most common optimization method used in cartography is the Jenks optimization method implemented at the `"jenks"` style (Figure \@ref(fig:discrete-methods):C). - - - - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "jenks") -``` - -The Fisher method (`style = "fisher"`) has a similar role, which creates groups with maximized homogeneity [@fisher_grouping_1958]. -A different approach is used by the `dpih` style, which uses kernel density estimations to select the width of the intervals [@wand_databased_1997]. -You can visit `?KernSmooth::dpih` for more details. - -Another group of classification methods uses existing clustering methods. -It includes k-means clustering (`"kmeans"`), bagged clustering (`"bclust"`), and hierarchical clustering (`"hclust"`). - - -Finally, there are a few methods created to work well for a variable with a heavy-tailed distribution, including `"headtails"` and `"log10_pretty"`. -The `"headtails"` style is an implementation of the head/tail breaks method aimed at heavily right-skewed data. -In it, values of the given variable are being divided around the mean into two parts, and the process continues iteratively for the values above the mean (the head) until the head part values are no longer heavy-tailed distributed [@jiang_head_2013]. -The `"log10_pretty"` style uses a logarithmic base-10 transformation (Figure \@ref(fig:discrete-methods):D). -In this style, each class starts with a value ten times larger than the beginning of the previous class. -In other words, each following class shows us the next order of magnitude. -This style allows for a better distinction between low, medium, and high values. -However, maps with logarithmically transformed variables are usually less intuitive for the readers and require more attention from them. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "log10_pretty") -``` - -(ref:discrete-methods) Examples of four methods of creating discrete maps: (A) default method ('pretty'), (B) the 'fixed' method with manually set breaks, (C) the 'jenks' method, and (D) the 'log10_pretty' method. - - -```{r discrete-methods, warning=FALSE, fig.asp=NA, fig.height=3.46, message=FALSE, echo=FALSE, fig.cap="(ref:discrete-methods)"} -tm_pre = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap") + - tm_layout(title = "A") -tm_fix = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - breaks = c(0, 10000, 30000, 111000), - labels = c("low", "medium", "high")) + - tm_layout(title = "B") -tm_jen = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "jenks") + - tm_layout(title = "C") -tm_lop = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "log10_pretty") + - tm_layout(title = "D") -tmap_arrange(tm_pre, tm_fix, tm_jen, tm_lop, ncol = 2, asp = NA, - outer.margins = 0) -``` - - - -\index{Continuous maps} -Continuous maps also represent continuous numerical variables, but without any discrete class intervals (Figure \@ref(fig:cont-methods)). -Three continuous methods exist in **tmap**: `cont`, `order`, and `log10`. -Values change increasingly in all of them, but they differ in the relations between values and colors. - -The `cont` style creates a smooth, linear gradient. -In other words, the change in values is proportionally related to the change in colors. -We can see that in Figure \@ref(fig:cont-methods):A, where the value change from 20,000 to 40,000 has a similar impact on the color scale as the value change from 40,000 to 60,000. -The `cont` style is similar to the `pretty` one, where the values also change linearly. -The main difference between these styles is that we can see differences between, for example, values of 45,000 and 55,000 in the former, while both values have exactly the same color in the later one. -The `cont` style works well in situations where there is a large number of objects in vectors or a large number of cells in rasters, and where the values change continuously (do not have many outliers). - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "cont") -``` - -However, when the presented variable is skewed or have some outliers, we can use either `order` or `log10` style. -The `order` style also uses a smooth gradient with a large number of colors, but the values on the legend do not change linearly (Figure \@ref(fig:cont-methods):B). - -It is fairly analogous to the `quantile` style, with the values on a color scale that divides a dataset into several equal-sized groups. - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "order") -``` - -Finally, the `log10` style is the continuous equivalent of the `log10_pretty` style (Figure \@ref(fig:cont-methods):C). - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "log10") -``` - -```{r cont-methods, warning=FALSE, fig.asp=NA, fig.height=3.46, message=FALSE, echo=FALSE, fig.cap="Examples of three methods of creating continuous maps: (A) the ‘cont’ method, (B) the ‘order’ method, and (C) the ‘log10’ method."} -tm_con = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "cont") + - tm_layout(title = "A") -tm_ord = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "order") + - tm_layout(title = "B") -tm_log = tm_shape(worldvector) + - tm_polygons(col = "gdp_per_cap", - style = "log10") + - tm_layout(title = "C") -tmap_arrange(tm_con, tm_ord, tm_log, ncol = 2, asp = NA, - outer.margins = 0) -``` - -The `tm_polygons()` also offer a third way of specifying the fill color. -When the `col` argument is set to `"MAP_COLORS"` then polygons will be colored in such a way that adjacent polygons do not get the same color (Figure \@ref(fig:colorscalesmc)). - -```{r, eval=FALSE} -tm_shape(worldvector) + - tm_polygons(col = "MAP_COLORS") -``` - -In this case, it is also possible to change the default colors with the `palette` argument, but also to activate the internal algorithm to search for a minimal number of colors for visualization by setting `minimize = TRUE`. - -```{r colorscalesmc, warning=FALSE, fig.cap="Example of a map with adjacent polygons having different colors.", echo=FALSE, fig.asp=NA, fig.height=2} -tm_uni = tm_shape(worldvector) + - tm_polygons(col = "MAP_COLORS") -tm_uni -``` - -All of the color scale styles mentioned above work not only for `tm_polygons()` - they can be also applied for `tm_symbols()` (and its derivatives - `tm_dots()`, `tm_bubbles()`, `tm_squares()`), `tm_lines()`, `tm_fill()`, and `tm_raster()`. -The `col` argument colors symbols' fillings in `tm_symbols()`, lines in `tm_lines()`, and cells in `tm_rasters()`. - - - - - -```{r, echo=FALSE, eval=FALSE} -data("metro", package = "tmap") -data("rivers", package = "tmap") -data("land", package = "tmap") -tm_shape(metro) + - tm_symbols(col = "pop2030", style = "cont") -tm_shape(rivers) + - tm_lines(col = "strokelwd", style = "cont") -tm_shape(land) + - tm_raster(col = "elevation", style = "cont") -``` - - - -## Sizes - -```{r} -ei_points = read_sf("data/easter_island/ei_points.gpkg") -volcanos = subset(ei_points, type == "volcano") -``` - -Differences in sizes between objects are relatively easy to recognize on maps. -Sizes can be used for points, lines (line widths), or text to represent quantitative (numerical) variables, where small values are related to small objects and large values are presented by large objects. -Large sizes can be also used to attract viewers' attention. - -By default, **t**maps present points, lines, or text objects of the same size. -For example, `tm_symbols()` returns a map where each object is a circle with a consistent size^[The default value of size is 1, which corresponds to the area of symbols that have the same height as one line of text.]. -We can change the sizes of all objects using the `size` argument (Figure \@ref(fig:tmsizes):A). - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_symbols(size = 0.5) -``` - -On the other hand, if we provide the name of the numerical variable in the `size` argument (e.g., `"elevation"`), then symbol sizes are scaled proportionally to the provided values. -Objects with small values will be represented by smaller circles, while larger values will be represented by larger circles (Figure \@ref(fig:tmsizes):B). - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_symbols(size = "elevation") -``` - - - - - - - - -We can adjust size legend breaks with `sizes.legend` and the corresponding labels with `sizes.legend.labels` (Figure \@ref(fig:tmsizes):C). -However, this only modifies the legend, not the related objects. - -```{r, eval=FALSE} -tm_shape(volcanos) + - tm_symbols(size = "elevation", - title.size = "Elevation", - sizes.legend = c(100, 600), - sizes.legend.labels = c("low", "high")) -``` - -For example in the above code, we just show examples of how symbols with population of one million and 10 million looks like on the map. - -```{r tmsizes, echo=FALSE, fig.cap="Examples of three approaches for changing sizes of symbols: (A) all symbols have a consistent size of 0.5, (B) sizes of symbols depends on the values of the elevation variable, (C) sizes of symbols have a manually created legend.", fig.asp=0.42, message=FALSE} -library(tmap) -tmsize1 = tm_shape(volcanos) + - tm_symbols(size = 0.5) + - tm_layout(title = "A") -tmsize2 = tm_shape(volcanos) + - tm_symbols(size = "elevation") + - tm_layout(title = "B") -tmsize3 = tm_shape(volcanos) + - tm_symbols(size = "elevation", - title.size = "Elevation", - sizes.legend = c(100, 600), - sizes.legend.labels = c("low", "high")) + - tm_layout(title = "C") -tmap_arrange(tmsize1, tmsize2, tmsize3, ncol = 3) -``` - -Widths of the lines can represent values of numerical variables for line data similar to sizes of the symbols for point data. -The `lwd` argument in `tm_lines()` creates thin lines for small values and thick lines for large values of the given variable (Figure \@ref(fig:tmlwd)). - -```{r tmlwd, fig.asp=0.66, fig.cap="Example of a map where lines' widths represent values of the corresponding lines."} -ei_roads = read_sf("data/easter_island/ei_roads.gpkg") -tm_shape(ei_roads) + - tm_lines(lwd = "strokelwd") -``` - -In the above example, values of the `"strokelwd"` are divided into four groups and represented by four line widths. -Lines' thickness can be change using the `scale` argument, where the value of 1 is the default, and increasing this values increases lines' thickness. -Also, similarly to the last example of the `tm_symbols` above, it is possible to modify the lines width legend, by changing its title (`title.lwd`), categories (`lwd.legend`), and their names (`lwd.legend.labels`). - - - -```{r, echo=FALSE, eval=FALSE} -tm_shape(rivers) + - tm_lines(lwd = "strokelwd") -tm_shape(rivers) + - tm_lines(lwd = "strokelwd", - scale = 2) -tm_shape(rivers) + - tm_lines(lwd = "strokelwd", - scale = 2, n = 6) -tm_shape(rivers) + - tm_lines(lwd = "strokelwd", - scale = 2, - title.lwd = "Line legend", - lwd.legend = c(1, 2, 3, 5, 10, 15), - lwd.legend.labels = LETTERS[1:6]) -``` - -Text labels have a role to name features on a map or just to highlight some of them. -Usually, the size of text labels is consistent for the same spatial objects. -However, text labels can be also used to represent the values of some numerical variables. -Figure \@ref(fig:tmtextsize) shows an example, in which text labels show names of different volcanos areas, while their sizes are related to their elevations. - - -```{r tmtextsize, fig.asp=0.25, fig.cap="Example of a map where text sizes represent elevations of the volcanos."} -tm_shape(volcanos) + - tm_text(text = "name", size = "elevation") + - tm_layout(legend.outside = TRUE) -``` - - - - - - - -## Shapes - - - - -Shapes allow representing different categories of point data. -They can be very generic, e.g., circle or square, just to be able to differentiate between categories, but often we use symbols that we associate with different types of features. -For example, we use the letter *P* for parking lots, *I* for information centers, an airplane symbol for airports, or a bus symbol for bus stops. - -To use different shapes, we should use the `shape` argument in the `tm_symbols()` function. -It expects the name of the categorical variable. - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(shape = "type", - title.shape = "Type:", - shapes.labels = c("Cave entrance", "Peak", "Volcano")) -``` - -By default, **tmap** uses symbols of filled circle, square, diamond, point-up triangle, and point-down triangle^[They are represented in R by numbers from 21 to 25.]. -However, it is also possible to customize used symbols, their title, and labels. -Legend title related to shapes is modified with the `title.shape` argument, while their labels use the `shapes.lables` argument. - -Shapes can be specified with the `shapes` argument, that allows using one of three options. -The first one is a numeric value that specifies the plotting character of the symbol^[However, this is not supported for the "view" mode.]. -A complete list of available symbols and their corresponding numbers is in the `?pch` function's documentation. - - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = c(0, 2, 5)) -``` - -Second option is to use a *grob* object. - - - -```{r} -# library(grid) -# library(ggplotify) -library(ggplot2) - -# p1 = as.grob(~barplot(1:10)) -# p2 = as.grob(expression(plot(rnorm(10), yaxt = "n", xaxt = "n", ann = FALSE, bty = "n"))) -# p3 = as.grob(function() plot(sin, yaxt = "n", xaxt = "n", ann = FALSE, bty = "n")) -p4 = ggplotGrob(ggplot(data.frame(x = 1:5, y = 1:5), aes(x, y)) + geom_point() + theme_void()) -``` - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = list(p4, p4, p4), - border.col = NULL) -``` - - - - - - - -The last possibility is to use an icon specification created with the `tmap_icons()` function, that uses any png images. -The `tmap_icons()` function accepts a vector of file paths or urls, and also allows setting the width and height of the icon. -In our example, we have three distinct groups, therefore we need to create new icons based on three images - `icon1.png`, `icon2.png`, and `icon3.png` in this case. - -```{r} -my_icons = tmap_icons(c("images/icon1.png", - "images/icon2.png", - "images/icon3.png")) -``` - -Now, we can use the prepared icons in the `shapes` argument (Figure \@ref(fig:tmsymshape):D). - - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = my_icons, - border.col = NULL) -``` - -```{r tmsymshape, fig.asp=1.32, echo=FALSE, fig.cap="Examples of two maps with different symbols: (A) default symbols, (B) user-defined symbols, (C) grob objects, and (D) icons."} -tmsymshape1 = tm_shape(ei_points) + - tm_symbols(shape = "type", - title.shape = "Type:", - shapes.labels = c("Cave entrance", "Peak", "Volcano")) + - tm_layout(title = "A") -tmsymshape2 = tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = c(0, 2, 5)) + - tm_layout(title = "B") -tmsymshape3 = tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = list(p4, p4, p4), - border.col = NULL) + - tm_layout(title = "C") -tmsymshape4 = tm_shape(ei_points) + - tm_symbols(shape = "type", - shapes = my_icons, - border.col = NULL) + - tm_layout(title = "D") -tmap_arrange(tmsymshape1, tmsymshape2, - tmsymshape3, tmsymshape4, - ncol = 1) -``` - -```{r tmlinlty, fig.asp=0.33, fig.cap="", echo=FALSE, eval=FALSE} -tm_shape(rivers) + - tm_lines(lty = 2) -``` - -## Mixing visual variables - -The values of a given variable can be expressed by different categorical or sequential colors in polygons. -Lines can be also colored by one variable, but also widths of the lines can represent values of another quantitative variable. -When we use symbols, then we are able to use colors for one qualitative or quantitative variable, sizes for a quantitative variable, and shapes for another qualitative variable. -Therefore, it is possible to mix some visual variables for symbols and lines. -This section shows only some possible examples of mixing visual variables. - -Figure \@ref(fig:mixsymb):A shows symbols, which sizes are scales based on the `sv` variable and they are colored using the values from `elevation`. -This can be set with the `size` and `col` arguments. - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(size = "sv", col = "elevation") -``` - -We can also modify all of the visual variables using the additional arguments explained in the previous sections. -For example, we can set the color style (`style`), color palette (`palette`), or specify shapes (`shapes`) (Figure \@ref(fig:mixsymb):B). - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(col = "elevation", style = "cont", palette = "Greens", - shape = "type", shapes = c(23, 24, 25)) -``` - -When we use plot polygons, there is only one visual variable we can use - color. -Therefore, changing of map legend's title in functions like `tm_polygons()` or `tm_fill()` is done with the `title` argument. -However, what to do when we have two or more visual variables in, for example, `tm_symbols()`? -In these cases, we need to specify a corresponding suffix for each `title` argument. -The color title is set with `title.col`, size title with `title.size`, and shape title with `title.size` (Figure \@ref(fig:mixsymb):C). - -```{r, eval=FALSE} -tm_shape(ei_points) + - tm_symbols(size = "elevation", title.size = "Elevation:", - shape = "type", title.shape = "Type:") -``` - -```{r mixsymb, echo = FALSE, fig.asp=1, fig.cap="Examples of maps using two visual variables at the same time: (A) size and color, (B) color and shape, (C) size and shape."} -tm_mvv1 = tm_shape(ei_points) + - tm_symbols(size = "sv", col = "elevation") + - tm_layout(title = "A") -tm_mvv2 = tm_shape(ei_points) + - tm_symbols(col = "elevation", style = "cont", palette = "Greens", - shape = "type", shapes = c(23, 24, 25)) + - tm_layout(title = "B") -tm_mvv3 = tm_shape(ei_points) + - tm_symbols(size = "elevation", title.size = "Elevation:", - shape = "type", title.shape = "Type:") + - tm_layout(title = "C") -tmap_arrange(tm_mvv1, tm_mvv2, tm_mvv3, ncol = 1) -``` - -For line data, we can present its qualitative and quantitative variables using colors and quantitative variables using sizes (line widths) (Figure \@ref(fig:mixline)). - -```{r mixline, echo = FALSE, fig.asp=0.66, fig.cap="A map using two visual variables, color and size (line width), at the same time."} -tm_shape(ei_roads) + - tm_lines(col = "type", lwd = "strokelwd", palette = "Set1") -``` - diff --git a/06-other-types.Rmd b/06-other-types.Rmd deleted file mode 100644 index 559c863..0000000 --- a/06-other-types.Rmd +++ /dev/null @@ -1 +0,0 @@ -# Other types {#othertypes} diff --git a/07-layout.Rmd b/07-layout.Rmd deleted file mode 100644 index bc0a21c..0000000 --- a/07-layout.Rmd +++ /dev/null @@ -1,426 +0,0 @@ -# Layout {#layout} - -## Typography - - -The decision about the used fonts is often neglected when creating programmable plots and maps. -Most often, the default fonts are used in these kinds of graphs. -This, however, could be a missed opportunity. -A lot of map information is expressed by text, including text labels (section \@ref(text)), legend labels, text in attribute layers (section \@ref(attributes-layers)), or the map title (section \@ref(layout-elements)). -The used fonts impact the tone of the map [@guidero_typography_2017], and their customization allows for a map to stand out from maps using default options. - - - -As we mentioned above, many different map elements can be expressed or can use fonts. -In theory, we are able to set different fonts to each of them. -However, this could result in a confusing visual mix that would hinder our map information. -Therefore, the decision on the used fonts should be taken after considering the main map message, expected map audience, other related graph styles, etc. -In the following three sections, we explain font families and font faces, and give some overall tips on font selections, show how to define used fonts, and how to customize fonts on maps. - -### Font families and faces - -```{r, echo=FALSE} -library(tmap) -data("World") -bf = c("plain", "italic", "bold", "bold.italic") -tm_bf = tm_shape(World) + - tm_borders(alpha = 0) + - tm_credits("plain", fontface = "plain", position = c("center", "center"), - size = 2) + - tm_credits("italic", fontface = "italic", position = c("center", "center"), - size = 2) + - tm_credits("bold", fontface = "bold", position = c("center", "center"), - size = 2) + - tm_credits("bold.italic", fontface = "bold.italic", position = c("center", "center"), - size = 2) + - tm_layout(frame = FALSE) -``` - -```{r, echo=FALSE} -library(tmap) -data("World") -# ff = c("Times", "Helvetica", "Courier") -ff = c("serif", "sans", "monospace") -tm_ff = tm_shape(World) + - tm_borders(alpha = 0) + - tm_credits("serif", fontfamily = "serif", position = c("center", "center"), - size = 2) + - tm_credits("sans", fontfamily = "sans", position = c("center", "center"), - size = 2) + - tm_credits("monospace", fontfamily = "monospace", position = c("center", "center"), - size = 2) + - tm_layout(frame = FALSE) -``` - -```{r fonts, fig.cap="Basic (A) font families, and (B) font faces implemented in the tmap package.", echo=FALSE} -tmap_arrange(tm_ff + tm_layout(title = "A. Font families"), tm_bf + tm_layout(title = "B. Font faces"), nrow = 1) -``` - -In **tmap**, fonts are represented by a font family (Figure \@ref(fig:fonts):A) and a font face (Figure \@ref(fig:fonts):B). -A font family is a collection of closely related lettering designs. -Examples of font families include *Times*, *Helvetica*, *Courier*, *Palatino*, etc. -On the other hand, font faces, such as *italic* or *bold*, influence the orientation or width of the fonts. -A *font* is, thus, a combination of a selected font family and font face. - -There are a few general font families, such as serifs, sans serifs, and monospaced fonts. -Fonts from the serif family have small lines (known as *a serif*) attached to the end of some letters. -Notice, for example, short horizontal lines on the bottom of letters *r*, *i*, and *f* or vertical lines at the ends of the letter *s* in the top row of Figure \@ref(fig:fonts):A. -The fonts in this family are often viewed as more formal. -On the other hand, the sans serif family do not have serifs and is considered more informal and modern. -The last font family, monospaced fonts, is often used in computer programming (IDEs, software text editors), but less often on maps. -A distinguishing feature of the monospaced fonts is that each letter or character in this family has the same width. -Therefore, letters, such as *i* and *a* will occupy the same space in the monospaced fonts. - - - -Mixing the use of serif and sans serif fonts often works well for maps. -However, a rule of thumb is not to mix more than two font families on one map. -A sans serif font can be used to label cultural objects, while serif fonts to label physical features. -Then, italics, for example, can be used to distinguish water areas. -The role of bold font faces, together with increased font size, is to highlight the hierarchy of labels - larger, bold fonts indicate more prominent map features. -Additionally, customizing the fonts' colors can be helpful to distinguish different groups of map objects. - - -The decision on which fonts to use should also relates to the expected map look and feel. -Each font family has a distinct personality (creates a "semantic effect"), which can affect how the map is perceived. - -Some fonts are more formal, some are less. -Some fonts have a modern look, while others look more traditional. - -Another important concern is personal taste or map branding. -We should filter the decision about the used fonts based on our preferences or even our sense of beauty as it could create more personal and unique maps. -We just need to remember about the readability of the fonts - they should not be too elaborate as it can hinder the main map message. - - - -### Fonts available in **tmap** {#fonts-tmap} - -Before we discuss how to set a font family and its face, it is important to highlight that a different set of fonts could exist for each operating system (and even each computer). -Additionally, which fonts are available and how they are supported depends on the used *graphic device*. -A graphic device is a place where a plot or map is rendered. -The most commonly it is a some kind of a screen device, where we can see our plot or map directly after running the R code. -Other graphic devices allow for saving plots or maps as files in various formats (e.g., `.png`, `.jpg`, `.pdf`). -Therefore, it is possible to get different fonts on your map on the screen, and a (slightly) different one when saved to a file. -Visit `?Devices` or read the Graphic Devices chapter of @peng2016exploratory to learn more about graphic devices. - -The **tmap** package has two mechanism to select a font family. -The first one is by specifying on of three general font families - `serif`, `sans`, or `monospace`. -It tries to match selected general font family with a font family existing on the operating system. - -For example, `serif` could the `Times` font family, `sans` - `Helvetica` or `Arial`, and `monospace` - `Courier` (Figure \@ref(fig:fonts):A). -The second mechanism allows to select a font family based on its name (e.g., `Times` or `Palatino`). -Next, a member of the selected font families can be selected with one of the font faces: `plain`, `italic`, `bold`, and `bold.italic` (Figure \@ref(fig:fonts):B). - - -As mentioned before, available fonts depend on the computer setup (including operating system) and used graphic device. -Fonts available on the operating system can be checked with the `system_fonts()` function of the **systemfonts** package [@R-systemfonts] (result not shown). - -```{r, eval=FALSE} -library(systemfonts) -system_fonts() -``` - -Information on installing and debugging custom fonts can be found in [a blog post](https://yjunechoe.github.io/posts/2021-06-24-setting-up-and-debugging-custom-fonts/) by June Choe and in the **showtext** package [@R-showtext] documentation. - -The next step is to either view or save the map. -This also means that we need to carry over our fonts to the output window/file, which largely depends on the selected graphic device. -In general, screen device or graphical raster output formats, such as PNG, JPEG, or TIFF, works well with custom fonts as they rasterize them during saving. -In case of any problems with graphical raster outputs, it is possible to try alternative graphics devices implemented in the **ragg** package [@R-ragg]. -On the other hand, graphical vector formats, such as PDF or SVG, could have some problems with saving maps containing custom fonts^[You can get the `invalid font type` error when saving the file.]. -The PDF device in R, by default, adds metadata about the used fonts, but does not store them. -When the PDF reader shows the document, it tries to locate the font on your computer, and use other fonts when the expected one does not exist. -An alternative approach is called embedding, which adds a copy of each necessary font to the PDF file itself. -Gladly, the creation of a PDF with proper fonts can be achieved in a few ways. -Firstly, it could be worth trying some alternative graphic devices such as `cairo_pdf` or `svglite::svglite`. -The second option is to use the **showtext** package [@R-showtext], which converts text into color-filled polygonal outlines for graphical vector formats. - -Thirdly, the **extrafont** [@R-extrafont] package allows embedding the fonts in the PDF file, which makes PDFs properly displayed on computers that do not have the given font. - - -### Fonts on maps - -```{r, message=FALSE} -library(tmap) -library(sf) -ei_borders = read_sf("data/easter_island/ei_border.gpkg") -ei_points = read_sf("data/easter_island/ei_points.gpkg") -volcanos = subset(ei_points, type == "volcano") -``` - - -By default, **tmap** uses the `sans` font family with the `plain` font face (Figure \@ref(fig:tmtext)). -There are, however, three ways to customize the used fonts. -The first one is to change all of the fonts and font faces for the whole map at once (Figure \@ref(fig:mfonts):A). -This can be done with the `fontfamily` and `fontface` arguments of `tm_layout()`. - -```{r font1, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons() + - tm_shape(volcanos) + - tm_text(text = "name", size = "elevation") + - tm_credits("Data source: OSM") + - tm_layout(main.title = "Volcanos of Easter Island", - fontface = "italic", - fontfamily = "serif") -``` - -The second way is to specify just some text elements independently (Figure \@ref(fig:mfonts):B). -Many **tmap** functions, such as `tm_text()` or `tm_credits()`, have their own `fontfamily` and `fontface` arguments that can be adjusted. -Additionally, `tm_layout()` allows to customize fonts for other map elements using prefixed arguments, such as, `main.title.fontface` or `legend.title.fontfamily`. - -```{r font2, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons() + - tm_shape(volcanos) + - tm_text(text = "name", size = "elevation", fontfamily = "sans") + - tm_credits("Data source: OSM", fontface = "bold") + - tm_layout(main.title = "Volcanos of Easter Island", - main.title.fontface = "bold.italic", - legend.title.fontfamily = "monospace") -``` - -```{r mfonts, echo=FALSE, fig.cap="Examples of (A) one font (font family and font face) used for all of the map elements (title, text labels, legend, and text annotation), and (B) different fonts used for different map elements."} -tmt1 = tm_shape(ei_borders) + - tm_polygons() + - tm_shape(volcanos) + - tm_text(text = "name", size = "elevation") + - tm_credits("Data source: OSM") + - tm_layout(main.title = "Volcanos of Easter Island", - fontface = "italic", - fontfamily = "serif", - title.fontface = "plain", - title.fontfamily = "sans", - title = "A") -tmt2 = tm_shape(ei_borders) + - tm_polygons() + - tm_shape(volcanos) + - tm_text(text = "name", size = "elevation", fontfamily = "sans") + - tm_credits("Data source: OSM", fontface = "bold") + - tm_layout(main.title = "Volcanos of Easter Island", - main.title.fontface = "bold.italic", - legend.title.fontfamily = "monospace", - title = "B") -tmap_arrange(tmt1, tmt2, nrow = 1) -``` - -The third way is to use a different *tmap style* - see section \@ref() for more details. - -```{r, eval=FALSE, echo=FALSE} -#saving tests -tmap_save(tmt2, "tmt2.png") -tmap_save(tmt2, "tmt2.pdf") -``` - -## Attributes layers - -```{r, echo=FALSE} -attr_layers_df = tibble::tribble( - ~Function, ~Description, - "tm_grid()", "draws coordinate grid lines of the coordinate system of the main shape object", - "tm_graticules()", "draws latitude and longitude graticules", - "tm_scale_bar()", "adds a scale bar", - "tm_compass()", "adds a compass rose", - "tm_credits()", "adds a text annotation", - "tm_logo()", "adds a logo", - "tm_xlab()", "adds an x axis labels", - "tm_ylab()", "adds an y axis labels", - "tm_minimap()", "adds minimap in the view mode only" -) -``` - - -\@ref(tab:attr-layers-table) - -```{r attr-layers-table, echo=FALSE, warning=FALSE} -library(magrittr) -library(kableExtra) -options(kableExtra.html.bsTable = TRUE) -knitr::kable(attr_layers_df, - caption = "Attribute layers.", - caption.short = "Attribute layers.", - booktabs = TRUE) %>% - kableExtra::kable_styling("striped", - latex_options = "striped", - full_width = FALSE) %>% - kableExtra::column_spec(1, bold = TRUE, monospace = TRUE) -``` - -For the examples in this section, we will use a simple map of the Easter Island polygon (not shown). - -```{r, fig.show='hide'} -tm = tm_shape(ei_borders) + - tm_polygons() -tm -``` - -### Grid lines - -The **tmap** package offers two ways to draws coordinate lines - `tm_grid()` and `tm_graticules()`. -The role of `tm_grid()` is to represent the input data’s coordinates. -For example, the `ei_borders` object's CRS is UTM zone 12S with the units in meters (figure \@ref(fig:grids):A). - -```{r, grid1, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons() + - tm_grid() -``` - -`tm_graticules()` shows longitude lines (meridians) and latitude lines (parallels), with degrees as units. -This can be seen with the degree signs in the labels (figure \@ref(fig:grids):B). - -```{r, grid2, eval=FALSE} -tm_shape(ei_borders) + - tm_polygons() + - tm_graticules() -``` - -Both, `tm_grid()` and `tm_graticules()` can be placed above or below the map layers as their position on the map depends on their place in the code. -When `tm_grid()` or `tm_graticules()` is placed after the map layer (e.g., `tm_polygons()`), the grid lines are plotted on the top of the map. -On the other hand, when `tm_grid()` or `tm_graticules()` is placed before the map layer code, the grid lines are plotted behind the spatial data (figure \@ref(fig:grids):C). - -```{r, grid3, eval=FALSE} -tm_shape(ei_borders) + - tm_graticules() + - tm_polygons() -``` - -Grids and graticules can also be easily customized using several arguments, such as, `x` and `y` (x and y coordinates of the lines), `n.x` and `n.y` (number of horizontal (x) and vertical (y) lines), `labels.inside.frame`, `ticks`, `lines` -It is also possible to customize their appearance, for example, by changing the colors of the lines (`col`), width (`lwd`) or labels' sizes (`labels.size`). - -```{r grids, fig.cap="(A) Map grid, (B) graticules, and (C) graticules put behind the map layer.", echo=FALSE, fig.asp=0.33} -tmap_arrange( - <> - + tm_layout(title = "A"), - <> - + tm_layout(title = "B"), - <> - + tm_layout(title = "C"), - nrow = 1 -) -``` - -### Scale bar - -Scale bar is a graphic indicator of the relation between a distance on a map and the corresponding distance in the real world. -Nowadays, it is more often used than a traditional representative fraction (e.g., 1:10000). -Compared to the representative fraction, scale bars work correctly on variable screen sizes or different print sizes, as their sizes change together with the rest of the map. - -The `tm_scale_bar()` function adds a scale bar. -By default, it tries to create a scale bar with the width of 1/4 of the whole map, and fills it with several breaks. -It is possible, however, to manually update the values of scale bar's breaks with the `breaks` argument and its size with the `text.size` argument (figure \@ref(fig:scalebar)). - -```{r scalebar, fig.cap="A map with a customized scale bar."} -tm + - tm_scale_bar(breaks = c(0, 2, 4), text.size = 1) -``` - -The `tm_scale_bar()` also has several additional arguments, allowing to modify its colors, and position (subsection \@ref(north-arrow)). - -Importantly, the scale bar is accurate, depending on a map projection, at standard points or lines only (subsection \@ref(types-of-map-projections)) - it is never completely correct across the whole map. -The scale bar distortion increases with the true size of the area we are mapping - it is less visible on local maps, and very prominent on global maps. -For example, try to add a scale bar to a world map seen in the section \@ref(shapes-and-layers). -The created scale bar will be accurate for the equator, but less and less correct going to the north and south poles. - -```{r, echo=FALSE, eval=FALSE} -library(stars) -library(tmap) -worldelevation = read_stars("data/worldelevation.tif") -tm_shape(worldelevation) + - tm_raster("worldelevation.tif", palette = terrain.colors(8)) + - tm_scale_bar() -``` - -### North arrow - -North arrow, also known as a map compass or a compass rose, is a prominent orientation indicator pointing to which way is north^[Orientation may also be shown by graticule or grid lines (subsection \@ref(grid-lines)).]. -The decision on whether to use north arrows or not usually requires some critical thinking. -While, it can be added to every map, north arrows are not always necessary - especially on maps of large areas (e.g., continents), where the cardinal directions are obvious for most people. -The North arrow is, however, necessary when the north on the map is offset (rotated) and recommended when we want to help orient the map readers. - -We can use the `tm_compass()` function to add the north arrow. -By default, its "north" is oriented toward the top of the map (the `north` argument of `0`), and the north arrow is represented by an actual arrow (the `type` argument of `"arrow"`). -**tmap** offers also a few other north arrow types, including `"4star"` (figure \@ref(fig:northarrow)), `"8star"`, `"radar"`, and `"rose"`. -The north arrow can be also further customized with the `size`, `show.labels` and `cardinal.directions` arguments, and its colors may be modified (`text.color`, `color.dark`, `color.light`) (figure \@ref(fig:northarrow)). - -```{r northarrow, fig.cap="A map with customized north arrow."} -tm + - tm_compass(type = "4star", size = 2, position = c("left", "top")) -``` - -The location of the north arrow, by default, is placed automatically, but can also be changed using the `position` argument. -It expects a vector of two values, specifying the x and y coordinates. -The x coordinate can be set with `"left"`, `"LEFT"`, `"center"`, `"right"`, or `"RIGHT"`, while the y coordinate uses `"top"`, `"TOP"`, `"center"`, `"bottom"`, or `"BOTTOM"`. -The arguments with all letters uppercase result in a position closer to the map frame (without margins). -Alternatively, `position` can be specified with numeric values between 0 and 1 representing the x and y value of the left bottom corner of the north arrow. - -The `position` argument also works in the same way in other functions, such as `tm_scale_bar()`, `tm_credits()`, `tm_logo()`, and in some of the `tm_layout()` arguments - `legend.position`, `title.position`, or `attr.position`. - -### Text annotation - - -Text annotations, also known as map credits, are used to store additional information about the created map. -They can include the source of data, the name of the author, the date of map creation, or information about the map projection. - -Text annotations are created with the `tm_credits()` function, which can be used more than one time (figure \@ref(fig:credits)). - -```{r credits, fig.cap="A map with placeholders for text annotations."} -tm + - tm_credits("Data source: ", fontface = "italic", align = "right") + - tm_credits("Author: ", fontface = "bold", align = "right") -``` - -The first argument of `tm_credits()` is the text, which can be spread over multiple lines with the line break symbol `\n`. -When the created map has several facets (section \@ref(multiples)), it is also possible to provide each facet a different text. -In that case, a vector of characters is expected, where you can use `""` to omit the credits for specific facets. -Text annotations can also be further customized, by changing their sizes (`size`), colors (`col`), -horizontal alignment (`align`), positions, and fonts (section \@ref(fonts-on-maps)). - -### Logo - -Logos on maps can serve a similar purpose as text annotation or accompany them. -They can represent your affiliation, funding institution, data sources logos, etc. - -The `tm_logo()` function adds png images, either from a file or url, to the map (figure \@ref(fig:logos)). - -```{r logos, fig.cap="A map with an array of R logos."} -tm + - tm_logo("https://www.r-project.org/logo/Rlogo.png", - height = 2) + - tm_logo(c("https://www.r-project.org/logo/Rlogo.png", - "https://www.r-project.org/logo/Rlogo.png"), - height = 1) -``` - -There are two ways to use multiple logos. -Many `tm_logo()` functions will places logos on top of each other, while providing a vector of png files will show them next to each other. -Additional arguments include the height of the logo (`height`, the width is scaled automatically) and its position (`position`). - -### Axis labels - -```{r} -tm + - tm_xlab("X") + - tm_ylab("Y") -``` - - - -### Minimap - -```{r} -tmap_mode("view") -``` - -```{r, eval=FALSE} -tm + - tm_minimap() -``` - - - -```{r} -tmap_mode("plot") -``` - - -## Layout elements diff --git a/08-interactive.Rmd b/08-interactive.Rmd deleted file mode 100644 index 3255f04..0000000 --- a/08-interactive.Rmd +++ /dev/null @@ -1 +0,0 @@ -# Interactive settings {#interactive} diff --git a/10-options.Rmd b/10-options.Rmd deleted file mode 100644 index 7ec5175..0000000 --- a/10-options.Rmd +++ /dev/null @@ -1 +0,0 @@ -# tmap options {#options} diff --git a/12-animations.Rmd b/12-animations.Rmd deleted file mode 100644 index 97189c3..0000000 --- a/12-animations.Rmd +++ /dev/null @@ -1,3 +0,0 @@ -# Animations - - \ No newline at end of file diff --git a/13-shiny.Rmd b/13-shiny.Rmd deleted file mode 100644 index 5235057..0000000 --- a/13-shiny.Rmd +++ /dev/null @@ -1 +0,0 @@ -# **tmap** in **shiny** {#shiny} diff --git a/14-good-maps.Rmd b/14-good-maps.Rmd deleted file mode 100644 index 6146633..0000000 --- a/14-good-maps.Rmd +++ /dev/null @@ -1 +0,0 @@ -# How to make good maps? {#goodmaps} diff --git a/DESCRIPTION b/DESCRIPTION index 73d4111..ae25b91 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,23 +1,19 @@ Package: tmapbook Type: Book Title: Elegant and informative maps with tmap -Version: 0.0.2 +Version: 0.0.3 Imports: - bookdown, tmaptools, tmap, spDataLarge, spData, dplyr, tidyr, - rgdal, rmapshaper, - kableExtra, + tinytable, curl, - maptiles, webshot Remotes: - rstudio/bookdown, - mtennekes/tmaptools, - mtennekes/tmap, + r-tmap/tmaptools, + r-tmap/tmap, Nowosad/spDataLarge \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 03a2f04..0000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM geocompr/geocompr:rstudio_preview -RUN R -e "remotes::install_cran('tinytest')" -# install the r-spatial stack linking to new OSGeo pkgs -RUN su rstudio && \ - cd /home/rstudio && \ - wget https://github.com/mtennekes/tmap_book/archive/master.zip && \ - unzip master.zip && \ - mv tmap_book-master /home/rstudio/tmap_book && \ - cd tmap_book && \ - make html -RUN chown -Rv rstudio /home/rstudio/tmap_book \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index e3c5ba5..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -html: - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::gitbook")' - Rscript code/generate_book_stats.R - cp -r widgets _book/. - #mv widgets _book/. - -pdf: - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::pdf_book")' - cp -r widgets _book/. - -all: - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::pdf_book")' - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::epub_book")' - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::gitbook", clean = FALSE)' - cp -r widgets _book/. - -both: - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::pdf_book")' - Rscript -e 'bookdown::render_book("index.Rmd", output_format = "bookdown::gitbook", clean = FALSE)' - cp -r widgets _book/. - -clean: - Rscript -e "bookdown::clean_book(TRUE)" - rm -fvr *.log Rplots.pdf _bookdown_files - -cleaner: - make clean && rm -fvr rsconnect - rm -frv *.aux *.out *.toc # Latex output - rm -fvr *.html # rogue html files - rm -fvr *.rds # rogue rds files - rm -fvr *utf8.md # rogue md files - rm -fvr *.Rmd~ # rogue rmd~ files \ No newline at end of file diff --git a/README.md b/README.md index 51eb5af..9e072f1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # tmap_book - [![R build status](https://github.com/mtennekes/tmap_book/workflows/Render-Book-from-master/badge.svg)](https://github.com/mtennekes/tmap_book/actions) + [![R build status](https://github.com/geocompx/tmap/workflows/Render/badge.svg)](https://github.com/geocompx/tmap/actions) tmap: elegant and effective thematic maps in R diff --git a/XX-animations.qmd b/XX-animations.qmd new file mode 100644 index 0000000..28672b9 --- /dev/null +++ b/XX-animations.qmd @@ -0,0 +1,8 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Animations {#sec-animations} + + diff --git a/XX-attr-layers.qmd b/XX-attr-layers.qmd new file mode 100644 index 0000000..b337e44 --- /dev/null +++ b/XX-attr-layers.qmd @@ -0,0 +1,251 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Attributes layers {#sec-attributes-layers} + +```{r} +#| echo: false +attr_layers_df = tibble::tribble( + ~Function, ~Description, + "tm_grid()", "draws coordinate grid lines of the coordinate system of the main shape object", + "tm_graticules()", "draws latitude and longitude graticules", + "tm_scalebar()", "adds a scale bar", + "tm_compass()", "adds a compass rose", + "tm_credits()", "adds a text annotation", + "tm_logo()", "adds a logo", + "tm_xlab()", "adds an x axis labels", + "tm_ylab()", "adds an y axis labels", + "tm_minimap()", "adds minimap in the view mode only" +) +``` + + +@tbl-attr-layers-table + +```{r} +#| label: tbl-attr-layers-table +#| echo: false +#| warning: false +tinytable::tt(attr_layers_df, caption = "Attribute layers.") |> + tinytable::style_tt(j = 1, monospace = TRUE) +# library(magrittr) +# library(kableExtra) +# options(kableExtra.html.bsTable = TRUE) +# knitr::kable(attr_layers_df, +# caption = "Attribute layers.", +# caption.short = "Attribute layers.", +# booktabs = TRUE) |> +# kableExtra::kable_styling("striped", +# latex_options = "striped", +# full_width = FALSE) |> +# kableExtra::column_spec(1, bold = TRUE, monospace = TRUE) +``` + +```{r} +#| message: false +library(tmap) +library(sf) +ei_borders = read_sf("data/easter_island/ei_border.gpkg") +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanos = subset(ei_points, type == "volcano") +``` + +For the examples in this section, we will use a simple map of the Easter Island polygon (not shown). + +```{r} +#| fig-show: hide +tm = tm_shape(ei_borders) + + tm_polygons() +tm +``` + +## Grid lines {#sec-grid-lines} + +The **tmap** package offers two ways to draws coordinate lines - `tm_grid()` and `tm_graticules()`. +The role of `tm_grid()` is to represent the input data’s coordinates. +For example, the `ei_borders` object's CRS is UTM zone 12S with the units in meters (@fig-grids-1), and thus the grid lines are in meters. + +```{r} +#| label: grid1 +#| eval: false +tm_shape(ei_borders) + + tm_polygons() + + tm_grid() +``` + +`tm_graticules()` shows longitude lines (meridians) and latitude lines (parallels), with degrees as units. +This can be seen with the degree signs in the labels (@fig-grids-2). + +```{r} +#| label: grid2 +#| eval: false +tm_shape(ei_borders) + + tm_polygons() + + tm_graticules() +``` + +Both, `tm_grid()` and `tm_graticules()` can be placed above or below the map layers as their position on the map depends on their place in the code. +When `tm_grid()` or `tm_graticules()` is placed after the map layer (e.g., `tm_polygons()`), the grid lines are plotted on the top of the map. +On the other hand, when `tm_grid()` or `tm_graticules()` is placed before the map layer code, the grid lines are plotted behind the spatial data (@fig-grids-3). + +```{r} +#| label: grid3 +#| eval: false +tm_shape(ei_borders) + + tm_graticules() + + tm_polygons() +``` + +Grids and graticules can also be easily customized using several arguments, such as, `x` and `y` (x and y coordinates of the lines), `n.x` and `n.y` (number of horizontal (x) and vertical (y) lines), `labels.inside.frame`, `ticks`, `lines` +It is also possible to customize their appearance, for example, by changing the colors of the lines (`col`), width (`lwd`) or labels' sizes (`labels.size`). + +```{r} +#| label: fig-grids +#| echo: false +#| layout-ncol: 3 +#| fig-cap: Examples of grid lines and graticules. +#| fig-subcap: +#| - Grid lines +#| - Graticules +#| - Graticules behind the map layer +<> +<> +<> +``` + +## Scale bar {#sec-scale-bar} + +Scale bar is a graphic indicator of the relation between a distance on a map and the corresponding distance in the real world. +Nowadays, it is more often used than a traditional representative fraction (e.g., 1:10000). +Compared to the representative fraction, scale bars work correctly on variable screen sizes or different print sizes, as their sizes change together with the rest of the map. + +The `tm_scalebar()` function adds a scale bar. + + +It is possible, however, to manually update the values of scale bar's breaks with the `breaks` argument and its size with the `text.size` argument (@fig-scalebar). + +```{r} +#| label: fig-scalebar +#| fig-cap: A map with a customized scale bar. +tm + + tm_scalebar(breaks = c(0, 1, 2), text.size = 1) +``` + +The `tm_scalebar()` also has several additional arguments, allowing to modify its colors, and position (@sec-north-arrow). + +Importantly, the scale bar is accurate, depending on a map projection, at standard points or lines only (@sec-proj-types) -- it is never completely correct across the whole map. +The scale bar distortion increases with the true size of the area we are mapping -- it is less visible on local maps, and very prominent on global maps. +For example, try to add a scale bar to a world map seen in the @sec-shapes-and-layers. +The created scale bar will be accurate for the equator, but less and less correct going to the north and south poles. + +```{r} +#| echo: false +#| eval: false +library(stars) +library(tmap) +worldelevation = read_stars("data/worldelevation.tif") +tm_shape(worldelevation) + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) + + tm_scalebar() +``` + +## North arrow {#sec-north-arrow} + +North arrow, also known as a map compass or a compass rose, is a prominent orientation indicator pointing to which way is north^[Orientation may also be shown by graticule or grid lines (@sec-grid-lines).]. +The decision on whether to use north arrows or not usually requires some critical thinking. +While, it can be added to every map, north arrows are not always necessary -- especially on maps of large areas (e.g., continents), where the cardinal directions are obvious for most people. +The north arrow is, however, necessary when the north on the map is offset (rotated) and recommended when we want to help orient the map readers. + +We can use the `tm_compass()` function to add the north arrow. +By default, its *north* is oriented toward the top of the map (the `north` argument of `0`), and the north arrow is represented by an actual arrow (the `type` argument of `"arrow"`). +**tmap** offers also a few other north arrow types, including `"4star"` (@fig-northarrow), `"8star"`, `"radar"`, and `"rose"`. +The north arrow can be also further customized with the `size`, `show.labels` and `cardinal.directions` arguments, and its colors may be modified (`text.color`, `color.dark`, `color.light`) (@fig-northarrow). + +```{r} +#| label: fig-northarrow +#| fig-cap: A map with customized north arrow. +tm + + tm_compass(type = "4star", size = 2, position = c("left", "top")) +``` + +The location of the north arrow, by default, is placed automatically, but can also be changed using the `position` argument. + + + + + + +The `position` argument also works in the same way in other functions, such as `tm_scalebar()`, `tm_credits()`, `tm_logo()`, and in some of the `tm_layout()` arguments: `legend.position`, `title.position`, or `chart.position`. + +## Text annotation {#sec-text-annotation} + + +Text annotations, also known as map credits, are used to store additional information about the created map. +They can include the source of data, the name of the author, the date of map creation, or information about the map projection. + +Text annotations are created with the `tm_credits()` function, which can be used more than one time (@fig-credits). + +```{r} +#| label: fig-credits +#| fig-cap: A map with placeholders for text annotations. +tm + + tm_credits("Data source: ", fontface = "italic") + + tm_credits("Author: ", fontface = "bold") +``` + +The first argument of `tm_credits()` is the text, which can be spread over multiple lines with the line break symbol `\n`. +When the created map has several facets (@sec-multiples), it is also possible to provide each facet a different text. +In that case, a vector of characters is expected, where you can use `""` to omit the credits for specific facets. +Text annotations can also be further customized, by changing their sizes (`size`), colors (`color`), positions, and fonts (@sec-fonts-on-maps). + +## Logo {#sec-logo} + +Logos on maps can serve a similar purpose as text annotation or accompany them. +They can represent your affiliation, funding institution, data sources logos, etc. + +The `tm_logo()` function adds png images, either from a file or url, to the map (@fig-logos). + +```{r} +#| label: fig-logos +#| fig-cap: A map with an array of R logos. +tm + + tm_logo("https://www.r-project.org/logo/Rlogo.png", + height = 2) + + tm_logo(c("https://www.r-project.org/logo/Rlogo.png", + "https://www.r-project.org/logo/Rlogo.png"), + height = 1) +``` + +There are two ways to use multiple logos. +Many `tm_logo()` functions will places logos on top of each other, while providing a vector of png files will show them next to each other. +Additional arguments include the height of the logo (`height`, the width is scaled automatically) and its position (`position`). + +## Axis labels {#sec-axis-labels} + +```{r} +tm + + tm_xlab("X") + + tm_ylab("Y") +``` + + + +## Minimap {#sec-minimap} + +```{r} +tmap_mode("view") +``` + +```{r} +tm + + tm_minimap() +``` + + + +```{r} +tmap_mode("plot") +``` diff --git a/XX-data_processing.qmd b/XX-data_processing.qmd new file mode 100644 index 0000000..a409855 --- /dev/null +++ b/XX-data_processing.qmd @@ -0,0 +1,78 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Data preparation {#sec-data-prep} + +```{r} +#| message: false +library(tmap) +library(sf) +worldvector = read_sf("data/worldvector.gpkg") +``` + +## Data simplification {#sec-data-simplification} + + + +Geometries in spatial vector data consists of sets of coordinates (@sec-vector-data-model). +Spatial vector objects grow larger with more features to present and more details to show, and this also has an impact on time to render a map. + +@fig-vectordown-1 shows a map of countries from the `worldvector` object. + +```{r} +#| label: vectordown1 +#| eval: false +tm_shape(worldvector) + + tm_polygons() +``` + +This level of detail can be good for some maps, but sometimes the number of details can make reading the map harder. +To create a simplified (smoother) version of vector data, we can use the `ms_simplify` function of the **rmapshaper** package. +. +It expects a numeric value from 0 to 1 -- a proportion of vertices in the data to retain. +In the example below, we set `keep` to 0.05, which keeps 5% of vertices (@fig-vectordown-2). + +```{r} +#| label: vectordown2 +#| eval: false +library(rmapshaper) +worldvector_s1 = ms_simplify(worldvector, keep = 0.05) +tm_shape(worldvector_s1) + + tm_polygons() +``` + +The process of simplification can also be more controlled. +By default, the underlining algorithm (called the Visvalingam method, learn more at https://bost.ocks.org/mike/simplify/), removes small features, such as islands in our case. +This could have far-reaching consequences - in the process of simplification, we could remove some countries! +To prevent the deletion of small features, we also need to set `keep_shapes` to `TRUE`. +In the case of one country consisting of many small polygons, only one is sure to be retained. +For example, look at New Zealand, which is now only represented by Te Waipounamu (the South Island). +To keep all of the spatial geometries (even the smallest of islands), we should also specify `explode` to `TRUE`. + +```{r} +#| label: vectordown3 +#| eval: false +worldvector_s2 = ms_simplify(worldvector, keep = 0.05, + keep_shapes = TRUE, explode = TRUE) +tm_shape(worldvector_s2) + + tm_polygons() +``` + +@fig-vectordown-3 contains a simplified map, where each spatial geometry of the original map still exists, but in a less detailed form. + +```{r} +#| label: fig-vectordown +#| echo: false +#| message: false +#| layout-ncol: 3 +#| fig-cap: "A map of world's countries based on:" +#| fig-subcap: +#| - original data +#| - simplified data with 5% of vertices kept +#| - simplified data with 5% of vertices, all features, and all polygons kept +<> +<> +<> +``` \ No newline at end of file diff --git a/02-geodata.Rmd b/XX-geodata.qmd similarity index 78% rename from 02-geodata.Rmd rename to XX-geodata.qmd index 8b50d54..6f205ef 100644 --- a/02-geodata.Rmd +++ b/XX-geodata.qmd @@ -1,48 +1,48 @@ -# Spatial data in R {#geodata} +# Spatial data in R {#sec-geodata} \index{spatial data} -## Introduction {#intro-geodata} +## Introduction {#sec-intro-geodata} Vector and raster data models are two basic models used to represent spatial data. These spatial data models are closely related to map making, with each model having its own pros and cons. -This chapter stars by describing several popular spatial data models (section \@ref(data-models)). +This chapter stars by describing several popular spatial data models (@sec-data-models). Each data model is introduced, explained how it is built, and how it is stored using different file formats. -Next, this chapter presents how these different data models are implemented in R (section \@ref(spatial-data-representations-in-r)). +Next, this chapter presents how these different data models are implemented in R (@sec-spatial-data-representations-in-r). It includes showing how to read different spatial data formats, how to understand spatial R objects, and where to find more information about preprocessing spatial data. -Finally, it focuses on the map projections (section \@ref(crs)). +Finally, it focuses on the map projections (@sec-crs). This section gives a background on why do we need map projections and how to translate spatial data from an ellipsoid into a flat surface or computer screen. It also explains basic terms and gives an overview of map projections. -## Data models +## Data models {#sec-data-models} Traditionally, spatial data is described by two basic data models: -vector data model aimed at (section \@ref(vector-data-model)) representing the world using points, lines, and polygons, and raster data model focused on representing surfaces (section \@ref(raster-data-model)). +vector data model aimed at (@sec-vector-data-model) representing the world using points, lines, and polygons, and raster data model focused on representing surfaces (@sec-raster-data-model). Additionally, now we have an abundance of available spatial data and a variety of ways to obtain it. It includes having many district variables and repeated measurements for the same area. -Therefore, we also present the concept of spatial data cubes (section \@ref(spatial-data-cubes)). +Therefore, we also present the concept of spatial data cubes (@sec-spatial-data-cubes). -### Vector data model +### Vector data model {#sec-vector-data-model} \index{vector data model} \index{spatial geometries} \index{spatial attributes} -The vector data model represent the world as a set of spatial geometries with non-spatial attributes (Figure \@ref(fig:vector-data-model)). +The vector data model represent the world as a set of spatial geometries with non-spatial attributes (@fig-vector-data-model). The role of geometry is to describe the location and shape of spatial objects. Attributes, on the other hand, are used to store the properties of the data. \index{spatial geometries} -There are three basic types of geometries: points, lines, and polygons, all of them are made up of coordinates (left part of Figure \@ref(fig:vector-data-model)). +There are three basic types of geometries: points, lines, and polygons, all of them are made up of coordinates (left part of @fig-vector-data-model). A point is represented by a pair of coordinates, usually described as X and Y, allowing for locating this point in some space. -X and Y could be unitless, in degrees, or in some measure units, such as meters (extended discussion on coordinates and related topics is in section \@ref(crs)). +X and Y could be unitless, in degrees, or in some measure units, such as meters (extended discussion on coordinates and related topics is in section @sec-crs). Points can represent features on different spatial scales, from a GPS position, location of a bench in a park, to a city on a small scale map. They are also used to express abstract features, such as locations of map labels. Properties of points can be expressed on maps by different point sizes, colors, or shapes. @@ -65,11 +65,13 @@ For example, a lake with an island can be depicted as a polygon with a hole. The values of polygons' attributes can be represented by the areas (fill) colors. \index{spatial attributes} -The second part of the vector data model relates to non-spatial attributes (right part of Figure \@ref(fig:vector-data-model)). +The second part of the vector data model relates to non-spatial attributes (right part of @fig-vector-data-model). Attributes are usually stored as a table, in which each column depicts some property, such as an identification number, a name of a feature, or a value of some characteristic. Each row, on the other hand, relates to a single spatial geometry. -```{r, echo=FALSE, message=FALSE} +```{r} +#| echo: false +#| message: false library(sf) library(tmap) point_data = st_sf( @@ -78,26 +80,28 @@ point_data = st_sf( geom = st_sfc( st_multipoint(rbind(c(1, 2))), st_multipoint(rbind(c(1.4, 1.2), c(2, 1.5))), - crs = 4326) + crs = "EPSG:4326") ) ``` -```{r, echo=FALSE} +```{r} +#| echo: false linestring_sfg1 = st_linestring(rbind(c(0.9, 1.9), c(1.6, 1.9), c(1.8, 1.8))) linestring_sfg2 = st_linestring(rbind(c(0.8, 1.6), c(1.4, 1.5), c(2, 1.6))) -linestring_sfg3 = st_linestring(rbind(c(1.1, 1), c(1.6, 1.2), c(1.9, 1.2))) +linestring_sfg3 = st_linestring(rbind(c(1.1, 1), c(1.6, 1.2), c(1.9, 1.2))) lines_data = st_sf( id = as.factor(1:3), type = c("a", "b", "c"), geom = st_sfc(linestring_sfg1, linestring_sfg2, linestring_sfg3, - crs = 4326) + crs = "EPSG:4326") ) lines_data_p = st_cast(lines_data, "MULTIPOINT") ``` -```{r, echo=FALSE} +```{r} +#| echo: false polygon_sfg1 = st_polygon(list(rbind(c(1.4, 1.6), c(1.6, 1.6), c(1.6, 1.8), @@ -121,15 +125,22 @@ polygon_data = st_sf( type = c("a", "b"), geom = st_sfc(polygon_sfg1, polygon_sfg2, - crs = 4326) + crs = "EPSG:4326") ) polygon_data_p = st_cast(polygon_data, "MULTIPOINT") ``` -```{r vector-data-model, echo=FALSE, fig.width = 5, fig.asp = 1, fig.cap="Instances of spatial vector data model: POINTS, LINES, and POLYGONS.", message=FALSE} +```{r} +#| label: fig-vector-data-model +#| echo: false +#| message: false +#| warning: false +#| fig-width: 5 +#| fig-asp: 1 +#| fig-cap: 'Instances of spatial vector data model: POINTS, LINES, and POLYGONS.' source("code/data_model_figures.R") -draw_vector_data(scale = .75) +draw_vector_data(scale = 0.75) ``` \index{simple feature} @@ -142,7 +153,7 @@ We can have a POINT feature and a MULTIPOINT feature, and similarly LINESTRING a The main difference between single element features (such as POINT or POLYGON) and multi-element features (such as MULTIPOINT or MULTIPOLYGON) can be clearly seen by looking at attribute tables. For example, six points stored as POINT features fill six separate rows, while six points stored as just one MULTIPOINT feature occupy just one row. -Examples of single- and multi-element features can be seen in Figure \@ref(fig:vector-data-model). +Examples of single- and multi-element features can be seen in @fig-vector-data-model. The top example shows point data represented as MULTIPOINT feature: although we have seven points (seven distinct pairs of coordinates), they are gathered into two groups, green and orange, which can be seen in the associated attribute table. The central example, on the other hand, uses single-element features, where each line geometry relates to one row in the attribute table. Finally, the bottom example again uses multi-element features, where the second feature (`Country B`) consist of two separate geometries. @@ -156,7 +167,7 @@ Finally, GeometryCollection exists that contains all of the possible geometry ty A couple hundreds of file formats exist to store spatial vector data. One of the simplest ways to store spatial data is in the form of a text file (`.csv`) or as a spreadsheet (`.xls` or `.xlsx`). While it makes storing point data simple, with two columns representing coordinates, it is not easy to store more complex objects in this way. -Text files are also not suitable for storing information about the coordinate reference system used (section \@ref(crs)). +Text files are also not suitable for storing information about the coordinate reference system used (@sec-crs). Historically, the shapefile format (`.shp`) developed by the ESRI company gained a lot of interest and become the most widely supported spatial vector file format. Despite its popularity, this format has a number of shortcomings, including the need to store several files, attribute names limited to ten characters, the ability to store up to 255 attributes and files up to 2GB, and many more. A fairly recent file format, OGC GeoPackage (`.gpkg`), was developed as an alternative. @@ -165,18 +176,29 @@ Other popular spatial vector file formats include GeoJSON (`.geojson`), GPX (`.g -### Raster data model +### Raster data model {#sec-raster-data-model} \index{raster data model} -The raster data model represents the world using a continuous grid of cells, where each cell has a single associated value (Figure \@ref(fig:raster-intro)). +The raster data model represents the world using a continuous grid of cells, where each cell has a single associated value (@fig-raster-intro). Depending on the type of values, we can distinguish continuous and categorical rasters. In continuous rasters, such as elevation or precipitation, values vary progressively. Categorical rasters, on the other hand, uses integer values to represent classes. Their examples include land cover or soil types maps. -Raster data can also contain cells for which we do not know the value (Figure \@ref(fig:raster-intro)). +Raster data can also contain cells for which we do not know the value (@fig-raster-intro). For example, data for this part of the area was not collected, or these locations are outside of our area of interest. -```{r raster-intro, echo=FALSE, warning=FALSE, fig.asp = .4, fig.cap="Basic representation of the raster data model: (1) Cell IDs, (2) Cell values, and (3) A raster map", message=FALSE} +```{r} +#| label: fig-raster-intro +#| echo: false +#| warning: false +#| message: false +#| #fig-asp: 0.4 +#| layout-ncol: 3 +#| fig-cap: 'Basic representations of the raster data model' +#| fig-subcap: +#| - Cell IDs +#| - Cell values +#| - A raster map #replace example later library(tmap) library(spData) @@ -194,42 +216,51 @@ sta_sf = st_as_sf(sta) sta_sf$A1.2 = as.character(as.factor(sta_sf$A1.1)) sta_sf$A1.2[is.na(sta_sf$A1.1)] = "NA" -tm_raster1 = tm_shape(sta_sf) + +tm_shape(sta_sf) + tm_polygons() + tm_text("A1") + - tm_layout(frame = FALSE, main.title = "1. Cell IDs") + tm_layout(frame = FALSE) -tm_raster2 = tm_shape(sta_sf) + +tm_shape(sta_sf) + tm_polygons("A1.1") + tm_text("A1.2") + - tm_layout(frame = FALSE, legend.show = FALSE, - main.title = "2. Cell values") + tm_layout(frame = FALSE, legend.show = FALSE) -tm_raster3 = tm_shape(sta) + +tm_shape(sta) + tm_raster("A1.1") + - tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = 0.02, - main.title = "3. Raster map") - -tmap_arrange(tm_raster1, tm_raster2, tm_raster3, nrow = 1) + tm_layout(frame = FALSE, legend.show = FALSE, inner.margins = 0.02) ``` \index{raster data grid types} -When we think about raster data, most of the time we are referring to regular grids (Figure \@ref(fig:grid-types)). +When we think about raster data, most of the time we are referring to regular grids (@fig-grid-types). In regular grids, each cell has the same, constant size, and coordinates change from top to bottom and from left to right^[Regular grids can also have coordinated changing in different directions, e.g., from bottom to top.]. -Regular rasters can be transformed into rotated and sheared rasters (Figure \@ref(fig:grid-types)). +Regular rasters can be transformed into rotated and sheared rasters (@fig-grid-types). Rotated grids are the result of transforming both coordinated, $x$ and $y$ using the same rotation coefficients. Sheared grids are created when the rotation coefficients are not equal. -Rectilinear grids, on the other hand, have orthogonal axes, but consist of rectangular cells with different sizes and shapes (Figure \@ref(fig:grid-types)). -In the last type of raster data grids, curvilinear grids, cells are cuboids of different sizes and shapes (Figure \@ref(fig:grid-types)). +Rectilinear grids, on the other hand, have orthogonal axes, but consist of rectangular cells with different sizes and shapes (@fig-grid-types). +In the last type of raster data grids, curvilinear grids, cells are cuboids of different sizes and shapes (@fig-grid-types). -```{r grid-types, echo=FALSE, warning=FALSE, fig.asp = .4, fig.cap="Main types of raster data grids: (1) Regular, (2) Rotated, (3) Sheared, (4) Rectilinear, and (5) Curvilinear"} +```{r} +#| label: fig-grid-types +#| echo: false +#| warning: false +#| #fig-asp: 0.4 +#| layout-ncol: 5 +#| fig-cap: 'Main types of raster data grids' +#| fig-subcap: +#| - Regular +#| - Rotated +#| - Sheared +#| - Rectilinear +#| - Curvilinear # regular grid tm_sta_regular = tm_shape(sta_sf) + tm_polygons() + tm_text("A1") + - tm_layout(frame = FALSE, main.title = "Regular") + # tm_title("Regular") + + tm_layout(frame = FALSE) # rotated grids sta_rotated = sta attr(attr(sta_rotated, "dimensions"), "raster")$affine = c(0.1, 0.1) @@ -237,7 +268,8 @@ sta_rotated_sf = st_as_sf(sta_rotated) tm_sta_rotated = tm_shape(sta_rotated_sf) + tm_polygons() + tm_text("A1") + - tm_layout(frame = FALSE, main.title = "Rotated") + # tm_title("Rotated") + + tm_layout(frame = FALSE) # sheared grids sta_sheared = sta attr(attr(sta_sheared, "dimensions"), "raster")$affine = c(0.1, 0.2) @@ -245,7 +277,8 @@ sta_sheared_sf = st_as_sf(sta_sheared) tm_sta_sheared = tm_shape(sta_sheared_sf) + tm_polygons() + tm_text("A1") + - tm_layout(frame = FALSE, main.title = "Sheared") + # tm_title("Sheared") + + tm_layout(frame = FALSE) # rectilinear grids x = c(0, 0.5, 1.5, 2.1, 3, 5) y = rev(c(0.1, 1, 1.5, 2, 4)) @@ -255,7 +288,8 @@ sta_rectilinear_sf = st_as_sf(sta_rectilinear) tm_sta_rectilinear = tm_shape(sta_rectilinear_sf) + tm_polygons() + tm_text("m") + - tm_layout(frame = FALSE, main.title = "Rectilinear") + # tm_title("Rectilinear") + + tm_layout(frame = FALSE) # curvilinear grids sta_curvilinear = sta X1 = matrix(rep(1:5, times = 4), nrow = 5, ncol = 4) @@ -276,11 +310,14 @@ tm_sta_curvilinear = tm_shape(sta_curvilinear_sf) + tm_polygons() + tm_text("A1") + # tm_grid() + - tm_layout(frame = FALSE, main.title = "Curvilinear") + # tm_title("Curvilinear") + + tm_layout(frame = FALSE) # all -tm_sta_all = tmap_arrange(tm_sta_regular, tm_sta_rotated, tm_sta_sheared, - tm_sta_rectilinear, tm_sta_curvilinear, nrow = 1) -tm_sta_all +tm_sta_regular +tm_sta_rotated +tm_sta_sheared +tm_sta_rectilinear +tm_sta_curvilinear ``` Contrary to spatial vector data, a basic raster data stores just one attribute. @@ -288,27 +325,43 @@ It is, however, possible to stack together many single rasters (also known as ra This allows us to store and operate on many rasters having the same dimensions at the same time. Examples of multi-layer rasters include satellite imageries or temporal rasters. Satellite imageries usually consist of many bands (layers) for different wavelengths. -The most basic bands, representing the colors red, green, and blue, can be connected together to create one composite image with true colors (Figure \@ref(fig:rgb-raster)). +The most basic bands, representing the colors red, green, and blue, can be connected together to create one composite image with true colors (@fig-rgb-raster). Temporal rasters store one attribute, but for many moments in time. -Additional information about multi-layer rasters can be also found in Section \@ref(spatial-data-cubes). +Additional information about multi-layer rasters can be also found in @sec-spatial-data-cubes. -```{r rgb-raster, echo=FALSE, message=FALSE, warning=FALSE, fig.asp = .4, fig.cap="Example of three satellite imagery bands: red, green, blue, and the composite image with true colors created using these three bands."} +```{r} +#| label: fig-rgb-raster +#| echo: false +#| message: false +#| warning: false +#| #fig-asp: 0.4 +#| layout-ncol: 4 +#| fig-cap: 'Example of three satellite imagery bands: red, green, blue, and the composite +#| image with true colors created using these three bands.' +#| fig-subcap: +#| - Red +#| - Green +#| - Blue +#| - Composite # we can/should change this example later -landsat234 = raster::stack(system.file("raster/landsat.tif", package = "spDataLarge"))[[1:3]] -tmrgb1 = tm_shape(raster::brick(landsat234[[3]])) + - tm_raster(palette = "Greys") + - tm_layout(frame = FALSE, legend.show = FALSE, main.title = "Red") -tmrgb2 = tm_shape(raster::brick(landsat234[[2]])) + - tm_raster(palette = "Greys") + - tm_layout(frame = FALSE, legend.show = FALSE, main.title = "Green") -tmrgb3 = tm_shape(raster::brick(landsat234[[1]])) + - tm_raster(palette = "Greys") + - tm_layout(frame = FALSE, legend.show = FALSE, main.title = "Blue") -tmrgb4 = tm_shape(landsat234) + - tm_rgb(r = 3, g = 2, b = 1, max.value = 25780) + - tm_layout(frame = FALSE, main.title = "Composite") -tmap_arrange(tmrgb1, tmrgb2, tmrgb3, tmrgb4, nrow = 1) +landsat234 = terra::rast(system.file("raster/landsat.tif", package = "spDataLarge"))[[1:3]] +tm_shape(landsat234[[3]]) + + tm_raster(col.scale = tm_scale(values = "Greys")) + + # tm_title("Red") + + tm_layout(frame = FALSE, legend.show = FALSE) +tm_shape(landsat234[[2]]) + + tm_raster(col.scale = tm_scale(values = "Greys")) + + # tm_title("Green") + + tm_layout(frame = FALSE, legend.show = FALSE) +tm_shape(landsat234[[1]]) + + tm_raster(col.scale = tm_scale(values = "Greys")) + + # tm_title("Blue") + + tm_layout(frame = FALSE, legend.show = FALSE) +tm_shape(landsat234) + + tm_rgb(tm_vars(names(landsat234)[3:1], multivariate = TRUE)) + + # tm_title("Composite") + + tm_layout(frame = FALSE) ``` \index{spatial file formats} @@ -319,7 +372,7 @@ It is an extended image TIF format that stores spatial metadata (e.g., map proje Another popular spatial raster formats include Arc ASCII (`.asc`) and ERDAS Imagine (`.img`). -### Spatial data cubes +### Spatial data cubes {#sec-spatial-data-cubes} \index{spatial data cubes} Traditionally, spatial vector and raster data models refer to a unique set of locations. @@ -330,33 +383,45 @@ It includes situations when we have many attributes, often for several moments i \index{spatial vector data cubes} Storing multiple attributes is not a problem for the vector data model, when an attribute table can have many columns. The question is how to extend the spatial vector data model to include measurements for many times. -For example, let's consider a polygon data with many attributes representing shares of land-use types for several years (Figure \@ref(fig:vector-data-cubes)). +For example, let's consider a polygon data with many attributes representing shares of land-use types for several years (@fig-vector-data-cubes). One approach would be to create a separate column for each variable in each year. Alternatively, we can have one column representing the year and one column for each attribute, however, this approach would require multiplying each geometry as many times as we have time stamps. The third approach involves separating geometries from attributes, and where attributes for each moment are stored independently. -The last idea is used in spatial vector data cubes (section \@ref(the-stars-package)). -An example of the spatial vector data cubes idea can be seen in Figure \@ref(fig:vector-data-cubes). +The last idea is used in spatial vector data cubes (@sec-the-stars-package). +An example of the spatial vector data cubes idea can be seen in @fig-vector-data-cubes. It consists of two elements: a geometry (MULTIPOLYGON) of provinces of the Netherlands and an array connected to it that stores shares of land-use types for several years. -```{r vector-data-cubes, fig.width = 6, fig.asp=0.60606, message = FALSE, warning = FALSE, echo=FALSE, fig.cap="Vector data cube."} +```{r} +#| label: fig-vector-data-cubes +#| message: false +#| warning: false +#| echo: false +#| fig-width: 6 +#| fig-asp: 0.60606 +#| fig-cap: Vector data cube. source("code/data_model_figures.R") draw_vector_cubes() ``` \index{spatial raster data cubes} A single raster dataset can store just one variable for a given area. -To store several attributes, we can connect rasters representing different attributes for the same extent, creating multi-layer rasters (section \@ref(raster-data-model)). +To store several attributes, we can connect rasters representing different attributes for the same extent, creating multi-layer rasters (@sec-raster-data-model). Additionally, each of the aforementioned rasters can be collected for many moments in time, adding other layers to the data. The question here is how to efficiently store multi-layer raster data to understand what layers relate to which attribute and time. -Similarly to spatial vector data cubes, we can think of separating spatial dimensions from non-spatial attributes and create spatial raster data cubes (section \@ref(the-stars-package)). -Figure \@ref(fig:raster-data-cubes) gives an example of a raster data cube. +Similarly to spatial vector data cubes, we can think of separating spatial dimensions from non-spatial attributes and create spatial raster data cubes (@sec-the-stars-package). +@fig-raster-data-cubes gives an example of a raster data cube. It consists of several single-layer rasters with the same spatial properties, such as resolution, extent, and CRS. These rasters are organized to store four-dimensions of the data: latitude, longitude, time, and attributes. It has values of three attributes for five moments in time in total. -```{r raster-data-cubes, fig.asp=0.25, message = FALSE, echo=FALSE, fig.cap="Raster data cube."} +```{r} +#| label: fig-raster-data-cubes +#| message: false +#| echo: false +#| fig-asp: 0.25 +#| fig-cap: Raster data cube. source("code/data_model_figures.R") draw_data_cubes() ``` @@ -373,7 +438,7 @@ It includes formats such as NetCDF (`.nc`) and HDF (`.hdf`). -## Spatial data representations in R +## Spatial data representations in R {#sec-spatial-data-representations-in-r} \index{vector data model} R has several packages aimed to represent spatial vector data. @@ -388,13 +453,13 @@ Several R packages can be used to represent spatial raster data, including **ras The **raster** package was used as a backbone of raster data visualization until **tmap** version 3.0. Nowadays, the **stars** package is used by **tmap** to operate on raster data and spatial data cubes. -In the two next sections, we introduce the **sf** package (\@ref(the-sf-package)) and the **stars** package (section \@ref(the-stars-package)). +In the two next sections, we introduce the **sf** package (@sec-the-sf-package) and the **stars** package (@sec-the-stars-package). -### The sf package +### The sf package {#sec-the-sf-package} \index{sf} \index{sf (package)|see {sf}} @@ -406,7 +471,7 @@ It also has one additional column, most often named `geom` or `geometry`^[Howeve This column contains geometries in a form of well-known text (WKT), storing all of the coordinates. -The **sf** package can read all of the spatial data formats mentioned in section \@ref(vector-data-model) using the `read_sf()` function^[It is also possible to read spatial vector data using the `st_read()` function, which differs from `read_sf()` by having different default arguments.]. +The **sf** package can read all of the spatial data formats mentioned in @sec-vector-data-model using the `read_sf()` function^[It is also possible to read spatial vector data using the `st_read()` function, which differs from `read_sf()` by having different default arguments.]. ```{r} library(sf) @@ -428,7 +493,7 @@ Each polygon's vertices are represented by a pair of values (`dimension: XY`). Bounding box allows to quickly understand the spatial extension of the input data. Finally, it has `r ifelse(sf::st_crs(worldvector)$IsGeographic, "geographic", "projected")` CRS named `r sf::st_crs(worldvector)$Name`. -You can learn more about Coordinate Reference Systems in section \@ref(crs). +You can learn more about Coordinate Reference Systems in @sec-crs. Spatial vector data of class `sf` can be also obtained using some of other R data packages. @@ -441,12 +506,12 @@ The **tmap** package accepts spatial vector data objects from both **sf** and ** In case of having vector objects in a different representation, they should be converted into `sf` objects first, before making maps. The **sf** package has the `st_as_sf()` function that translates objects of many classes, including `Spatial` (from the **sp** package), `ppp`, `psp`, and `lpp` (from the **spatstat** package), to the objects of class `sf`. The `st_as_sf()` function also allows to turn data frames into `sf` objects - the user needs to provide the input data frame, names of columns with coordinates, and additionally definition of the CRS of the data. -For example `my_sf = st_as_sf(my_df, coords = c("Xcolumn", "Ycolumn"), crs = 4326)`. +For example `my_sf = st_as_sf(my_df, coords = c("Xcolumn", "Ycolumn"), crs = "EPSG:4326")`. -If you want to learn more about operating on `sf` objects, we recommend visiting the package website and vignettes at https://r-spatial.github.io/sf/index.html and reading [the Geocomputation with R book](https://geocompr.github.io/) [@lovelace2019geocomputation]. +If you want to learn more about operating on `sf` objects, we recommend visiting the package website and vignettes at and reading [the Geocomputation with R book](https://geocompr.github.io/) [@lovelace_geocomputation_2025]. -### The stars package +### The stars package {#sec-the-stars-package} \index{stars} \index{stars (package)|see {stars}} @@ -520,18 +585,24 @@ The **stars** package also has support for vector data cubes, where each geometr More information on how the `stars` objects are organized and how to operate on them can be found in the **stars** package vignettes at https://r-spatial.github.io/stars. -## Map projections (CRS) {#crs} +## Map projections (CRS) {#sec-crs} -### What are map projections? +### What are map projections? {#sec-crs-intro} -```{r crs-01, eval=TRUE, echo=FALSE, results='hide', warning=FALSE, message=FALSE} +```{r} +#| label: crs-01 +#| eval: true +#| echo: false +#| results: hide +#| warning: false +#| message: false library(sf) library(tmap) library(grid) library(dplyr) -library(kableExtra) +#library(kableExtra) data(World) clr_peel = "#FD8A04" clr_orange_land = "#FB6801" @@ -544,7 +615,6 @@ clr_land = "grey85" clr_grat = "grey50" clr_border = "grey30" - #source("code/crs_examples.R", local = knitr::knit_global()) # something strange going on: when saving this R script, the content isn't updated in the envir, or there is a cashing problem. Therefore, I had to add source(... ) to every chunk below. ``` @@ -557,12 +627,22 @@ The world is shown as an orange below, not just to stimulate your appetite for t A world map can be seen as an orange peel that is put flat on the table. The question is how to do this. -```{r crs-02, echo=FALSE, results='hide', warning=FALSE, eval=FALSE} +```{r} +#| label: crs-02 +#| echo: false +#| results: hide +#| warning: false +#| eval: false source("code/crs_examples.R") map_orange_world() ``` -```{r orange, echo=FALSE, message=FALSE, fig.cap="How to peel an orange?", fig.scap="How to peel an orange?"} +```{r} +#| label: fig-orange +#| echo: false +#| message: false +#| fig-cap: How to peel an orange? +#| fig-scap: How to peel an orange? knitr::include_graphics("images/orange_world.png") ``` @@ -572,13 +652,19 @@ The (interrupted) Goode homolosine projection, which is shown below, embodies th All continents and countries are preserved, except Antarctica and Greenland. There is also a version of the Goode homolosine projection that focuses on preserving the oceans. -```{r crs-goode, echo=FALSE, results='hide', fig.asp=0.45, warning=FALSE, fig.cap="The (interrupted) Goode homolosine projection"} +```{r} +#| label: fig-crs-goode +#| echo: false +#| results: hide +#| warning: false +#| fig-asp: 0.45 +#| fig-cap: The (interrupted) Goode homolosine projection source("code/crs_examples.R") goode = map_goode() tm_shape(goode$bg) + tm_polygons(clr_peel) + tm_shape(goode$land) + - tm_polygons(clr_orange_land, border.col = clr_border, lwd = 1) + + tm_polygons(clr_orange_land, col = clr_border, lwd = 1) + tm_shape(goode$grat) + tm_lines(col = clr_grat, lwd = 1) + tm_layout(frame = FALSE) @@ -589,10 +675,10 @@ These properties are needed in order to make a non-interrupted map, as we will s \index{map projections} \index{coordinate reference system (CRS)} -A method to flatten down the earth, for which the Goode homolosine projection shown Figure \@ref(fig:crs-goode) is an example, is called a *map projection*. +A method to flatten down the earth, for which the Goode homolosine projection shown in @fig-crs-goode is an example, is called a *map projection*. Technically, it is also known as a *coordinate reference system* (*CRS*), which specifies the corresponding coordinate system, as well as the transformations to other map projections. -### A model of the Earth +### A model of the Earth {#sec-crs-earth} \index{ellipsoid} @@ -622,7 +708,7 @@ The longitude specifies the east-west position in degrees, where by convention, The Longitude range is -180$^\circ$ to 180$^\circ$, and since this is a full circle, -180$^\circ$ and $^\circ$ specify the same longitude. \index{graticule} -When we see the earth in its three-dimensional form, as in Figure \@ref(fig:orange), the latitude parallels are the horizontal lines around the earth, and the longitude meridians are the vertical lines around the earth. +When we see the earth in its three-dimensional form, as in @fig-orange, the latitude parallels are the horizontal lines around the earth, and the longitude meridians are the vertical lines around the earth. The set of longitude meridians and latitude parallels is also referred to as *graticule*. In all the figures in this section, latitude parallels are shown as gray lines for $-60^\circ$, $-30^\circ$, $0^\circ$, $30^\circ$ and $60^\circ$, and longitude meridians from $-180^\circ$ to $180^\circ$ at every $30^\circ$. @@ -631,7 +717,7 @@ A datum is required. When people exchange latitude-longitude data, it is safe to assume that they implicitly have used the WGS84 datum. However, it is good practice to specify the datum explicitly. -### Platte Carrée and Web Mercator +### Platte Carrée and Web Mercator {#sec-crs-projections} \index{web mercator} \index{EPSG} @@ -641,16 +727,24 @@ EPSG is an institute that maintains a database of standard map projections. -```{r crs-04, echo=FALSE, results='hide', warning=FALSE, fig.cap="Latitude longitude coordinates (EPSG 4326)", message=FALSE, fig.cap="The WGS84 coordinate system (EPSG4326)", eval=FALSE} +```{r} +#| label: fig-crs-04 +#| echo: false +#| results: hide +#| warning: false +#| message: false +#| fig-cap: "Latitude longitude coordinates (EPSG 4326)" +sf::sf_use_s2(FALSE) source("code/crs_examples.R") - m4326 = map_4326() m4326_cyl = map_4326_cyl() +sf::sf_use_s2(TRUE) + tmap_arrange({ tm_shape(m4326_cyl$bg_back) + tm_polygons(clr_bg2) + tm_shape(m4326_cyl$bg_front) + - tm_polygons(clr_bg, border.col = clr_border) + + tm_polygons(clr_bg, col = clr_border) + tm_shape(m4326_cyl$land) + tm_polygons(clr_land) + tm_shape(m4326_cyl$grat) + @@ -662,46 +756,54 @@ tmap_arrange({ tm_polygons(clr_land) + #tm_shape(m4326$grat) + #tm_lines(col = clr_grat, lwd = 1) + - tm_graticules(x = seq(-180, 180, by = 30), y = seq(-60, 60, by = 30), col = clr_grat, labels.size = .6, labels.cardinal = FALSE, lines = TRUE) + + tm_graticules(x = seq(-180, 180, by = 30), y = seq(-60, 60, by = 30), + col = clr_grat, labels.size = .6, labels.cardinal = FALSE, lines = TRUE) + tm_layout(frame = TRUE, inner.margins = 0) }, widths = c(.3, .7), asp = NULL) ``` -When we fictitiously make little holes in the orange peel at both poles, and stretch these open so wide that they have the same width as the equator, we obtain the cylinder depicted in Figure \@ref(fig:crs-04) (left). +When we fictitiously make little holes in the orange peel at both poles, and stretch these open so wide that they have the same width as the equator, we obtain the cylinder depicted in @fig-crs-04 (left). Note that the longitude lines have become straight vertical lines. When we unroll this cylinder, we obtain a map where the $x$ and $y$ coordinates are the longitude and latitude respectively. -This CRS, which is known as EPSG4326, is shown in Figure \@ref(fig:crs-04) (right). +This CRS, which is known as EPSG4326, is shown in Figure @fig-crs-04 (right). \index{coordinate reference system (CRS)} \index{unprojected coordinate reference system (CRS)} \index{projected coordinate reference system (CRS)} EPSG4326 is an *unprojected* CRS, since the longitude and latitude have not been transformed. With *projected* CRSs, the $x$ and $y$ coordinates refer to specific measurement units, usually meters. -The projected variant of this CRS is called the *Platte Carrée* (EPSG4087), and is exactly the same map as shown in Figure \@ref(fig:crs-04) (right), but with other $x$ and $y$ value ranges. +The projected variant of this CRS is called the *Platte Carrée* (EPSG4087), and is exactly the same map as shown in Figure @fig-crs-04 (right), but with other $x$ and $y$ value ranges. Observe since we stretched the poles open, the area near the poles have been stretched out as well. More specifically, the closer the land is to one of the poles, the more it has been stretched out. Since the stretching direction is only horizontally, the shapes of the areas have become wider. -A good example is Greenland, which is normally a 'tall' area (as can be seen in Figure \@ref(fig:orange)). +A good example is Greenland, which is normally a 'tall' area (as can be seen in @fig-orange). \index{web mercator} In order to fix these deformed areas, Gerardus Mercator, a Flemish geographer in the 16th century introduced a method to compensate for this by inflating the areas near the poles even more, but now only in a vertical direction. This projection is called the Mercator projection. For web applications, this projection has been slightly modified and renamed to the Web Mercator projection (EPSG3857). -The cylinder and plain map that uses this projection are shown in Figure \@ref(fig:crs-05). +The cylinder and plain map that uses this projection are shown in @fig-crs-05. -```{r crs-05, echo=FALSE, results='hide', warning=FALSE, fig.cap="Web Mercator projection (EPSG3857)", message=FALSE, eval=FALSE} +```{r} +#| label: fig-crs-05 +#| echo: false +#| results: hide +#| warning: false +#| message: false +#| fig-cap: "Web Mercator projection (EPSG 3857)" +sf::sf_use_s2(FALSE) source("code/crs_examples.R") - m3857 = map_3857() m3857_cyl = map_3857_cyl() +sf::sf_use_s2(TRUE) tmap_arrange({ tm_shape(m3857_cyl$bg_back) + tm_polygons(clr_bg2) + tm_shape(m3857_cyl$bg_front) + - tm_polygons(clr_bg, border.col = clr_border) + + tm_polygons(clr_bg, col = clr_border) + tm_shape(m3857_cyl$land) + tm_polygons(clr_land) + tm_shape(m3857_cyl$grat) + @@ -713,19 +815,20 @@ tmap_arrange({ tm_polygons(clr_land) + #tm_shape(m3857$grat) + #tm_lines(col = clr_grat, lwd = 1) + - tm_graticules(x = seq(-180, 180, by = 30), y = seq(-60, 60, by = 30), col = clr_grat, labels.size = .6, labels.cardinal = FALSE, lines = TRUE) + + tm_graticules(x = seq(-180, 180, by = 30), y = seq(-60, 60, by = 30), + col = clr_grat, labels.size = .6, labels.cardinal = FALSE, lines = TRUE) + tm_layout(frame = TRUE, inner.margins = 0) }, widths = c(.3, .7), asp = NULL) ``` -Although the areas near the poles have been inflated quite a lot, especially Antarctica and Greenland, the shape of the areas is more or less correct, in particular regarding small areas (which can be seen by comparing with Figure \@ref(fig:orange)). +Although the areas near the poles have been inflated quite a lot, especially Antarctica and Greenland, the shape of the areas is more or less correct, in particular regarding small areas (which can be seen by comparing with @fig-orange. The Mercator projection is very useful for navigational purposes, and has therefore been embraced by sailors ever since. Also today, the Web Mercator is the de facto standard for interactive maps and navigation services. However, for maps that show data the (Web) Mercator projection should be used with great caution, because the hugely inflated areas will influence how we perceive spatial data. We will discuss this in the next section. -### Types of map projections +### Types of map projections {#sec-proj-types} -### CRS in R +### CRS in R {#sec-crs-in-r} \index{coordinate reference system (CRS)} \index{PROJ} @@ -949,10 +1112,11 @@ The **sf** package integrates the **PROJ** functions into R. A CRS is represented in R by an object of class `crs`, which can be retrieved or set with the function `st_crs` (from the **sf** package). In the following example, a `crs` object is created from an EPSG code, in this case 3035, the Lambert Azimuthal Equal-Area projection for Europe. -```{r crs-new} +```{r} +#| label: crs-new library(sf) # CRS Lambert Azimuthal Equal-Area projection -st_crs(3035) +st_crs("EPSG:3035") ``` \index{crs objects} @@ -979,7 +1143,8 @@ In the example below, we created a new object, `waterfalls`, with names and coor Next, we converted it into a spatial object of the `sf` class, `waterfalls_sf()` with `st_as_sf()`. We can see that our object's coordinate reference system is not defined with the `st_crs()` function. -```{r crs-use} +```{r} +#| label: crs-use # create a data.frame of three famous waterfalls waterfalls = data.frame(name = c("Iguazu Falls", "Niagara Falls", "Victoria Falls"), lat = c(-25.686785, 43.092461, -17.931805), @@ -995,7 +1160,7 @@ We can also confirmed that our operation was successful also using `st_crs()`. ```{r} # specify crs -st_crs(waterfalls_sf) = 4326 +st_crs(waterfalls_sf) = "EPSG:4326" # extract crs st_crs(waterfalls_sf) ``` @@ -1003,32 +1168,39 @@ st_crs(waterfalls_sf) Alternatively, it is possible to set the CRS when creating a new `sf` object, as you can see below. ```{r} -waterfalls_sf = st_as_sf(waterfalls, coords = c("lon", "lat"), crs = 4326) +waterfalls_sf = st_as_sf(waterfalls, coords = c("lon", "lat"), crs = "EPSG:4326") ``` The `st_transform()` function is used to convert the existing spatial object's coordinates into another projection. For example, let's transform our `waterfalls_sf` object to the Equal Earth projection (EPSG 8857). -```{r crs-trans, warning=FALSE, warning=FALSE} -waterfalls_sf_trans = st_transform(waterfalls_sf, 8857) +```{r} +#| label: crs-trans +#| warning: false +waterfalls_sf_trans = st_transform(waterfalls_sf, "EPSG:8857") waterfalls_sf_trans ``` -Figure \@ref(fig:crs-trans-plot) shows the data in the WGS84 coordinate system on the top and in the Equal Earth projection on the bottom. +@fig-crs-trans-plot shows the data in the WGS84 coordinate system on the top and in the Equal Earth projection on the bottom. You can see here that the decision of the projection used has an impact not only on the coordinates (notice the grid values), but also the continents' shapes. -```{r crs-trans-plot, echo=FALSE, warning=FALSE, fig.cap="Comparison between the same dataset of three waterfalls using: (A) the WGS84 coordinate system, (B) the Equal Earth projection."} -ctp1 = tm_shape(World, projection = 4326) + +```{r} +#| label: fig-crs-trans-plot +#| echo: false +#| warning: false +#| layout-nrow: 2 +#| fig-cap: 'Comparison between the same dataset of three waterfalls' +#| fig-subcap: +#| - 'WGS84 coordinate system' +#| - 'Equal Earth projection' +tm_shape(World, crs = "EPSG:4326") + tm_polygons() + tm_shape(waterfalls_sf) + tm_markers(text = "name") + - tm_grid() + - tm_layout(title = "A") -ctp2 = tm_shape(World, projection = 8857) + + tm_grid() +tm_shape(World, crs = "EPSG:8857") + tm_polygons() + tm_shape(waterfalls_sf_trans) + tm_markers(text = "name") + - tm_grid() + - tm_layout(title = "B") -tmap_arrange(ctp1, ctp2, ncol = 1) + tm_grid() ``` diff --git a/XX-good-maps.qmd b/XX-good-maps.qmd new file mode 100644 index 0000000..e542006 --- /dev/null +++ b/XX-good-maps.qmd @@ -0,0 +1,7 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# How to make good maps? {#sec-good-maps} + diff --git a/XX-interactive.qmd b/XX-interactive.qmd new file mode 100644 index 0000000..643a726 --- /dev/null +++ b/XX-interactive.qmd @@ -0,0 +1,6 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Interactive settings {#sec-interactive} diff --git a/01-intro.Rmd b/XX-intro.qmd similarity index 76% rename from 01-intro.Rmd rename to XX-intro.qmd index 2780e75..72f2661 100644 --- a/01-intro.Rmd +++ b/XX-intro.qmd @@ -1,15 +1,22 @@ -```{asis index-22, echo=knitr::is_latex_output()} +```{r} +#| echo: false +source("code/before_script.R") +``` + +```{asis} +#| label: index-22 +#| echo: !expr knitr::is_latex_output() \mainmatter ``` # Introduction {#intro} This book teaches how to make elegant and informative maps with the R-package **tmap**. -A couple of real-world applications are used to illustrate the whole process from exploring raw spatial data to presenting insightful results. +A couple of real-world applications are used to illustrate the whole process, from exploring raw spatial data to presenting insightful results. We can distinguish three aspects that are required to make good maps: -* *Software skills* Without knowing how to use software for making maps, it will obviously be challenging to make maps with the computer. +* *Software skills* Without knowing how to use software to make maps, making maps with a computer will obviously be challenging. Yes, back in the old days, people like Henry Drury Harness and Charles Joseph Minard used pen and paper to draw maps, but the computational speed, reproducibility, and interactivity of digital maps cannot be missed. * *Domain knowledge* It is essential to know the background of the data. Where does the data come from? @@ -20,17 +27,16 @@ Since data visualization is all about conveying information, it is obviously imp However, these maps will not necessarily be good maps, since visualization of spatial data is trickier than most people think. There are a few underlying principles in data visualization that, when violated, will result in maps that are prone to misinterpretation of the data. -The main focus on this book will be on software skills, since our aim is to create maps with **tmap**. +The main focus of this book will be on software skills, since our aim is to create maps with **tmap**. Along the way, we will cover the most important data visualization methodology. Since whole books have been written about it already, we will keep this brief and pragmatic. Obviously, it is not possible to cover the remaining aspect, domain knowledge. However, the example datasets do not require much specific domain knowledge. - ## What is **tmap**? -The short answer is that **tmap** is an R package for visualization spatial data. -The slightly longer answer is that **tmap** allows users to explore, analyze, and present spatial data in a intuitive way. +The short answer is that **tmap** is an R package for the visualization of spatial data. +The slightly longer answer is that **tmap** allows users to explore, analyze, and present spatial data in an intuitive way. In this book, you will find the long answer. ## Thematic maps diff --git a/XX-layers.qmd b/XX-layers.qmd new file mode 100644 index 0000000..7a869a4 --- /dev/null +++ b/XX-layers.qmd @@ -0,0 +1,577 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Layers {#sec-layers} + + + +```{r} +#| echo: false +layers_basic_df = tibble::tribble( + ~Function, ~Element, ~Geometry, + "tm_polygons()", "polygons (borders and fill)", "polygons", + "tm_symbols()", "symbols", "points, polygons, and lines", + "tm_lines()", "lines", "lines", + "tm_raster()", "raster", "raster", + "tm_text()", "text", "points, polygons, and lines", + "tm_basemap()", "tile" , "", + "tm_tiles()", "tile", "" +) +layers_extended_df = tibble::tribble( + ~Function, ~Element, ~Geometry, + "tm_borders()", "polygons (borders)", "polygons", + "tm_fill()", "polygons (fill)", "polygons", + "tm_bubbles()", "bubbles", "points, polygons, and lines", + "tm_dots()", "dots", "points, polygons, and lines", + "tm_markers()", "marker symbols", "points, polygons, and lines", + "tm_square()", "squares", "points, polygons, and lines", + "tm_iso()", "lines with text labels", "lines", + "tm_rgb()/tm_rgba()", "raster (RGB image)", "raster" +) +layers_df = rbind(layers_basic_df, + layers_extended_df) +``` + +@tbl-layers-table + +```{r} +#| label: tbl-layers-table +#| echo: false +#| warning: false +#| message: false +tinytable::tt(layers_df, caption = "Map layers.") |> + tinytable::group_tt(i = list("Basic functions" = 1, "Derived functions" = 8)) |> + tinytable::style_tt(i = c(1, 9), color = "white", background = "gray", bold = TRUE) |> + tinytable::style_tt(j = 1, monospace = TRUE) + +# library(magrittr) +# library(kableExtra) +# options(kableExtra.html.bsTable = TRUE) +# knitr::kable(layers_df, +# caption = "Map layers.", +# caption.short = "Map layers.", +# booktabs = TRUE) |> +# kableExtra::kable_styling("striped", +# latex_options = "striped", +# full_width = FALSE) |> +# kableExtra::column_spec(1, bold = TRUE, monospace = TRUE) |> +# kableExtra::pack_rows("Basic functions", 1, 7) |> +# kableExtra::pack_rows("Derived functions", 8, 15) +``` + + + + + +In this chapter, we focus on what map layers are available in **tmap** and how they differ. +@sec-visual-variables, on the other hand, is all about how to present information given in variables using colors, sizes, and shapes. + + + + +## Polygons {#sec-polygons} + + + +```{r} +#| warning: false +#| message: false +library(tmap) +library(sf) +ei_borders = read_sf("data/easter_island/ei_border.gpkg") +``` + +The main function of visualizing polygons is `tm_polygons()`. +By default, it plots areas of polygons in light gray (`gray85`) and polygons borders in slightly dark gray (`gray25`). + + +```{r} +#| eval: false +tm_shape(ei_borders) + + tm_polygons() +``` + +Both, colors of areas (polygons' fillings) and colors of borders can be modified using the `fill` and `col` arguments (@fig-tmpolygonsder-1). + +```{r} +#| label: tmpolygonsder1 +#| eval: false +tm_shape(ei_borders) + + tm_polygons(fill = "lightblue", col = "black", lwd = 0.5, lty = "dashed") +``` + +In fact, `tm_polygons()` is a combination of two separate functions: `tm_fill()` and `tm_borders()`. +The `tm_fill()` function fills polygons with a fixed color or a color palette representing a selected variable (@fig-tmpolygonsder-2). + +```{r} +#| label: tmpolygonsder2 +#| eval: false +tm_shape(ei_borders) + + tm_fill(fill = "lightblue") +``` + +The `tm_borders()` function draws the borders of the polygons only (@fig-tmpolygonsder-3). +It allows you to change the colors of borders, their widths, or the lines type. + +```{r} +#| label: tmpolygonsder3 +#| eval: false +tm_shape(ei_borders) + + tm_borders(col = "black", lwd = 0.5, lty = "dashed") +``` + +```{r} +#| label: fig-tmpolygonsder +#| warning: false +#| echo: false +#| layout-nrow: 1 +#| fig-cap: "Example of a map created using:" +#| fig-subcap: +#| - "tm_polygons()" +#| - "tm_fill()" +#| - "tm_borders()" +#| #fig-asp: 0.4 +<> +<> +<> +``` + +More information on colors and how they can be applied and modified is explained in detail in @sec-colors. + +## Symbols {#sec-symbols} + +```{r} +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanos = subset(ei_points, type == "volcano") +``` + +Symbols are a very flexible layer type. +They are usually used to represent point data but can also be used for lines and polygons. +In the latter cases, they are located in the centroid coordinates of each feature. +Their flexibility is also related to the ways symbols can be visualized -- it is possible to show values of a given variable by colors of symbols, their sizes, or shapes (more about that is explained in @sec-visual-variables). + +The `tm_symbols()` is the main function in **tmap**, allowing to use and modify symbol elements (@fig-tmsymbols1). +By default, this function draws a gray circle symbol with a black border for each element of an input feature. + +```{r} +#| label: fig-tmsymbols1 +#| warning: false +#| echo: false +#| asp: 0.25 +#| fig-cap: A map showing the default tmap symbols. +tm_shape(volcanos) + + tm_symbols() +``` + +In the above example, each symbol is related to one feature (row) in the `volcanos` object. +However, in a case when we provide multi-element features (such as MULTIPOINT; @sec-vector-data-model), each multi-element object is first split into a number of single-element features and then plotted. + +The `tm_symbols()` is a very flexible function with a large number of arguments. +While this allows adjusting its results to almost any need, it also makes this function complicated. +Therefore, four additional layers are implemented in **tmap**: `tm_squares()`, `tm_bubbles()`, `tm_dots()`, `tm_markers()`. +All of them use `tm_symbols()`, but with different default values. + +`tm_squares()` uses square symbols (`shape = 22`) instead of circles (`shapes = 21`) (@fig-tmsymbols2-1). + + +```{r} +#| label: tmsymbols21 +#| eval: false +tm_shape(volcanos) + + tm_squares() +``` + + +(@fig-tmsymbols2-2) + + +```{r} +#| label: tmsymbols22 +#| eval: false +tm_shape(volcanos) + + tm_bubbles() +``` + +The main role of `tm_dots()` is to present many locations at the same time. +To do this, this layer has a small size value (`0.02`) at the default (@fig-tmsymbols2-3). + +```{r} +#| label: tmsymbols23 +#| eval: false +tm_shape(volcanos) + + tm_dots() +``` + +The last additional layer is `tm_markers()`, which uses a marker icon by default (@fig-tmsymbols2-4). + +```{r} +#| label: tmsymbols24 +#| eval: false +tm_shape(volcanos) + + tm_markers() +``` + +```{r} +#| label: fig-tmsymbols2 +#| warning: false +#| echo: false +#| layout-nrow: 2 +#| fig-cap: "Maps showing default visualizations using various types of symbols." +#| fig-subcap: +#| - "tm_squares()" +#| - "tm_bubbles()" +#| - "tm_dots()" +#| - "tm_markers()" +#| #fig-asp: 0.69 +<> +<> +<> +<> +``` + +## Lines {#sec-lines} + +```{r} +ei_roads = read_sf("data/easter_island/ei_roads.gpkg") +``` + +The `tm_lines()` function allows the visualization of different types of line data (@fig-tmlines). + +```{r} +#| label: fig-tmlines +#| fig-cap: "Example of a map created with tm_lines()." +tm_shape(ei_roads) + + tm_lines() +``` + +Lines can be presented using different colors, widths, or types (@sec-visual-variables). +This allows to show a hierarchy (for example, increased line widths for higher capacity roads) or distinguish between types of objects (for example, blue rivers comparing to gray roads). + +## Text {#sec-text} + + + +Text labels are often an integral part of many maps. +They can serve several functions, from naming features, indicating relations between them, or representing a given variable's values. +The main function for creating text labels is `tm_text()`, which adds a label to each spatial feature (@fig-tmtext). + +```{r} +#| label: fig-tmtext +#| fig-cap: "Example of a map created with tm_text()." +#| fig-asp: 0.35 +tm_shape(volcanos) + + tm_text(text = "name", size = "elevation") +``` + +We can adjust sizes (`size`; @sec-sizes), colors (`col`), font faces (`fontface`), and background colors (`bgcol`) of labels either by providing a single value or a name of a data variable. +Text labels can be further modified with the `opt_tm_text()` function provided to the `options` argument. +It includes a set of arguments that allow to adjustment of the text labels' appearance, such as `remove_overlap,` `along_lines,` and `shadow.` + + +```{r} +#| echo: false +tm_shape(ei_roads) + + tm_lines(lwd = "strokelwd") + + tm_text("name", size = "strokelwd", col = "red", + options = opt_tm_text(remove_overlap = TRUE, shadow = TRUE)) +``` + + +Text labels can be added to spatial (multi-)points, (multi-)lines, and (multi-)polygons, and each of the cases is quite different. +The simplest case is for POINT data, for which each text label will be located precisely in the coordinates of the given points (@fig-tmtext). +However, how to add text labels to multipoints, lines, multilines, polygons, or multipolygons? +Should each label correspond to one spatial feature, or should every sub-feature have its own label? +Where should the labels be placed for lines or polygons - in the center of a line and centroid of a polygon or somewhat different? + + + +```{r} +#| echo: false +#| eval: false +metro3 = metro2 |> + dplyr::mutate(g = rep(1:5, 6)) |> + dplyr::group_by(g) |> + dplyr::summarize() +tm_shape(metro3) + + tm_text(text = "g", size = "g") + + tm_layout(legend.outside = TRUE) +``` + + +```{r} +#| warning: false +# x2 = x |> +# dplyr::group_by(region_un) |> +# dplyr::summarise() +# tm_shape(x2) + +# tm_polygons() + +# tm_text("region_un") +``` + + + + + + + + +Text labels are also often presented together with lines (@sec-lines). +One example is an isopleth -- a line drawn on a map through all points having the same value of a given variable, such as atmospheric pressure or elevation. +Isopleths can be created with the `tm_iso()` function. + +```{r} +# to improve +library(stars) +library(terra) +ei_elev = read_stars("data/easter_island/ei_elev.tif") +ei_elev_raster = rast(ei_elev) +elev_isopleths = as.contour(ei_elev_raster) +tm_shape(elev_isopleths) + + tm_iso() +``` + +```{r} +ei_terrain = terra::terrain(ei_elev_raster, c("slope", "aspect"), unit = "radians") +hs = terra::shade(ei_terrain[[1]]*3, ei_terrain[[2]]) + +tm_shape(hs) + + tm_grid() + + tm_raster(col.scale = tm_scale(values = gray(0:100 / 100), n = 100), + col.legend = tm_legend_hide()) + + tm_shape(ei_elev) + + tm_raster(col_alpha = 0.25, + col.scale = tm_scale(values = terrain.colors(25)), + col.legend = tm_legend_hide()) + + tm_shape(elev_isopleths) + + tm_lines(col = "white") + + tm_text("level", col = "white") +``` + + + + + + + + + +## Raster {#sec-raster-layer} + +```{r} +#| message: false +library(stars) +ei_elev = read_stars("data/easter_island/ei_elev.tif") +ei_geomorphons = read_stars("data/easter_island/ei_geomorphons.tif") +``` + + +Visualization of raster data depends on the raster type (continuous or categorical), its resolution, and the number of layers. +@fig-rasterdown shows two simple examples of continuous and categorical raster visualizations created with `tm_raster().` +This function attempts to recognize the type of a given raster -- when the input raster is continuous then the pretty style is used (@fig-rastertype-1). + + +```{r} +#| label: rastertype1 +#| eval: false +tm_shape(ei_elev) + + tm_raster() +``` + +On the other hand, when the given raster is categorical, then `tm_raster()` uses `tm_scale_categorical()` automatically (@fig-rastertype-2). +We can also adjust the legend title, used colors, and many more, in a similar fashion as in the previously mentioned layers. + +```{r} +#| label: rastertype2 +#| eval: false +tm_shape(ei_geomorphons) + + tm_raster() +``` + +```{r} +#| label: fig-rastertype +#| echo: false +#| message: false +#| layout-nrow: 1 +#| fig-cap: Examples of raster maps +#| fig-subcap: +#| - "Continuous raster map" +#| - "Categorical raster map" +<> +<> +``` + +The above examples used a raster with one layer only. +However, rasters can have many layers, either represented by dimensions or attributes. +By default, **tmap** shows all of the layers, where each raster has its own legend. + +```{r} +#| results: hide +#| message: false +#| fig-show: hide +raster2 = c(ei_elev, ei_geomorphons) +tm_shape(raster2) + + tm_raster() +``` + +We can modify their arrangement with `tm_facets()` (@fig-tmrasterml). + +```{r} +#| label: fig-tmrasterml +#| message: false +#| fig-cap: A map created from a multilayered raster. +tm_shape(raster2) + + tm_raster() + + tm_facets(ncol = 1) + + tm_layout(panel.labels = c("Elevation", "Geomorphons")) +``` + +If you want to learn more -- we focus on how to specify and modify facets (also known as small multiples) in @sec-multiples and how to modify map layout in @sec-layout. + +```{r} +#to replace later +library(stars) +landsat = read_stars(system.file("raster/landsat.tif", package = "spDataLarge")) +``` + +The `landsat` object contains four bands (blue, green, red, and near-infrared) of the Landsat 8 image for the area of Zion National Park taken on the 18th of August 2015. +We can plot all of the bands independently or as a combination of three bands. +This combination is known as a color composite image, and we can create such images with the `tm_rgb()` function (@fig-tmrgbs). + +Standard composite image (true color composite) uses the visible red, green, and blue bands to represent the data in natural colors. +We can specify which band in `landsat` relates to red (third band), green (second band), and blue (first band) color in `tm_rgb()`. + + + + + +```{r} +#| label: tmrgbs1 +#| eval: false +tm_shape(landsat) + + tm_rgb(tm_vars(dimvalues = c(3, 2, 1), multivariate = TRUE), + col.scale = tm_scale_rgb(stretch = TRUE)) +``` + +True color images are straightforward to interpret and understand, but they make subtle differences in features challenging to recognize. +However, nothing stops us from using the above tools to integrate different bands to create so called false color composites. +Various band combinations emphasize some spatial characteristics, such as water, agriculture, etc., and allow us to visualize wavelengths that our eyes can not see. + +@fig-tmrgbs-2 shows a composite of near-infrared, red, and green bands, highlighting vegetation with a bright red color. + +```{r} +#| label: tmrgbs2 +#| eval: false +tm_shape(landsat) + + tm_rgb(tm_vars(dimvalues = c(4, 3, 2), multivariate = TRUE), + col.scale = tm_scale_rgb(stretch = TRUE)) +``` + +```{r} +#| label: fig-tmrgbs +#| message: false +#| echo: false +#| layout-nrow: 1 +#| fig-cap: 'Two color composite images' +#| fig-subcap: +#| - 'True color composite image' +#| - 'False color composite image' +<> +<> +``` + + + + +## Tile {#sec-tile-layer} + + + + +Tile layers can be used for two purposes: either as a basemap or an overlay layer. +By default, three basemaps are used in the interactive mode (`tmap_mode("view")`): +`"Esri.WorldGrayCanvas"`, `"OpenStreetMap"`, and `"Esri.WorldTopoMap"`. +However, we can change the basemaps with a vector with the names of the tile layers' providers (@fig-tmbasemap1). + +```{r} +#| eval: false +tmap_mode("view") +tm_basemap(c(StreetMap = "OpenStreetMap", TopoMap = "OpenTopoMap")) + + tm_shape(volcanos, is.main = TRUE) + + tm_dots(col = "red", group = "Volcanos") +``` + +```{r} +#| label: fig-tmbasemap1 +#| cache: false +#| eval: true +#| echo: false +#| message: false +#| fig-cap: OpenStreetMap tile layer used as a base map with the red dots representing +#| volcanos on Easter Island. +library(tmap) +tmap_mode("view") +tmbasemap1 = tm_basemap(c(StreetMap = "OpenStreetMap", TopoMap = "OpenTopoMap")) + + tm_shape(volcanos, is.main = TRUE) + + tm_dots(col = "red", group = "Volcanos") +view_map(tmbasemap1, "tmbasemap1") +``` + +In the above code, we made two basemaps available: `"OpenStreetMap"` and `"OpenTopoMap"`, and for the map legend purpose, we renamed them as `StreetMap` and `TopoMap`. +A complete list of available basemaps is in the `leaflet::providers` object and on the https://leaflet-extras.github.io/leaflet-providers/preview/ website^[Additional details can be found in the `leaflet::providers.details` object]. + + + + + +The `tm_basemap(NULL)` function allows to disable basemaps entirely. + +The `tm_tiles()` function, on the other hand, draws the tile layer on the top (as an overlay layer) of the previous `tm_` layer. +In the next example, we put the vector `"CartoDB.PositronOnlyLabels"` tiles on top of the previously set basemaps, but below the dots layer (@fig-tmtiles1). + +```{r} +#| eval: false +tm_basemap(c(StreetMap = "OpenStreetMap", TopoMap = "OpenTopoMap")) + + tm_tiles(c(CartoDB = "CartoDB.PositronOnlyLabels")) + + tm_shape(volcanos, is.main = TRUE) + + tm_dots(col = "red", group = "Volcanos") +``` + +```{r} +#| label: fig-tmtiles1 +#| cache: false +#| echo: false +#| message: false +#| fig-cap: OpenStreetMap tile layer used as a base map with dashed lines representing +#| island coastline and the red dots representing volcanos on Easter Island. +tmtiles1 = tm_basemap(c(StreetMap = "OpenStreetMap", TopoMap = "OpenTopoMap")) + + tm_tiles(c(CartoDB = "CartoDB.PositronOnlyLabels")) + + tm_shape(volcanos, is.main = TRUE) + + tm_dots(col = "red", group = "Volcanos") +view_map(tmtiles1, "tmtiles1") +``` + +Tile layers are usually created to be used interactively. +We can see it, for example, by the number of details varying depending on the zoom level we set. +That being said, many people find them useful also for static maps, and **tmap** allows us to use them in this way. +It uses the **maptiles** package to download the tiles and then plot them as a raster layer. + +A complete list of available providers and some [information about zoom levels](https://wiki.openstreetmap.org/wiki/Zoom_levels) are in the help file of the `?maptiles::get_tiles()` function. +Different map tiles providers offer unique map styles, while zoom levels relate to different levels of detail -- the larger level, the more details we will get. +When using map tiles, we should also consider adding their attribution to the map. +Attribution for each provider can be obtained using the `maptiles::get_credit()` function by specifying the provider name, for example, `get_credit("CartoDB.VoyagerNoLabels").` + +The code below plots the `"CartoDB.VoyagerNoLabels"` tiles in the background, adds the island outline in light blue color, and puts attribution text in the bottom right corner of the map (@fig-stiles)). + +```{r} +#| label: fig-stiles +#| cache: false +#| fig-cap: 'Example of a static map using a downloaded `"CartoDB.VoyagerNoLabels"` tile layer.' +tmap_mode("plot") +tm_basemap("CartoDB.VoyagerNoLabels") + + tm_shape(ei_borders) + + tm_borders(lwd = 5, col = "lightblue") + + tm_credits(maptiles::get_credit("CartoDB.VoyagerNoLabels"), + bg.color = "white") +``` diff --git a/XX-layout.qmd b/XX-layout.qmd new file mode 100644 index 0000000..d8debaa --- /dev/null +++ b/XX-layout.qmd @@ -0,0 +1,257 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Map layout {#sec-map-layout} + + + + +## Positions {#sec-positions} + +## Layout {#sec-layout} + +### Colors {#sec-lcolors} + + + +### Frame {#sec-lframe} + +### Margins {#sec-lmargins} + +### Typography {#sec-ltypography} + + +The decision about the used fonts is often neglected when creating programmable plots and maps. +Most often, the default fonts are used in these kinds of graphs. +This, however, could be a missed opportunity. +A lot of map information is expressed by text, including text labels (@sec-text), legend labels, text in attribute layers (@sec-attributes-layers), or the map title (@sec-layout-elements). +The used fonts impact the tone of the map [@guidero_typography_2017], and their customization allows for a map to stand out from maps using default options. + + + +As we mentioned above, many different map elements can be expressed or can use fonts. +In theory, we are able to set different fonts to each of them. +However, this could result in a confusing visual mix that would hinder our map information. +Therefore, the decision on the used fonts should be taken after considering the main map message, expected map audience, other related graph styles, etc. +In the following three sections, we explain font families and font faces, and give some overall tips on font selections, show how to define used fonts, and how to customize fonts on maps. + +#### Font families and faces {#sec-fonts} + +```{r} +#| echo: false +library(tmap) +data("World") +bf = c("plain", "italic", "bold", "bold.italic") +tm_bf = tm_shape(World) + + tm_borders(col_alpha = 0) + + tm_credits("plain", fontface = "plain", position = c("center", "center"), + size = 2) + + tm_credits("italic", fontface = "italic", position = c("center", "center"), + size = 2) + + tm_credits("bold", fontface = "bold", position = c("center", "center"), + size = 2) + + tm_credits("bold.italic", fontface = "bold.italic", position = c("center", "center"), + size = 2) + + tm_layout(frame = FALSE) +``` + +```{r} +#| echo: false +library(tmap) +data("World") +# ff = c("Times", "Helvetica", "Courier") +ff = c("serif", "sans", "monospace") +tm_ff = tm_shape(World) + + tm_borders(col_alpha = 0) + + tm_credits("serif", fontfamily = "serif", position = c("center", "center"), + size = 2) + + tm_credits("sans", fontfamily = "sans", position = c("center", "center"), + size = 2) + + tm_credits("monospace", fontfamily = "monospace", position = c("center", "center"), + size = 2) + + tm_layout(frame = FALSE) +``` + +```{r} +#| label: fig-fonts +#| message: false +#| echo: false +#| layout-ncol: 2 +#| fig-cap: Basic font families, and font faces implemented in the tmap package. +#| fig-subcap: +#| - Font families +#| - Font faces +tm_ff +tm_bf +``` + +In **tmap**, fonts are represented by a font family (@fig-fonts-1) and a font face (@fig-fonts-2). +A font family is a collection of closely related lettering designs. +Examples of font families include *Times*, *Helvetica*, *Courier*, *Palatino*, etc. +On the other hand, font faces, such as *italic* or *bold*, influence the orientation or width of the fonts. +A *font* is, thus, a combination of a selected font family and font face. + +There are a few general font families, such as serifs, sans serifs, and monospaced fonts. +Fonts from the serif family have small lines (known as *a serif*) attached to the end of some letters. +Notice, for example, short horizontal lines on the bottom of letters *r*, *i*, and *f* or vertical lines at the ends of the letter *s* in the top row of @fig-fonts-1. +The fonts in this family are often viewed as more formal. +On the other hand, the sans serif family do not have serifs and is considered more informal and modern. +The last font family, monospaced fonts, is often used in computer programming (IDEs, software text editors), but less often on maps. +A distinguishing feature of the monospaced fonts is that each letter or character in this family has the same width. +Therefore, letters, such as *i* and *a* will occupy the same space in the monospaced fonts. + + +Mixing the use of serif and sans serif fonts often works well for maps. +However, a rule of thumb is not to mix more than two font families on one map. +A sans serif font can be used to label cultural objects, while serif fonts to label physical features. +Then, italics, for example, can be used to distinguish water areas. +The role of bold font faces, together with increased font size, is to highlight the hierarchy of labels -- larger, bold fonts indicate more prominent map features. +Additionally, customizing the fonts' colors can be helpful to distinguish different groups of map objects. + + +The decision on which fonts to use should also relates to the expected map look and feel. +Each font family has a distinct personality (creates a "semantic effect"), which can affect how the map is perceived. + +Some fonts are more formal, some are less. +Some fonts have a modern look, while others look more traditional. + +Another important concern is personal taste or map branding. +We should filter the decision about the used fonts based on our preferences or even our sense of beauty as it could create more personal and unique maps. +We just need to remember about the readability of the fonts -- they should not be too elaborate as it can hinder the main map message. + + + +#### Fonts available in **tmap** {#sec-fonts-tmap} + +Before we discuss how to set a font family and its face, it is important to highlight that a different set of fonts could exist for each operating system (and even each computer). +Additionally, which fonts are available and how they are supported depends on the used *graphic device*. +A graphic device is a place where a plot or map is rendered. +The most commonly it is a some kind of a screen device, where we can see our plot or map directly after running the R code. +Other graphic devices allow for saving plots or maps as files in various formats (e.g., `.png`, `.jpg`, `.pdf`). +Therefore, it is possible to get different fonts on your map on the screen, and a (slightly) different one when saved to a file. +Visit `?Devices` or read the Graphic Devices chapter of @peng2016exploratory to learn more about graphic devices. + +The **tmap** package has two mechanism to select a font family. +The first one is by specifying on of three general font families: `"serif"`, `"sans"`, or `"monospace"`. +It tries to match selected general font family with a font family existing on the operating system. + +For example, `"serif"` could the `"Times"` font family, `"sans"` -- `"Helvetica"` or `"Arial"`, and `"monospace"` -- `"Courier"` (@fig-fonts-1). +The second mechanism allows to select a font family based on its name (e.g., `"Times"` or `"Palatino"`). +Next, a member of the selected font families can be selected with one of the font faces: `"plain"`, `"italic"`, `"bold"`, and `"bold.italic"` (@fig-fonts-2). + + +As mentioned before, available fonts depend on the computer setup (including operating system) and used graphic device. +Fonts available on the operating system can be checked with the `system_fonts()` function of the **systemfonts** package [@R-systemfonts] (result not shown). + +```{r} +#| eval: false +library(systemfonts) +system_fonts() +``` + +Information on installing and debugging custom fonts can be found in [a blog post](https://yjunechoe.github.io/posts/2021-06-24-setting-up-and-debugging-custom-fonts/) by June Choe and in the **showtext** package [@R-showtext] documentation. + +The next step is to either view or save the map. +This also means that we need to carry over our fonts to the output window/file, which largely depends on the selected graphic device. +In general, screen device or graphical raster output formats, such as PNG, JPEG, or TIFF, works well with custom fonts as they rasterize them during saving. +In case of any problems with graphical raster outputs, it is possible to try alternative graphics devices implemented in the **ragg** package [@R-ragg]. +On the other hand, graphical vector formats, such as PDF or SVG, could have some problems with saving maps containing custom fonts^[You can get the `invalid font type` error when saving the file.]. +The PDF device in R, by default, adds metadata about the used fonts, but does not store them. +When the PDF reader shows the document, it tries to locate the font on your computer, and use other fonts when the expected one does not exist. +An alternative approach is called embedding, which adds a copy of each necessary font to the PDF file itself. +Gladly, the creation of a PDF with proper fonts can be achieved in a few ways. +Firstly, it could be worth trying some alternative graphic devices such as `cairo_pdf` or `svglite::svglite`. +The second option is to use the **showtext** package [@R-showtext], which converts text into color-filled polygonal outlines for graphical vector formats. + +Thirdly, the **extrafont** [@R-extrafont] package allows embedding the fonts in the PDF file, which makes PDFs properly displayed on computers that do not have the given font. + + +#### Fonts on maps {#sec-fonts-on-maps} + +```{r} +#| message: false +library(tmap) +library(sf) +ei_borders = read_sf("data/easter_island/ei_border.gpkg") +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanos = subset(ei_points, type == "volcano") +``` + + +By default, **tmap** uses the `"sans"` font family with the `"plain"` font face (@fig-fonts). +There are, however, three ways to customize the used fonts. +The first one is to change all of the fonts and font faces for the whole map at once (@fig-mfonts-1). +This can be done with the `text.fontfamily` and `text.fontface` arguments of `tm_layout()`. + +```{r} +#| label: font1 +#| eval: false +tm_shape(ei_borders) + + tm_polygons() + + tm_shape(volcanos) + + tm_text(text = "name", size = "elevation") + + tm_credits("Data source: OSM") + + tm_title("Volcanos of Easter Island") + + tm_layout(text.fontface = "italic", + text.fontfamily = "serif") +``` + +The second way is to specify just some text elements independently (@fig-mfonts-2). +Many **tmap** functions, such as `tm_text()` or `tm_credits()`, have their own `fontfamily` and `fontface` arguments that can be adjusted. +Additionally, `tm_layout()` allows to customize fonts for other map elements using prefixed arguments, such as, `title.fontface` or `legend.title.fontfamily`. + +```{r} +#| label: font2 +#| eval: false +tm_shape(ei_borders) + + tm_polygons() + + tm_shape(volcanos) + + tm_text(text = "name", size = "elevation", fontfamily = "sans") + + tm_credits("Data source: OSM", fontface = "bold") + + tm_title("Volcanos of Easter Island") + + tm_layout(title.fontface = "bold.italic", + legend.title.fontfamily = "monospace") +``` + +```{r} +#| label: fig-mfonts +#| echo: false +#| layout-ncol: 2 +#| fig-cap: Examples of one font (font family and font face) used for all of the +#| map elements (title, text labels, legend, and text annotation), and different +#| fonts used for different map elements. +#| fig-subcap: +#| - One font for all elements +#| - Different fonts for different elements +tm_shape(ei_borders) + + tm_polygons() + + tm_shape(volcanos) + + tm_text(text = "name", size = "elevation") + + tm_credits("Data source: OSM") + + tm_title("Volcanos of Easter Island") + + tm_layout(text.fontface = "italic", + text.fontfamily = "serif") +tm_shape(ei_borders) + + tm_polygons() + + tm_shape(volcanos) + + tm_text(text = "name", size = "elevation", fontfamily = "sans") + + tm_credits("Data source: OSM", fontface = "bold") + + tm_title("Volcanos of Easter Island") + + tm_title_in("B") + + tm_layout(title.fontface = "bold.italic", + legend.title.fontfamily = "monospace") +``` + + + +```{r} +#| eval: false +#| echo: false +#saving tests +tmap_save(tmt2, "tmt2.png") +tmap_save(tmt2, "tmt2.pdf") +``` + + diff --git a/XX-legends.qmd b/XX-legends.qmd new file mode 100644 index 0000000..232cf48 --- /dev/null +++ b/XX-legends.qmd @@ -0,0 +1,59 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Legends {#sec-legends} + +```{r} +library(sf) +library(stars) +library(tmap) +ei_elev = read_stars("data/easter_island/ei_elev.tif") +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)")) +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)", + reverse = TRUE)) +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)", + position = tm_pos_in("right", "bottom"))) +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)", + position = tm_pos_in("right", "bottom"), + bg.color = "gray", bg.alpha = 0.5)) +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale_continuous(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)", + orientation = "landscape", + position = tm_pos_out("center", "top", + "center"))) +``` + +```{r} +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(show = FALSE)) +``` + + + diff --git a/XX-nutshell.qmd b/XX-nutshell.qmd new file mode 100644 index 0000000..8285868 --- /dev/null +++ b/XX-nutshell.qmd @@ -0,0 +1,260 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# **tmap** in a nutshell {#sec-nutshell} + +The **tmap** package allows the creation of thematic maps with great flexibility. +It accepts spatial data in various formats -- shape objects (@sec-shape-objects) +Next, the data can be used to create simple, quick maps (@sec-quick-maps) and more complex and expandable maps (@sec-regular-maps). +These maps can be presented in two modes: as a static map and an interactive one. +Additionally, **tmap** makes it possible to create small multiples maps (@sec-sm-section) and map animations (@sec-ani-section). +The goal of this chapter is to provide a brief overview of the main **tmap** features. + +## Shape objects {#sec-shape-objects} + +As we established in @sec-geodata, spatial data comes in various file formats related to two main data models -- vector and raster. +There are also several spatial object classes in R, for example, `sf` from the **sf** package for vector data and `stars` from **stars** for raster data and spatial data cubes. +Additionally, packages such as **sp**, **raster**, or **terra** offer their own classes, and this abundance of spatial object classes can be generally overwhelming. +Gladly, **tmap** can work with all of the above objects -- it treats all supported spatial data classes as so-called *shape objects*. + +For example, we read the `ei_points.gpkg` file containing several points on Easter Island into a new object, `ei_points`, and select only points with the `type` of `"volcano"`. +The `volcanoes` object is a *shape object*. + +```{r} +#| message: false +library(tmap) +library(sf) +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanoes = subset(ei_points, type == "volcano") +``` + + + +Spatial data, no matter the class, usually stores two interrelated sets of information - about the locations/geometries and their associated values/attributes. +Visualization of the attributes only can be done with regular plotting functions (e.g., `plot()`, `hist()`, `barplot()`) or dedicated packages, such as **ggplot2** . +On the other hand, **tmap** is suitable when our goal is to visualize spatial geometries only or spatial geometries together with their attributes. + +## Quick maps {#sec-quick-maps} + +The **tmap** package offers a distinction between quick and regular maps. +The first approach, using the `qtm()` function, could be handy for data exploration. +It works even if we just provide any *shape object* -- in that case, only the geometry is plotted. +@fig-qtm-1 shows a visualization of the geometries from the `volcanoes`. + +```{r} +#| label: qtm1 +#| eval: false +qtm(volcanoes) +``` + +The `qtm()` function allows to customize many map elements for the provided *shape object*. +For example, we can change the shapes of the points in `volcanoes`, make their sizes related to the the `"elevation"` argument, and add a title (@fig-qtm-2). + +```{r} +#| label: qtm2 +#| eval: false +qtm(volcanoes, shape = 24, size = "elevation", title = "Volcanoes") +``` + +```{r} +#| label: fig-qtm +#| echo: false +#| layout-ncol: 2 +#| fig-cap: Example of a quick map created with the `qtm()` function. +#| fig-subcap: +#| - A map with geometries only. +#| - A map with geometries and attributes. +<> +<> +``` + + + + +## Regular maps {#sec-regular-maps} + +Therefore, for most applications, we recommend using the regular mapping approach. +This approach operates on many functions that start with `tm_`. +The first element always^[Almost always...] is `tm_shape()`, which specifies the input shape object. +Next, map layers, additional map elements, and overall layout options can be customized. + + + +The last example in @sec-quick-maps can be reproduced with the regular map approach using the following code. + +```{r} +#| eval: false +tm_shape(volcanoes) + + tm_symbols(shape = 24, size = "elevation") + + tm_title("Volcanoes") +``` + +Here, we specify the input data (our shape object) with `tm_shape()`, aesthetics (also known as *visual variables*) of map layers with `tm_symbols()`, and the map title with `tm_title()`. + +The **tmap** package has a number of possible map layers, but the most prominent ones are `tm_polygons()`, `tm_symbols()`, `tm_lines()`, `tm_raster()`, and `tm_text()` (@sec-layers). +Overall, most visual variables of map layers can be assigned in two main ways. +First, they accept a fixed, constant value, for instance, `shape = 24`, which sets the symbols' shapes to triangles. +Second, it is also possible to provide a variable name, for example `size = "elevation"`. +This plots each point with a size based on the `elevation` attribute from the `volcanoes` object and automatically adds a related map legend. + +The `tm_shape()` function and one or more following map layers create a *group* together. +In other words, map layers are related only to the preceding `tm_shape()` call. +One map can have several *groups*. +Let's see how many *groups* can be used by reading some additional datasets -- the `ei_elev` raster with elevation data for Easter Island, the `ei_borders` polygon with the island outline, and the `ei_roads` lines contains a road network for this island. + + +```{r} +#| message: false +library(sf) +library(stars) +ei_elev = read_stars("data/easter_island/ei_elev.tif") +ei_borders = read_sf("data/easter_island/ei_border.gpkg") +ei_roads = read_sf("data/easter_island/ei_roads.gpkg") +``` + +Look at the following example and try to guess how many *groups* it has, and how many layers exist for each *group* (@fig-rmap1). + +```{r} +#| label: fig-rmap1 +#| warning: false +#| fig-height: 9 +#| fig-asp: 0.73 +#| fig-cap: "Example of a map with four groups of map layers: an elevation layer, island borders layer, roads layer, and volcanoes layer." +tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)")) + + tm_shape(ei_borders) + + tm_borders() + + tm_shape(ei_roads) + + tm_lines(lwd = "strokelwd", + lwd.legend = tm_legend(show = FALSE)) + + tm_shape(volcanoes) + + tm_symbols(shape = 24, size = "elevation", + size.legend = tm_legend(title = "Volcanoes (m asl)")) + + tm_title("Easter Island") + + tm_layout(bg.color = "lightblue") +``` + +The correct answer is four groups, all with just one layer. +Each *group* is put on top of the previous one -- **tmap** uses a layered approach. +The first *group* represents elevation data with a continuous color scale style, a color palette called `"geyser"`, and a legend title. +The second *group* shows the borders of Easter Island with the default aesthetics, while the third *group* presents the road network (the `ei_roads` object), with each line's width based on the values from the `"strokelwd"` column, but with a legend hidden. +The last *group* is similar to our previous example with fixed symbol shapes and sizes related to the `"elevation"` attribute, but also with the legend title instead of the map title. +Additionally, we use the `tm_title()` function to add a map title and `tm_layout` to modify the general apperance of the map. +You can also notice that we can control scales of various visual variables, such as color, size, or width, with the `tm_scale_*()` function and customize legends with the `tm_legend()` function. + + +Often, maps also have additional map elements, such as graticule lines, north arrow, scale bar, or map credits (@fig-rmap2). +They help map readers understand the location or extent of the input data and provide some ancillary information. +The **tmap** package offers a set of functions for additional map elements. +The `tm_graticules()` function draws latitude and longitude graticules and adds their labels. +It also uses the layered approach, and thus, the lines will be drawn either below or above the shape objects, depending on the position of this function in the code. +In our example below, `tm_graticules()` is used after all of the map groups, and that is why the graticule lines are put on the top of the spatial data. +We can also use `tm_compass()` to create a north arrow, `tm_scalebar()` to add a scale bar, and `tm_credits()` to add a text annotation representing credits or acknowledgments. +The location of all these three elements on the map is, by default, automatically determined. +It, however, can be adjusted with the `position` argument -- see an example of its use in the `tm_compass()` function below. +Moreover, it is possible to add any type of manual legend with `tm_add_legend()`. +It includes simple legends below, such as the `"Roads"` legend element, that is only represented by a single black line and a related label, but more complex custom legends with several elements are also possible. + + +```{r} +#| warning: false +my_map = tm_shape(ei_elev) + + tm_raster(col.scale = tm_scale(values = "geyser"), + col.legend = tm_legend(title = "Elevation (m asl)")) + + tm_shape(ei_borders) + + tm_borders() + + tm_shape(ei_roads) + + tm_lines(lwd = "strokelwd", + lwd.legend = tm_legend(show = FALSE)) + + tm_shape(volcanoes) + + tm_symbols(shape = 24, size = "elevation", + size.legend = tm_legend(title = "Volcanoes (m asl)")) + + tm_graticules() + + tm_compass(position = c("right", "top")) + + tm_scalebar() + + tm_credits("Author, 2025") + + tm_add_legend(type = "lines", col = "black", labels = "Roads") + + tm_title("Easter Island") + + tm_layout(bg.color = "lightblue") +``` + +Maps created with **tmap** can be saved as an R object. +This is a useful feature that allows to use one map in a few places in a code, modify existing **tmap** objects, or save these objects to files. + +```{r} +#| label: fig-rmap2 +#| message: false +#| fig-height: 9 +#| fig-asp: 0.73 +#| fig-cap: Example of a map with four groups of map layers and additional map elements, such as graticule lines, north arrow, scale bar, and text annotation. It also has a manually added legend. +my_map +``` + + + +## Map modes {#sec-map-modes} + +Each map created with **tmap** can be viewed in one of two modes: `"plot"` and `"view"`. + +The `"plot"` mode is used by default and creates static maps similar to those shown before in this chapter. +This mode supports almost all of **tmap**'s features, and it is recommended, for example, for scientific publications or printing. + +The second mode, `"view"`, allows the creation of interactive maps. +They can be zoomed in and out or panned, allow for changing background tiles (*basemaps*), or click on map objects to get some additional information. +This mode has, however, some constraints and limitations comparing to `"plot"`, for example, the legend cannot be fully customized, and some additional map elements are not supported. + +Both modes can be used on the same **tmap** code. +Therefore, there is no need to create two separate maps for static and interactive use. +The `tmap_mode()` function can be used to switch from one mode to the other^[Map modes can be also changed globally using `tmap_options()` or switched using `ttm()`.]. + +```{r} +tmap_mode("view") +``` + +The above line of code just changes the mode -- it does not return anything except a message. +Now, if we want to use this mode, we need to either write a new **tmap** code or provide some existing **tmap** object, such as `my_map`. + +```{r} +#| eval: false +my_map +``` + +Our main result is the interactive map (@fig-imap1). + +It shows our spatial data using aesthetics similar to @fig-rmap2 but allows us to zoom in and out or move the map. +We also can select a basemap or click on any line and point to get some information. + +```{r} +#| label: fig-imap1 +#| echo: false +#| cache: false +#| message: false +#| fig-cap: Map from the previous figure shown using the interactive ("view") mode. +view_map(my_map, "imap1") +``` + +To go back to the `"plot"` mode, we need to use the `tmap_mode()` function again -- map not shown: + +```{r} +#| fig-show: hide +#| message: false +tmap_mode("plot") +my_map +``` + +More information about the interactive `"view"` mode and how to customize its outputs is in @sec-interactive. + +## Small multiples {#sec-sm-section} + + + +@sec-multiples + +## Animations {#sec-ani-section} + + + +@sec-animations diff --git a/XX-options.qmd b/XX-options.qmd new file mode 100644 index 0000000..d8e35c4 --- /dev/null +++ b/XX-options.qmd @@ -0,0 +1,45 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# tmap options {#sec-options} + + + +```{r} +#| message: false +library(tmap) +library(stars) +worldelevation = read_stars("data/worldelevation.tif") +``` + +## Raster resolution + + +Raster data is represented by a grid of cells (@sec-raster-data-model), and the number of cells impacts the time to render a map. +Rasters with hundreds of cells will be plotted quickly, while rasters with hundreds of millions or billions of cells will take a lot of time (and RAM) to be shown. + +Therefore, the **tmap** package downsamples large rasters by default to be below 10,000,000 cells in the plot mode and 1,000,000 cells in the view mode. + +This values can be adjusted with the `raster.max_cells` argument of `tmap_options()`, which expects a named vector with two elements - `plot` and `view` (@fig-rasterdown). + + + +```{r} +#| label: fig-rasterdown +#| message: false +#| fig-cap: A raster map with the decreased resolution +tmap_options(raster.max_cells = c(plot = 5000, view = 2000)) +tm_shape(worldelevation) + + tm_raster("worldelevation.tif") +``` + +## Resetting the options + +Any **tmap** options can be reset (set to default) with `tmap_options_reset()`. + +```{r} +#| message: false +tmap_options_reset() +``` diff --git a/XX-other-types.qmd b/XX-other-types.qmd new file mode 100644 index 0000000..486096e --- /dev/null +++ b/XX-other-types.qmd @@ -0,0 +1,6 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Other types {#sec-other-types} diff --git a/11-save.Rmd b/XX-save.qmd similarity index 70% rename from 11-save.Rmd rename to XX-save.qmd index b1be55f..ffd8639 100644 --- a/11-save.Rmd +++ b/XX-save.qmd @@ -1,15 +1,22 @@ -# Save maps {#save} +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Save maps {#sec-save} Maps created programmatically can serve several purposes, from exploratory, through visualizations of the processing steps, to being the final outputs of a given project. Therefore, often we want just to see our map on the screen, but sometimes we also want to save our map results to an external file. **tmap** objects can be directly saved to output files with `tmap_save()`^[Standard R approach of saving graphic files by opening a graphic device, e.g., `png()`, plotting the data, and then closing the device with `dev.off()` also works.]. -The `tmap_save()` function allows to save our map in three groups of file formats, (a) raster graphics (section \@ref(raster-graphic-formats)), (b) vector graphics (section \@ref(vector-graphic-formats)), and (c) interactive ones (section \@ref(interactive-format)). -Additionally, chapter \@ref(animations) shows how to save map animations with the use of the `tmap_animation()` function. +The `tmap_save()` function allows to save our map in three groups of file formats, (a) raster graphics (@sec-raster-graphic-formats), (b) vector graphics (@sec-vector-graphic-formats), and (c) interactive ones (@sec-interactive-format). +Additionally, @sec-animations shows how to save map animations with the use of the `tmap_animation()` function. For the examples in this section, we will use a simple map of the Easter Island polygon with the island name superimposed (not shown), stored in the `tm` object. -```{r, message=FALSE, fig.show='hide'} +```{r} +#| message: false +#| fig-show: hide library(tmap) library(sf) ei_borders = read_sf("data/easter_island/ei_border.gpkg") @@ -19,7 +26,7 @@ tm = tm_shape(ei_borders) + tm ``` -## Raster graphic formats +## Raster graphic formats {#sec-raster-graphic-formats} Raster graphics are non-spatial relatives of spatial rasters. The digital images are composed of many pixels - squares filled with specific colors. @@ -27,10 +34,10 @@ Main raster graphic file formats include PNG, JPEG, BMP, and TIFF. One of the major parameters of the raster graphic images is DPI (*Dots Per Inch*, in this context, a more proper name probably should be PPI, *Pixels Per Inch*) - is a number of pixels per inch of the output image. For example, if the width and height of our image are 10 inches, then DPI of 300 would mean that our final image would have 3000 by 3000 pixels, and DPI of 72 would result in an image of 720 by 720 pixels. -Therefore, an image with the same width and height, but larger value of DPI would occupy more space of the hard drive, but also have better quality. +Therefore, an image with the same width and height but a larger value of DPI would occupy more space on the hard drive but also have better quality. Saving **tmap** objects to a file can be done with the `tmap_save()`. -It usually accepts two arguments^[In fact, one argument is enough - if you just provide a **tmap** object, then it will be saved to a `tmap01` file with some default format.] - the first one, `tm`, is our map object, and the second one, `filename`, is the path to the created file. +It usually accepts two arguments^[In fact, one argument is enough -- if you just provide a **tmap** object, then it will be saved to a `tmap01` file with some default format.] -- the first one, `tm`, is our map object, and the second one, `filename`, is the path to the created file. ```{r} tmap_save(tm, "my_map.png") @@ -43,61 +50,66 @@ These parameters can be, however, changed with the `dpi`, `width`, and `height` tmap_save(tm, "my_map.png", width = 1000, height = 750, dpi = 300) ``` -The units of `width` or `height` depend on the value you set. -They are pixels (`"px"`) when the value is greater than 50, and inches (`"in"`) otherwise. +The units of `width` or `height` depend on the value you set: they are pixels (`"px"`) when the value is greater than 50, and inches (`"in"`) otherwise. Units can also be changed with the `units` argument. This function also has several additional arguments, including `outer.margins`, `scale` and `asp`. -All of them override the arguments' values set in `tm_layout()` (chapter \@ref(layout)). -Additionally, when set to `0`, the' asp' argument has a side effect: it moves the map frame to the edges of the output image. +All of them override the arguments' values set in `tm_layout()` (@sec-layout). +Additionally, when set to `0`, the `asp` argument has a side effect: it moves the map frame to the edges of the output image. -By default, **tmap** uses graphic devices^[Short discussion about graphic devices can be found in section \@ref(fonts-tmap).] incorporated in R. +By default, **tmap** uses graphic devices^[Short discussion about graphic devices can be found in section @sec-fonts-tmap.] incorporated in R. However, it is also possible to use other, external devices with the `device` argument. -```{r, eval=FALSE} +```{r} +#| eval: false tmap_save(tm, "my_map.png", device = ragg::agg_png) ``` For example, the `ragg::agg_png` device is usually faster and has better support for non-standard fonts than the regular `grDevices::png`. -```{r, echo=FALSE, eval=FALSE} +```{r} +#| echo: false +#| eval: false tmap_save(tm, "my_map_j1.jpg", quality = 100) tmap_save(tm, "my_map_j2.jpg", quality = 100) ``` -## Vector graphic formats +## Vector graphic formats {#sec-vector-graphic-formats} Vector graphics are quite distant relatives of spatial vectors, with vector graphics consisting of sets of coordinates. -Contrary to spatial vectors, however, their coordinates can be connected not only by straight lines (section \@ref(vector-data-model)), but also using curves. +Contrary to spatial vectors, however, their coordinates can be connected not only by straight lines (@sec-vector-data-model), but also using curves. This makes it possible to create polygons, circles, ellipses, and others. -They also allow storing of text and other objects. -Common vector graphic file formats are SVG, EPS, PDF. +They also allow text and other objects to be stored. +Common vector graphic file formats are SVG, EPS, and PDF. -To save a map to a vector graphic format, we still can use `tmap_save()` but either with a proper extension or by using the `device` argument, for example `device = svglite::svglite`. +To save a map to a vector graphic format, we still can use `tmap_save()` but either with a suitable file extension or by using the `device` argument, for example `device = svglite::svglite`. ```{r} tmap_save(tm, "my_map.svg") ``` -```{r, echo=FALSE, eval=FALSE} +```{r} +#| echo: false +#| eval: false tmap_save(tm, "my_map00.svg", width = 10) tmap_save(tm, "my_map01.svg", width = 1) ``` -While a level of zoom does not impact vector graphics' quality, the `width`, `height`, and `scale` arguments still can impact the output file. +Zooming in and out of vector graphics does not affect their quality. +At the same time, the `width,` `height,` and `scale` arguments can still impact the output file. For example, a vector graphic file saved with a narrower width value will have thicker lines and larger fonts compared to the one with a larger width value. You can check this effect by saving the `tm` object with `width = 1` and then with `width = 10`. Compared to raster graphics, vector graphics are not suitable for storing complex images or maps, and they are less supported by web browsers comparing to rasters. -They, however, has also many advantages. +They, however, also have many advantages. For example, they can be zoomed in and out without any decrease in quality. Vector graphics can also be easily edited in dedicated software (e.g., Inkscape or Adobe Illustrator), which allows to change the style of map elements and move them using a computer mouse outside of the R environment. This approach can be useful, for example, when you want to quickly adjust the position of map elements (e.g., labels) or collaborate with a graphic designer. Note, however, that this process is not fully reproducible. -## Interactive format +## Interactive format {#sec-interactive-format} -`tmap` map objects can not only be viewed in the interactive mode (section \@ref(map-modes)) but also saved as HTML files by adding the `.html` extension to the output file name. +`tmap` map objects can not only be viewed in the interactive mode (@sec-map-modes) but also saved as HTML files by adding the `.html` extension to the output file name. ```{r} tmap_save(tm, "my_map.html") @@ -106,10 +118,13 @@ tmap_save(tm, "my_map.html") The `tmap_save()` function also has several additional arguments reserved for the interactive format, including `selfcontained` and `in.iframe`. The `selfcontained` argument with `TRUE` by default saves our map together with additional resources (e.g., JavaScript libraries) into one HTML file. Otherwise, additional resources will be saved in an adjacent directory. -The `in.iframe` argument (`FALSE` by default) allows saving an interactive map as an iframe - when `TRUE` it creates two HTML files - a small HTML file with the iframe container and a large one with the actual map. +The `in.iframe` argument (`FALSE` by default) allows saving an interactive map as an iframe -- when `TRUE` it creates two HTML files - a small HTML file with the iframe container and a large one with the actual map. -```{r, echo=FALSE, results='hide', warning=FALSE} +```{r} +#| echo: false +#| results: hide +#| warning: false file.remove(c("my_map.png", "my_map.svg", "my_map.html")) ``` diff --git a/XX-scales.qmd b/XX-scales.qmd new file mode 100644 index 0000000..f269e5a --- /dev/null +++ b/XX-scales.qmd @@ -0,0 +1,318 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Scales {#sec-scales} + +Sections @sec-colors, @sec-sizes, and @sec-shapes showed how to set colors, sizes, and shapes for different types of spatial objects. +In them, we often used the `tm_scale()` function to modify the appearance of the map, such as changing the color palette (`col.scale` and `fill.scale`), sizes (`size.scale`), or shapes (`shape.scale`). +The `tm_scale()` function automatically sets the scale for the given visual variable and the data type (factor, numeric, and integer). +Thus, for example, when we provide a character variable's name to the `fill` argument, then the `tm_scale()` function automatically sets the color scale for a categorical variable, and when we provide a numeric variable's name to the `size` argument, then the `tm_scale()` function automatically sets the size scale for a continuous variable. + +However, we often want to have more control over how our spatial objects are presented on the map. +For that purpose, the `tm_scale()` function has a set of related functions that can be used to modify and customize the used scale. +Table @tbl-scale-table presents all available scale functions in **tmap**. +Let's see how to use them in the following sections -- we will mostly focus on using scales for the `fill.scale` argument, but the same principles apply to the `col.scale`, `size.scale`, and `shape.scale` arguments. + + + +```{r} +#| echo: false +scales_df = tibble::tribble( + ~Function, ~Description, + "tm_scale_categorical()", "Categorical scale", + "tm_scale_ordinal()", "Ordinal scale", + "tm_scale_intervals()", "Intervals scale", + "tm_scale_discrete()", "Discrete scale", + "tm_scale_continuous()", "Continuous scale", + "tm_scale_rank()", "Rank scale", + "tm_scale_continuous_log(), tm_scale_continuous_log2(), tm_scale_continuous_log10(), tm_scale_continuous_log1p(), tm_scale_continuous_sqrt(), tm_scale_continuous_pseudo_log()", "Logarithmic scales", + "tm_scale_rgb()", "RGB scale", + "tm_scale_bivariate()", "Bivariate scale", + "tm_scale_asis()", "As-is scale" +) +``` + +```{r} +#| label: tbl-scale-table +#| echo: false +#| warning: false +#| message: false +# library(kableExtra) +# options(kableExtra.html.bsTable = TRUE) +# knitr::kable(scales_df, +# caption = "Map scales", +# booktabs = TRUE) |> +# kableExtra::kable_styling("striped", +# latex_options = "striped", +# full_width = FALSE) |> +# kableExtra::column_spec(1, bold = TRUE, monospace = TRUE) +tinytable::tt(scales_df, caption = "Map scales") |> + tinytable::style_tt(j = 1, monospace = TRUE) +``` + +```{r} +#| echo: false +#| warning: false +#| message: false +library(tmap) +library(sf) +worldvector = read_sf("data/worldvector.gpkg") +``` + +## Categorical scales {#sec-categorical-scales} + +\index{Categorical maps} +An example of a categorical map can be seen in @fig-colorscales2. +We created it by providing a character variable's name, `"wb_region"`, in the `col` argument. + +```{r} +#| label: fig-colorscales2 +#| warning: false +#| message: false +#| fig-height: 2 +#| fig-cap: Example of a map in which polygons are colored based on the values of a categorical +#| variable. +tm_shape(worldvector) + + tm_polygons(fill = "wb_region") +``` + +The `tm_polygons(fill = "region_un", fill.scale = tm_scale_categorical())` code is run automatically in the background in this case. +It is possible to change the names of legend labels with the `labels` argument of the `tm_scale()` function. +As mentioned in the @sec-colors we can also change the used color palette with the `values` argument. + +```{r} +#| eval: false +#| echo: false +tm_shape(worldvector) + + tm_polygons(fill = "wb_region", + fill.scale = tm_scale_categorical(labels = as.character(1:7))) +``` + + + +## Intervals scales {#sec-intervals-scales} + + +\index{Intervals maps} +Intervals scales are used to represent continuous numerical variables using set of class intervals. +In other words, values are divided into several groups based on their properties. +Several approaches can be used to convert continuous variables to intervals, and each of them could result in different groups of values. + +Most of them use the **classInt** package [@R-classInt] in the background, therefore some additional information can be found in the `?classIntervals` function's documentation. + +By default, the `tm_scale_intervals()` function is used in the background (@fig-intervals-methods-1). +It uses a style called "pretty", which creates breaks that are whole numbers and spaces them evenly ^[For more information visit the `?pretty()` function documentation]. + +```{r} +#| label: intervals-methods1 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap") +``` + +It is also possible to indicate the desired number of classes using the `n` argument of the `tm_scale()` function provided to the `fill.scale` argument. +While not every `n` is possible depending on the input values, **tmap** will try to create a number of classes as close to possible to the preferred one. + +The next approach is to manually select the limits of each break with the `breaks` argument of `tm_scale()` (@fig-intervals-methods-2). +This can be useful when we have some pre-defined breaks, or when we want to compare values between several maps. +It expects threshold values for each break, therefore, if we want to have three breaks, we need to provide four thresholds. +Additionally, we can add a label to each break with the `labels` argument. + +```{r} +#| label: intervals-methods2 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_intervals(breaks = c(0, 10000, 30000, 111000), + labels = c("low", "medium", "high"))) +``` + +Another approach is to create breaks automatically using one of many existing classification methods with the `style` argument of the `tm_scale()` function. +Three basic methods are `"equal"`, `"sd"`, and `"quantile"` styles. +Let's consider a variable with 100 observations ranging from 0 to 10. +The `"equal"` style divides the range of values into *n* equal-sized intervals. +This style works well when the values change fairly continuously and do not contain any outliers. +In **tmap**, we can specify the number of classes with the `n` argument or the number of classes will be computed automatically . +For example, when we set `n` to 4, then our breaks will represent four classes ranging from 0 to 2.5, 2.5 to 5, 5 to 7.5, and 7.5 to 10. +The `"sd"` style represents how much values of a given variable varies from its mean, with each interval having a constant width of the standard deviation. +This style is used when it is vital to show how values relate to the mean. +The `"quantile"` style creates several classes with exactly the same number of objects (e.g., spatial features), but having intervals of various lengths. +This method has an advantage or not having any empty classes or classes with too few or too many values. +However, the resulting intervals from the `"quantile"` style can often be misleading, with very different values located in the same class. + +To create classes that, on the one hand, contain similar values, and on the other hand, are different from the other classes, we can use some optimization method. +The most common optimization method used in cartography is the Jenks optimization method implemented at the `"jenks"` style (@fig-intervals-methods-3). + +```{r} +#| label: intervals-methods3 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_intervals(style = "jenks")) +``` + +The Fisher method (`style = "fisher"`) has a similar role, which creates groups with maximized homogeneity [@fisher_grouping_1958]. +A different approach is used by the `dpih` style, which uses kernel density estimations to select the width of the intervals [@wand_databased_1997]. +You can visit `?KernSmooth::dpih` for more details. + +Another group of classification methods uses existing clustering methods. +It includes k-means clustering (`"kmeans"`), bagged clustering (`"bclust"`), and hierarchical clustering (`"hclust"`). + + +Finally, there are a few methods created to work well for a variable with a heavy-tailed distribution, including `"headtails"` and `"log10_pretty"`. +The `"headtails"` style is an implementation of the head/tail breaks method aimed at heavily right-skewed data. +In it, values of the given variable are being divided around the mean into two parts, and the process continues iteratively for the values above the mean (the head) until the head part values are no longer heavy-tailed distributed [@jiang_head_2013]. +The `"log10_pretty"` style uses a logarithmic base-10 transformation (@fig-intervals-methods-4). +In this style, each class starts with a value ten times larger than the beginning of the previous class. +In other words, each following class shows us the next order of magnitude. +This style allows for a better distinction between low, medium, and high values. +However, maps with logarithmically transformed variables are usually less intuitive for the readers and require more attention from them. + +```{r} +#| label: intervals-methods4 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_intervals(style = "log10_pretty")) +``` + + +```{r} +#| label: fig-intervals-methods +#| warning: false +#| message: false +#| echo: false +#| layout-ncol: 2 +#| fig-cap: Examples of four methods of creating intervals maps +#| fig-subcap: +#| - The "pretty" method +#| - The "fixed" method +#| - The "jenks" method +#| - The "log10_pretty" method +<> +<> +<> +<> +``` + + + +## Discrete scales {#sec-discrete-scales} + +## Continuous scales {#sec-continuous-scales} + +\index{Continuous maps} +Continuous maps also represent continuous numerical variables, but without any discrete class intervals (@fig-cont-methods). +A few continuous methods exist in **tmap**, including `tm_scale_continuous()`, `tm_scale_rank()`, and `tm_scale_continuous_log10()`. + +The `tm_scale_continuous` function creates a smooth, linear gradient. +In other words, the change in values is proportionally related to the change in colors. +We can see that in @fig-cont-methods-1, where the value change from 20,000 to 40,000 has a similar impact on the color scale as the value change from 40,000 to 60,000. +The continuous scale is similar to the pretty style, where the values also change linearly. +The main difference between them is that we can see differences between, for example, values of 45,000 and 55,000 in the former, while both values have exactly the same color in the later one. +The continuous scale works well in situations where there is a large number of objects in vectors or a large number of cells in rasters, and where the values change continuously (do not have many outliers). + +```{r} +#| label: cont-methods1 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_continuous()) +``` + +However, when the presented variable is skewed or have some outliers, we can use either `tm_scale_rank()` or `tm_scale_continuous_log10()`. +The `tm_scale_rank()` scale also uses a smooth gradient with a large number of colors, but the values on the legend do not change linearly (@fig-cont-methods-2). + +It is fairly analogous to the `"quantile"` style, with the values on a color scale that divides a dataset into several equal-sized groups. + +```{r} +#| label: cont-methods2 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_rank()) +``` + +Finally, the `tm_scale_continuous_log10()` scale is the continuous equivalent of the `"log10_pretty"` style of `tm_scale_intervals()` (@fig-cont-methods-3). + +```{r} +#| label: cont-methods3 +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "gdp_per_cap", + fill.scale = tm_scale_continuous_log10()) +``` + +```{r} +#| label: fig-cont-methods +#| warning: false +#| message: false +#| echo: false +#| fig-height: 3.46 +#| layout-ncol: 3 +#| fig-cap: "Examples of three methods of creating continuous maps" +#| fig-subcap: +#| - The "continuous" method +#| - The "rank" method +#| - The "log10" method +<> +<> +<> +``` + +## RGB scales {#sec-rgb-scales} + +## Bivariate scales {#sec-bivariate-scales} + +## As-is scales {#sec-asis-scales} + + + + + + +```{r} +#| eval: false +#| echo: false +tm_shape(worldvector) + + tm_polygons(fill = "MAP_COLORS") +``` + + + + + +```{r} +#| label: fig-colorscalesmc +#| warning: false +#| echo: false +#| eval: false +#| fig-cap: Example of a map with adjacent polygons having different colors. +#| fig-height: 2 +tm_uni = tm_shape(worldvector) + + tm_polygons(fill = "MAP_COLORS") +tm_uni +``` + + + + + +```{r} +#| echo: false +#| eval: false +data("metro", package = "tmap") +data("World_rivers", package = "tmap") +data("land", package = "tmap") +tm_shape(metro) + + tm_symbols(fill = "pop2030", fill.scale = tm_scale_continuous()) +tm_shape(World_rivers) + + tm_lines(col = "strokelwd", col.scale = tm_scale_continuous()) +tm_shape(land) + + tm_raster(col = "elevation", col.scale = tm_scale_continuous()) +``` + + + \ No newline at end of file diff --git a/XX-shiny.qmd b/XX-shiny.qmd new file mode 100644 index 0000000..8ea432f --- /dev/null +++ b/XX-shiny.qmd @@ -0,0 +1,6 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# **tmap** in **shiny** {#sec-shiny} diff --git a/09-small-multiples.Rmd b/XX-small-multiples.qmd similarity index 51% rename from 09-small-multiples.Rmd rename to XX-small-multiples.qmd index f0488eb..fe02310 100644 --- a/09-small-multiples.Rmd +++ b/XX-small-multiples.qmd @@ -1,4 +1,9 @@ -# Small multiples {#multiples} +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Small multiples {#sec-multiples} ## Vector data diff --git a/XX-tm-shape.qmd b/XX-tm-shape.qmd new file mode 100644 index 0000000..cc8ab36 --- /dev/null +++ b/XX-tm-shape.qmd @@ -0,0 +1,243 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Specifying spatial data {#sec-tmshape} + +At least two aspects need to be specified in order to plot spatial data: the spatial data object itself and the plotting method(s). +We will cover the former in this chapter. +The latter will be discussed in the next chapters. + +## Shapes and layers {#sec-shapes-and-layers} + +As described in @sec-geodata, shape objects can be vector or raster data. +We recommend `sf` objects for vector data and `stars` objects for raster data^[However, **tmap** also accepts other spatial objects, e.g., of `sp`, `raster`, and `terra` classes.]. + +```{r} +#| echo: true +#| warning: false +#| message: false +library(tmap) +library(dplyr) +library(sf) +library(stars) +worldelevation = read_stars("data/worldelevation.tif") +worldvector = read_sf("data/worldvector.gpkg") +worldcities = read_sf("data/worldcities.gpkg") +``` + +In **tmap**, a shape object needs to be defined with the function `tm_shape()`. +When multiple shape objects are used, each has to be defined in a separate `tm_shape()` call. +This is illustrated in the following example (@fig-tmshape1). + +```{r} +#| label: fig-tmshape1 +#| echo: true +#| warning: false +#| fig-cap: A map representing three shapes (worldelevation, worldvector, and worldcities) +#| using four layers. +tm_shape(worldelevation) + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) + + tm_shape(worldvector) + + tm_borders() + + tm_shape(worldcities) + + tm_dots() + + tm_text("name") +``` + +In this example, we use three shapes: `worldelevation` which is a `stars` object containing an attribute called `"worldelevation.tif"`, `worldvector` which is an `sf` object with country borders, and `worldcities` -- an `sf` object that contains metropolitan areas of at least 20 million inhabitants. + +Each `tm_shape()` function call is succeeded by one or more layer functions. +In the above example, these are `tm_raster()`, `tm_borders()`, `tm_dots()` and `tm_text()`. +We will describe layer functions in detail in the next chapter. +For this chapter, it is sufficient to know that each layer function call defines how the spatial data specified with `tm_shape()` is plotted. + +Shape objects can be used to plot multiple layers. +In the example, shape `worldcities` is used for two layers, `tm_dots()` and `tm_text()`. + +## Shapes hierarchy {#sec-shapes-hierarchy} + +The order of the `tm_shape()` functions' calls is crucial. +The first `tm_shape()`, known as *the main shape*, is not only shown below the following *shapes*, but also sets the projection and extent of the whole map. +In @fig-tmshape1, the `worldelevation` object was used as the first *shape*, and thus the whole map has the projection and extent of this object. + +However, we can quickly change the main *shape* with the `is.main` argument. +In the following example, we set the `worldcities` object as the main *shape*, which limits the output map to the point locations in `worldcities` (@fig-tmshape2)^[We will show how to adjust margins and text locations later in the book]. + +```{r} +#| label: fig-tmshape2 +#| message: false +#| #fig-asp: 0.35 +#| fig-cap: "A map representing three shapes (worldelevation, worldvector, and worldcities) using four layers and zoomed into the locations in the worldcities object." +tm_shape(worldelevation) + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) + + tm_shape(worldvector) + + tm_borders() + + tm_shape(worldcities, is.main = TRUE) + + tm_dots() + + tm_text("name") +``` + +## Map extent {#sec-map-extent} + +Another important aspect of mapping, besides projection, is its extent -- a portion of the area shown in a map. + +This is not an issue when the extent of our spatial data is the same as we want to show on a map. +However, what should we do when the spatial data contains a larger region than we want to present? + +Again, we could take two routes. +The first one is to preprocess our data before mapping - this can be done with vector clipping (e.g., `st_intersection()`) and raster cropping (e.g., `st_crop()`). +We would recommend this approach if you plan to work on the smaller data in the other parts of the project. +The second route is to specify the map extent in **tmap**. + +**tmap** allows specifying map extent using three approaches. +The first one is to specify minimum and maximum coordinates in the x and y directions that we want to represent. +This can be done with a numeric vector of four values in the order of minimum x, minimum y, maximum x, and maximum y, where all of the coordinates need to be specified in the input data units^[This can also be done with the object of class `st_bbox` or a 2 by 2 matrix.] +In the following example, we limit our map extent to the rectangular area between x from -15 to 45 and y from 35 to 65 (@fig-tbbox1). + +```{r} +#| label: fig-tbbox1 +#| warning: false +#| fig-cap: Global elevation data limited to the extent of the specified minimum and +#| maximum coordinates. +tm_shape(worldelevation, bbox = c(-15, 35, 45, 65)) + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) +``` + +The second approach allows for the map to be set to an extent based on a search query. +In the code below, we limit the map extent to the area of `"Europe"` (@fig-tbbox2). +This approach uses the OpenStreetMap tool called Nominatim to automatically generate minimum and maximum coordinates in the x and y directions based on the provided query. + + +```{r} +#| label: fig-tbbox2 +#| warning: false +#| fig-cap: Global elevation data limited to the extent specified with the 'Europe' query. +tm_shape(worldelevation, bbox = "Europe") + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) +``` + +In the last approach, the map extent is based on another existing spatial object. +@fig-tbbox3 shows the elevation raster data (`worldelevation`) limited to the edge coordinates from `worldcities`. + +```{r} +#| label: fig-tbbox3 +#| warning: false +#| fig-asp: 0.35 +#| fig-cap: Global elevation data limited to the extent of the other spatial object. +tm_shape(worldelevation, bbox = worldcities) + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) +``` + + + + + +## Map projection {#sec-map-projection} +\index{map projection} + +As we mentioned in the previous section, created maps use the projection from the main *shape*. +However, we often want to create a map with a different projection, for example, to preserve a specific map property (@sec-crs). +We can do this in three ways. +The first way to use a different projection on a map is to reproject the main data before plotting, as shown in @sec-crs-in-r. +The second way is to specify the map projection using the `crs` argument of `tm_shape()`. +This argument expects either some `crs` object or a CRS code. +The third way is to use a `tm_crs()` function. + +The next code chunks shows all of the three ways, in which we transform the CRS of the `worldvector` object to `"EPSG:8857"`. +This represents a projection called [Equal Earth](http://equal-earth.com/index.html) [@savric_equal_2019]. +The Equal Earth projection is an equal-area pseudocylindrical projection for world maps similar to the non-equal-area Robinson projection (@fig-crs-robin). + +```{r} +#| eval: false +#1 +worldvector8857 = st_transform(worldvector, crs = "EPSG:8857") +tm_shape(worldvector8857) + + tm_polygons() +#2 +tm_shape(worldvector, crs = "EPSG:8857") + + tm_polygons() +#3 +tm_shape(worldvector) + + tm_polygons() + + tm_crs("EPSG:8857") +``` + +The first way requires understanding various R packages, as different spatial objects have different functions for changing the projection. +The second way is the most straightforward, but it is important to remember that the `crs` argument can only be set in the main layer (@sec-shapes-hierarchy). +The third way is the most flexible, as it allows changing the projection for the whole map. +Additionally, the `tm_crs()` function can automatically determine the projection based on the expected property of the map, e.g., equal area (`"area"`), equidistant (`"distance"`), or conformal (`"shape"`). +For example, `tm_crs("auto")` will choose the projection that best preserves the area of the map (*Lambert Azimuthal Equal Area*), while `tm_crs("auto", property = "shape")` will choose the projection that best preserves the shape of the map (*Stereographic*). + +Reprojections of vector data are usually straightforward because each spatial coordinate is reprojected individually. + +Reprojecting of raster data, on the other hand, is more complex and requires using one of two approaches. +The first approach applies raster warping, which is a name for two separate spatial operations: creation of a new regular raster object and computation of new pixel values through resampling (for more details read Chapter 7 of @lovelace_geocomputation_2025). +This is the default option in **tmap**, however, it has some limitations and it is not always possible to use it. + + +@fig-tm-map-proj-1 shows the world elevation raster reprojected to Equal Earth. +Some of you can quickly notice that certain areas, such as parts of Antarctica, New Zealand, Alaska, and the Kamchatka Peninsula, are presented twice, with one version being largely distorted. +Another limitation of `raster.warp = TRUE` is the use of the nearest neighbor resampling only -- while it can be a proper method to use for categorical rasters, it can have some unintended consequences for continuous rasters (such as the `"worldelevation.tif"` data). + +```{r} +#| label: tm-map-proj1 +#| warning: false +#| eval: false +tm_shape(worldelevation, crs = "EPSG:8857") + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) +``` + +The second approach (`tm_options(raster.warp = FALSE)`) computes new coordinates for each raster cell keeping all of the original values and results in a curvilinear grid. +This calculation could deform the shapes of original grid cells, and usually curvilinear grids take a longer time to plot^[For more details of the first approach, see `?stars::st_warp()` and of the second approach, see `?stars::st_transform()`.]. + +@fig-tm-map-proj-2 shows an example of the second approach, which gave a better result in this case without any spurious lands. +However, the creation of the (b) map takes about ten times longer than the (a) map. + +```{r} +#| label: tm-map-proj2 +#| warning: false +#| eval: false +tm_shape(worldelevation, crs = "EPSG:8857") + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) + + tm_options(raster.warp = FALSE) +``` + +```{r} +#| label: fig-tm-map-proj +#| echo: false +#| warning: false +#| layout-nrow: 2 +#| fig-cap: Two elevation maps in the Equal Earth projection +#| fig-subcap: +#| - created using raster.warp = TRUE +#| - created using raster.warp = FALSE +<> +<> +``` + +```{r} +#| echo: false +#| eval: false +tmp1 = tm_shape(worldelevation, crs = "EPSG:8857") + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) +tmp2 = tm_shape(worldelevation, crs = "EPSG:8857") + + tm_raster("worldelevation.tif", + col.scale = tm_scale(values = terrain.colors(8))) + + tm_options(raster.warp = FALSE) +bench::mark(print(tmp1), print(tmp2), check = FALSE) +# 2.28s vs 24.52s +``` + + + + diff --git a/XX-visual-variables.qmd b/XX-visual-variables.qmd new file mode 100644 index 0000000..cd62077 --- /dev/null +++ b/XX-visual-variables.qmd @@ -0,0 +1,771 @@ +```{r} +#| echo: false +source("code/before_script.R") +``` + +# Visual variables {#sec-visual-variables} + +Visual variables are methods to translate information given in variables into many types of visualizations, including maps. +Basic visual variables are color, size, and shape^[Other visual variables include position, orientation, and texture.]. +All of them can influence our perception and understanding of the presented information; therefore, it is worth understanding when and how they can be used. + + + + +```{r} +#| label: fig-visual-variables +#| echo: false +#| warning: false +#| message: false +#| fig-cap: Basic visual variables and their representations on maps +source("code/visual_variables.R") +visual_variables() +``` + +The use of visual variables on maps depends on two main things: (a) type of the presented variable, and (b) type of the map layer. +@fig-visual-variables shows examples of different visual variables. +Color is the most universal visual variable. +It can represent both qualitative (categorical) and quantitative (numerical) variables, and also we can color symbols, lines, or polygon fillings. +Sizes, on the other hand, should focus on quantitative variables. +Small symbols could represent low values of a given variable, and the higher the value, the larger the symbol. +Quantitative values of line data can be shown with the widths of the lines (@sec-sizes). +The use of shapes usually should be limited to qualitative variables, and different shapes can represent different categories of points (@sec-shapes). +Similarly, qualitative variables in lines can be presented by different line types. +Values of polygons usually cannot be represented by either shapes or sizes, as these two features are connected to the geometries of the objects. + + + + +```{r} +#| echo: false +#| warning: false +#| message: false +library(tmap) +library(sf) +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanos = subset(ei_points, type == "volcano") +``` +```{r} +#| echo: false +#| warning: false +#| message: false +library(tmap) +library(sf) +worldvector = read_sf("data/worldvector.gpkg") +``` + +## Constant visual values {#sec-const-visual-variables} + +Before we start using visual variables, it is worth reminding that we can set constant values for visual variables: fill, color, size, and shape for all objects on the map. +For example, with `tm_polygons()` we can fill all polygons with the same color, and set borders to the same color, width, and type (@fig-colorscales1). + +```{r} +#| label: fig-colorscales1 +#| warning: false +#| fig-cap: Example of a map with all polygons filled with the same color. +#| fig-height: 2 +tm_shape(worldvector) + + tm_polygons(fill = "lightblue", + col = "black", + lwd = 0.5, + lty = "dashed") +``` + +## Automatic scales {#sec-auto-scales} + +On the other hand, if we provide a name of the column (variable) we want to visualize, **tmap** will automatically set the visual variables. +The **tmap** package has many defaults that automatically specify colors, sizes, and shapes used on the maps. +Thus, for example, when we provide a character variable's name to the `fill` argument, then the color scale for a categorical variable is set, and when we provide a numeric variable's name to the `size` argument, then the size scale for a continuous variable is used. +However, **tmap** also allows us to change them with a few additional functions, including `tm_scale()` and `tm_legend()`. +In this and the following sections, we will show how to use the `tm_scale()` function to modify the appearance of the visual variables. + + +The most basic use of the `tm_scale()` function is to modify the appearance of the map, such as changing the color palette (`col.scale` and `fill.scale`), sizes (`size.scale`), or shapes (`shape.scale`). +This is done with the `values` argument, which can be one of many things, depending on the visual variable. +For example, for colors, it can be a vector of colors, a palette function, or a name of a build-in palette; for sizes, it can be a numerical variable; and for shapes, it can be a custom icon or a numerical variable (@fig-tmscalevals). + +```{r} +#| label: fig-tmscalevals +#| fig-cap: "Examples of various ways of specifying values for visual variables" +#| fig-subcap: +#| - A name of a build-in color palette +#| - A vector of colors +#| - A numerical variable for sizes +#| - A numerical variable for shapes +#| message: false +#| layout-ncol: 2 +tm_shape(volcanos) + + tm_symbols(fill = "elevation", + fill.scale = tm_scale(values = "greens")) +tm_shape(volcanos) + + tm_symbols(col = "elevation", + col.scale = tm_scale(values = c("red", "blue"))) +tm_shape(volcanos) + + tm_symbols(size = "elevation", + size.scale = tm_scale(values = 1:5)) +tm_shape(volcanos) + + tm_symbols(shape = "elevation", + shape.scale = tm_scale(values = 1:6)) +``` + +The following sections @sec-colors, @sec-sizes, and @sec-shapes explain the basic visual variables. +Next, they show how to set colors, sizes, and shapes for different types of spatial objects. + +## Colors {#sec-colors} + +\index{colors} +Colors, along with sizes and shapes, are the most often used to express values of attributes or their properties. +Proper use of colors draws the attention of viewers and has a positive impact on the clarity of the presented information. +On the other hand, poor decisions about colors can lead to misinterpretation of the map. + +\index{color palettes} + + + + + +\index{colors} +\index{hexadecimal form} +Colors in R are created based either on the color name or its hexadecimal form. +R understands 657 built-in color names, such as `"red"`, `"lightblue"` or `"gray90"`, that are available using the `colors()` function. + + +Hexadecimal form, on the other hand, can represent 16,777,216 unique colors. +It consists of six-digits prefixed by the `#` (hash) symbol, where red, green, and blue values are each represented by two characters. +In hexadecimal form, `00` is interpreted as `0.0` which means a lack of a particular color and `FF` means `1.0` and shows that the given color has maximal intensity. +For example, `#000000` represents black color, `#FFFFFF` white color, and `#00FF00` green color. + + +Using a single color we are able to draw points, lines, polygon borders, or their areas. +In that scenario, all of the elements will have the same color. +However, often, we want to represent different values in our data using different colors. +This is a role for color palettes. +A color palette is a set of colors used to distinguish the values of variables on maps. + +\index{color palettes} +Color palettes in R are usually stored as a vector of either color names or hexadecimal representations. +For example, `c("red", "green", "blue")` or `c("#66C2A5", "#FC8D62", "#8DA0CB")`. +It allows every one of us to create our own color palettes. +However, the decision on how to decide which colors to use is not straightforward, and usually requires thinking about several aspects. + +\index{color properties} +Firstly, what kind of variable we want to show? + +Is it a categorical variable where each value represents a group or a numerical variable in which values have order? + +The variable type impacts how it should be presented on the map. +For categorical variables, each color usually should receive the same perceptual weight, which is done by using colors with the same brightness, but different hue. +On the other hand, for numerical variables, we should easily understand which colors represent lower and which represent higher values. +This is done by manipulating colorfulness and brightness. +For example, low values could be presented by a blue color with low colorfulness and high brightness, and with growing values, colorfulness increases, and brightness decreases. + +\index{color perception} +The next consideration is related to how people perceive some colors. +Usually, we want them to be able to preliminary understand which values the colors represent without looking at the legend -- colors should be intuitive. +For example, in the case of categorical variables representing land use, we usually want to use some type of blue color for rivers, green for trees, and white for ice. +This idea also extends to numerical variables, where we should think about the association between colors and cultural values. +The blue color is usually connected to cold temperature, while the red color is hot or can represent danger or something not good. +However, we need to be aware that the connection between colors and cultural values varied between cultures. + + +\index{color blindness} +Another thing to consider is to use a color palette that is accessible for people with color vision deficiencies (color blindness). + +There are several types of color blindness, with red-green color blindness (*deuteranomaly*) being the most common. +It is estimated that up to about 8% of the male population and about 0.5% of the female population in some regions of the world is color blind [@birch_worldwide_2012;@sharpe_opsin_1999]. + + + +The relation between the selected color palette and other map elements or the map background should also be taken into consideration. +For example, using a bright or dark background color on a map has an impact on how people will perceive different color palettes. + + + + + + + + + +\index{color palettes} +Generally, color palettes can be divided into three main types (@fig-palette-types): + +- **Categorical** (also known as Qualitative): used for presenting categorical information, such as categories or groups. +Every color in this type of palette should receive the same perceptual weight, and the order of colors is meaningless. +Categorical color palettes are usually limited to a dozen or so different colors, as our eyes have problems with distinguishing a large number of different hues. +Their use includes, for example, regions of the world or land cover categories. +- **Sequential**: used for presenting continuous variables, in which order matters. +Colors in this palette type changes from low to high (or vice versa), which is usually underlined by luminance differences (light-dark contrasts). +Sequential palettes can be found in maps of GDP, population density, elevation, and many others. +- **Diverging**: used for presenting continuous variables, but where colors diverge from a central neutral value to two extremes. +Therefore, in a sense, they consist of two sequential palettes that meet in the midpoint value. +Examples of diverging palettes include maps where a certain temperature or median value of household income is use as the midpoint. +It can also be used on maps to represent differences or change as well. + +```{r} +#| echo: false +#| eval: false +to = tmap_options() +to$values.var$fill +``` + + + +```{r} +#| label: fig-palette-types +#| echo: false +#| fig-cap: 'Examples of three main types of color palettes: categorical, sequential, +#| and diverging' +#| fig-asp: 0.5 +# y - a named list of palettes +source("code/palette_figures.R") +p_cat = cols4all::c4a("cols4all.area7", n = 7) +p_seq = cols4all::c4a("-hcl.blues3", n = 7) +p_div = cols4all::c4a("pu_gn_div", n = 7) + +p_cat2 = cols4all::c4a("brewer.set2", n = 7) +p_seq2 = cols4all::c4a("viridis", n = 7) +p_div2 = cols4all::c4a("brewer.br_bg", n = 7) + +y = list(Categorical = list(area7 = p_cat, set2 = p_cat2), + Sequential = list(blues3 = p_seq, viridis = p_seq2), + Diverging = list(pu_gn = p_div, br_bg = p_div2)) +plot_palette_types(y) +``` + +\index{color palettes} +Gladly, a lot of work has been put on creating color palettes that are grounded in the research of perception and design. +Currently, [several dozens of R packages](https://github.com/EmilHvitfeldt/r-color-palettes +) contain hundreds of color palettes. +The most popular among them are **RColorBrewer** [@R-RColorBrewer] and **viridis** [@R-viridis]. +**RColorBrewer** builds upon a set of perceptually ordered color palettes [@harrower_colorbrewer_2003] and the associated website at . +The website not only presents all of the available color palettes, but also allow to filter them based on their properties, such as being colorblind safe or print-friendly. +The **viridis** package has five color palettes that are perceptually uniform and suitable for people with color blindness. +Four palettes in this package ("viridis", "magma", "plasma", and "inferno") are derived from the work on the color palettes for [the matplotlib Python library](http://bids.github.io/colormap/). +The last one, "cividis", is based on the work of @nunez_optimizing_2018. + +```{r} +RColorBrewer::brewer.pal(7, "RdBu") +viridis::viridis(7) +``` + +\index{color palettes} +In the last few years, the **grDevices** package that is an internal part of R, has received several improvements in color palette handling.^[Learn more about them at and .] +It includes the creation of `hcl.colors()` and `palette.colors()`. +The `hcl.colors()` function [incorporates color palettes from several R packages](http://colorspace.r-forge.r-project.org/articles/approximations.html), including **RColorBrewer**, **viridis**, **rcartocolor** [@carto_cartocolors_2019;@R-rcartocolor], and **scico** [@crameri_geodynamic_2018;@R-scico]. +You can get the list of available palette names for `hcl.colors()` using the `hcl.pals()` function and visualize all of the palettes with `colorspace::hcl_palettes(plot = TRUE)`. +The `palette.colors()` function adds [several palettes for categorical data](https://developer.r-project.org/Blog/public/2019/11/21/a-new-palette-for-r/index.html). +It includes `"Okabe-Ito"` [suited for color vision deficiencies](https://jfly.uni-koeln.de/color/) or `"Polychrome 36"` that has 36 unique colors [@coombes_polychrome_2019]. +You can find the available names of the palettes for this function using `palette.pals()` + +```{r} +grDevices::hcl.colors(7, "Oslo") +grDevices::palette.colors(7, "Okabe-Ito") +``` + +The **cols4all** package is a set of tools for selecting color palettes; it also includes all of the groups of color palettes mentioned above and more. +Its main function is `cols4all::c4a_gui()` that starts an interactive application allowing to see and examine hundreds of color palettes. +Each color palette shown `cols4all::c4a_gui()` can be used in **tmap**. + +\index{color palettes!rainbow} +One of the most widely used color palettes is "rainbow" (the `rainbow()` function in R). +It was inspired by colors of a rainbow -- a set of seven colors going from red to violet. +However, this palette has a number of disadvantages, including irregular changes in brightness affecting its interpretation or being unsuitable for people with color vision deficiencies [@borland_rainbow_2007;@stauffer_somewhere_2015;@quinan_examining_2019]. +Depending on a given situation, there are many palettes better suited for visualization than "rainbow", including sequential `"viridis"` and `"ag_sunset"` or diverging `"pu_gn"` and `"geyser"`. +All of them can be specified in **tmap**.^[You can also derive them using `cols4all::c4a()` function.] +More examples showing alternatives to the "rainbow" palette are in the documentation of the **colorspace** package at +https://colorspace.r-forge.r-project.org/articles/endrainbow.html [@R-colorspace]. + +```{r} +#| echo: false +#| warning: false +#| message: false +library(tmap) +library(sf) +worldvector = read_sf("data/worldvector.gpkg") +``` + +```{r} +#| echo: false +#| eval: false +to = tmap_options() +to$values.var$fill +``` + +By default, the **tmap** package attempts to identify the type of variable used. +For example, it uses `"area7"` for a categorical (unordered) variable, `"blues3"` for a sequential variable, and "`pu_gn`" for a diverging one (@fig-tmpals). + + +```{r} +#| label: tmpals1 +#| eval: false +tm_shape(worldvector) + + tm_polygons("life_expectancy") +``` + +There are three main ways to specify color palettes as an `values` argument of the `tm_scale()` family of functions. + +This argument accepts (1) a vector of colors, (2) a palette function, or (3) one of the built-in palette names (@fig-tmpals). +A vector of colors can be specified using color names or hexadecimal representations (@fig-tmpals). +Importantly, the length of the provided vector does not need to be equal to the number of colors in the map legend. +**tmap** automatically interpolates new colors when a smaller number of colors are provided. + +```{r} +#| label: tmpals2 +#| eval: false +tm_shape(worldvector) + + tm_polygons("life_expectancy", + fill.scale = tm_scale(values = c("yellow", "darkgreen"))) +``` + +Another approach is to provide the output of a palette function (@fig-tmpals). +In the example below, we derived seven colors from `"ag_GrnYl"` palette. +This palette goes from green colors to yellow ones, however, we wanted to reverse the order of this palette. +Thus, we also used the `rev()` function here. + +```{r} +#| label: tmpals3 +#| eval: false +tm_shape(worldvector) + + tm_polygons("life_expectancy", + fill.scale = tm_scale(values = rev(hcl.colors(7, "ag_GrnYl")))) +``` + +The last approach is to use one of the names of color palettes built in **tmap** (@fig-tmpals). +In this example, we used the `"brewer.yl_gn"` palette that goes from yellow to green. + +```{r} +#| label: tmpals4 +#| eval: false +tm_shape(worldvector) + + tm_polygons("life_expectancy", + fill.scale = tm_scale(values = "brewer.yl_gn")) +``` + +It is also possible to reverse the order of any named color palette by using the `-` prefix. +Therefore, `"-yl_gn"` will return a palette going from green to yellow. + +```{r} +#| label: fig-tmpals +#| warning: false +#| echo: false +#| layout-ncol: 2 +#| fig-cap: "Examples of four ways of specifying color palettes" +#| fig-subcap: +#| - default sequential color palette +#| - palette created based on provided vector of colors +#| - palette created using the `hcl.colors()` function +#| - one of the build-in palettes +<> +<> +<> +<> +``` + + + +The default color palette for positive numerical variables is `"blues3"` as seen in @fig-tmmidpoint-1. +On the other hand, when the given variable has both negative and positive values, then **tmap** uses the `"pu_gn"` color palette, with purple colors below the midpoint value, light gray color around the midpoint value, and green colors above the midpoint value. +The use of diverging color palettes can be adjusted using the `midpoint` argument. +It has a value of 0 as the default, however, it is possible to change it to any other value. +For example, we want to create a map that shows countries with life expectancy below and above the median life expectancy of about 73 years. +To do that, we just need to set the `midpoint` argument to this value (@fig-tmmidpoint-2). + +```{r} +#| label: tmmidpoint2 +#| eval: false +# mean(x$lifeExp, na.rm = TRUE) +tm_shape(worldvector) + + tm_polygons(fill = "life_expectancy", + fill.scale = tm_scale(midpoint = 73)) +``` + +```{r} +#| label: fig-tmmidpoint +#| warning: false +#| echo: false +#| layout-ncol: 2 +#| fig-cap: Examples of maps with different midpoints used +#| fig-subcap: +#| - the default sequential color palette +#| - the diverging color palette around the midpoint value of 73 +#| fig-height: 2 +<> +<> +``` + +Now the countries with low life expectancy are presented with purple colors, light gray areas represent countries with life expectancy around the median value (the `midpoint` in our case), and the countries with high life expectancy are represented by green colors. + +The above examples all contain several polygons with missing values of a given variable. +Objects with missing values are, by default, represented by gray color and a related legend label *Missing*. +However, it is possible to change this color with the `value.na` argument and its label with `label.na`. + +**tmap** has a special way to manually set colors for categorical maps. +It works by providing a named vector to the `values` argument. +In this vector, the names of the categories from the categorical variable are the vector names, and specified colors are the vector values. +You can see it in the example below, where we plot the `"wb_region"` categorical variable (@fig-tmcatpals). +Each category in this variable (e.g., `"South Asia"`) has a new, connected to it color (e.g., `"#df5454"`). + + +```{r} +#| label: fig-tmcatpals +#| warning: false +#| fig-cap: An example of a categorical map with manually selected colors. +#| fig-height: 2 +tm_shape(worldvector) + + tm_polygons("wb_region", + fill.scale = tm_scale(values = c( + "Latin America & Caribbean" = "#11467b", + "Europe & Central Asia" = "#ffd14d", + "Middle East & North Africa" = "#86909a", + "Sub-Saharan Africa" = "#14909a", + "East Asia & Pacific" = "#7fbee9", + "South Asia" = "#df5454", + "North America" = "#7b1072") + )) +``` + +```{r} +#| echo: false +#| eval: false +tm_shape(worldvector) + + tm_polygons(fill = "life_expectancy") +tm_shape(worldvector) + + tm_polygons(fill = "life_expectancy", fill_alpha = 0.5) +tm_shape(worldvector) + + tm_polygons(fill = "life_expectancy", + fill.scale = tm_scale(value.na = "purple", + label.na = "I do not know!")) +tm_shape(worldvector) + + tm_polygons(fill = "life_expectancy", + fill.scale = tm_scale(values.range = c(0.4, 1))) +``` + +\index{color palettes!transparency} +Finally, visualized colors can be additionally modified. +It includes setting the `col_alpha` and `fill_alpha` arguments that represents the transparency of the used colors. +By default, the colors are not transparent at all as the value of `col_alpha` and `fill_alpha` is 1. +However, we can decrease this value to 0 -- a total transparency. +The `col_alpha`/ `fill_alpha` argument is useful in two ways: one -- it allows us to see-through some large objects (e.g., some points below the polygons or a hillshade map behind the colored raster of elevation), second -- it makes colors more subtle. + + + + + + + + + + + + + +## Sizes {#sec-sizes} + +```{r} +ei_points = read_sf("data/easter_island/ei_points.gpkg") +volcanos = subset(ei_points, type == "volcano") +``` + +Differences in sizes between objects are relatively easy to recognize on maps. +Sizes can be used for points, lines (line widths), or text to represent quantitative (numerical) variables, where small values are related to small objects and large values are presented by large objects. +Large sizes can also be used to attract viewers' attention. + +By default, **t**maps present points, lines, or text objects of the same size. +For example, `tm_symbols()` returns a map where each object is a circle with a consistent size^[The default value of size is 1, which corresponds to the area of symbols that have the same height as one line of text.]. +We can change the sizes of all objects using the `size` argument (@fig-tmsizes-1). + +```{r} +#| label: tmsizes1 +#| eval: false +tm_shape(volcanos) + + tm_symbols(size = 0.5) +``` + +On the other hand, if we provide the name of the numerical variable in the `size` argument (e.g., `"elevation"`), then symbol sizes are scaled proportionally to the provided values. +Objects with small values will be represented by smaller circles, while larger values will be represented by larger circles (@fig-tmsizes-2). + +```{r} +#| label: tmsizes2 +#| eval: false +tm_shape(volcanos) + + tm_symbols(size = "elevation") +``` + + + + + + + +We can adjust size legend breaks with `ticks` and the corresponding labels with `labels` (@fig-tmsizes C) as arguments of the `tm_scale` function provided to the `size.scale` argument. + +```{r} +#| label: tmsizes3 +#| eval: false +tm_shape(volcanos) + + tm_symbols(size = "elevation", + size.scale = tm_scale(ticks = c(100, 600), + labels = c("low", "high"))) +``` + +For example in the above code, we just show examples of how symbols with elevation of 100 and 600 meters about see level like on the map. + +```{r} +#| label: fig-tmsizes +#| echo: false +#| message: false +#| layout-ncol: 3 +#| fig-cap: 'Examples of three approaches for changing sizes of symbols' +#| fig-subcap: +#| - all symbols have a consistent size of 0.5 +#| - sizes of symbols depend on the values of the elevation variable +#| - sizes of symbols have a manually created legend +#| #fig-asp: 0.42 +<> +<> +<> +``` + +Widths of the lines can represent values of numerical variables for line data similar to sizes of the symbols for point data. +The `lwd` argument in `tm_lines()` creates thin lines for small values and thick lines for large values of the given variable (@fig-tmlwd). + +```{r} +#| label: fig-tmlwd +#| fig-asp: 0.66 +#| fig-cap: Example of a map where lines' widths represent values of the corresponding +#| lines. +ei_roads = read_sf("data/easter_island/ei_roads.gpkg") +tm_shape(ei_roads) + + tm_lines(lwd = "strokelwd") +``` + +In the above example, values of the `"strokelwd"` are divided into four groups and represented by four line widths. +Lines' thickness can be change using the `values.scale` argument of `tm_scale()`, where the value of 1 is the default, and increasing this values increases lines' thickness. + + + + +```{r} +#| echo: false +#| eval: false +tm_shape(World_rivers) + + tm_lines(lwd = "strokelwd") +tm_shape(World_rivers) + + tm_lines(lwd = "strokelwd", lwd.scale = tm_scale(values.scale = 2)) +tm_shape(World_rivers) + + tm_lines(lwd = "strokelwd", + lwd.scale = tm_scale(values.scale = 2, n = 12)) +tm_shape(World_rivers) + + tm_lines(lwd = "strokelwd", + lwd.scale = tm_scale(values.scale = 2, + ticks = c(1, 2, 3, 5, 10, 15)), + lwd.legend = tm_legend(title = "Line legend", + labels = LETTERS[1:6])) +``` + +Text labels have a role in naming features on a map or just highlighting some of them. +Usually, the size of text labels is consistent for the same spatial objects. +However, text labels can also be used to represent the values of some numerical variables. +@fig-tmtextsize shows an example, in which text labels show names of different volcanos areas, while their sizes are related to their elevations. + + +```{r} +#| label: fig-tmtextsize +#| message: false +#| fig-asp: 0.25 +#| fig-cap: Example of a map where text sizes represent elevations of the volcanos. +tm_shape(volcanos) + + tm_text(text = "name", size = "elevation") + + tm_layout(legend.outside = TRUE) +``` + + + + + + + +## Shapes {#sec-shapes} + + + + +Shapes allow representing different categories of point data. +They can be very generic, e.g., circle or square, just to be able to differentiate between categories, but often we use symbols that we associate with different types of features. +For example, we use the letter *P* for parking lots, *I* for information centers, an airplane symbol for airports, or a bus symbol for bus stops. + +To use different shapes, we should use the `shape` argument in the `tm_symbols()` function. +It expects the name of the categorical variable. + +```{r} +#| label: tmsymshape1 +#| eval: false +tm_shape(ei_points) + + tm_symbols(shape = "type") +``` + + +By default, **tmap** uses symbols of filled circle, square, diamond, point-up triangle, and point-down triangle^[They are represented in R by numbers from 21 to 25.]. +However, it is also possible to customize used symbols, their title, and labels. +Legend titles related to shapes and their labels are modified with the `shape.legend` argument. + +Shapes can be specified with the `shapes` argument, which allows using one of three options. +The first one is a numeric value that specifies the plotting character of the symbol. +A complete list of available symbols and their corresponding numbers is in the `?pch` function's documentation. + + +```{r} +#| label: tmsymshape2 +#| eval: false +tm_shape(ei_points) + + tm_symbols(shape = "type", + shape.scale = tm_scale(values = c(0, 2, 5))) +``` + +The second option is to use a *grob* object. + + + +```{r} +# library(grid) +# library(ggplotify) +library(ggplot2) + +# p1 = as.grob(~barplot(1:10)) +# p2 = as.grob(expression(plot(rnorm(10), yaxt = "n", xaxt = "n", ann = FALSE, bty = "n"))) +# p3 = as.grob(function() plot(sin, yaxt = "n", xaxt = "n", ann = FALSE, bty = "n")) +p4 = ggplotGrob(ggplot(data.frame(x = 1:5, y = 1:5), aes(x, y)) + geom_point() + theme_void()) +``` + +```{r} +#| label: tmsymshape3 +#| eval: false +tm_shape(ei_points) + + tm_symbols(shape = "type", + shape.scale = tm_scale(values = list(p4, p4, p4))) +``` + + + + + + + +The last possibility is to use an icon specification created with the `tmap_icons()` function, that uses any png images. +The `tmap_icons()` function accepts a vector of file paths or urls, and also allows setting the width and height of the icon. +In our example, we have three distinct groups, therefore we need to create new icons based on three images -- `icon1.png`, `icon2.png`, and `icon3.png` in this case. + +```{r} +my_icons = tmap_icons(c("images/icon1.png", + "images/icon2.png", + "images/icon3.png")) +``` + +Now, we can use the prepared icons in the `shapes` argument (@fig-tmsymshape-4). + + +```{r} +#| label: tmsymshape4 +#| eval: false +tm_shape(ei_points) + + tm_symbols(shape = "type", + shape.scale = tm_scale(values = my_icons)) +``` + +```{r} +#| label: fig-tmsymshape +#| echo: false +#| message: false +#| layout-row: 4 +#| fig-asp: 1.32 +#| fig-cap: 'Examples of maps with different symbols' +#| fig-subcap: +#| - default symbols +#| - user-defined symbols +#| - grob objects +#| - icons +<> +<> +<> +<> +``` + +```{r} +#| label: tmlinlty +#| echo: false +#| eval: false +#| fig-asp: 0.33 +#| fig-cap: '' +tm_shape(World_rivers) + + tm_lines(lty = 2) +``` + +## Mixing visual variables {#sec-mixing-visual-variables} + +The values of a given variable can be expressed by different categorical or sequential colors in polygons. +Lines can be also colored by one variable, but also widths of the lines can represent values of another quantitative variable. +When we use symbols, then we are able to use colors for one qualitative or quantitative variable, sizes for a quantitative variable, and shapes for another qualitative variable. +Therefore, it is possible to mix some visual variables for symbols and lines. +This section shows only some possible examples of mixing visual variables. + +@fig-mixsymb-1 shows symbols, which sizes are scales based on the `sv` variable and they are colored using the values from `elevation`. +This can be set with the `size` and `fill` arguments. + +```{r} +#| label: mixsymb1 +#| eval: false +tm_shape(ei_points) + + tm_symbols(size = "sv", + fill = "elevation") +``` + +We can also modify all of the visual variables using the additional arguments explained in the next sections. +For example, we can set the color style, color palette, or specify shapes (@fig-mixsymb-2). + +```{r} +#| label: mixsymb2 +#| eval: false +tm_shape(ei_points) + + tm_symbols(fill = "elevation", + fill.scale = tm_scale(values = "Greens"), + shape = "type", + shape.scale = tm_scale(values = c(23, 24, 25))) +``` + +```{r} +#| label: fig-mixsymb +#| echo: false +#| message: false +#| layout-nrow: 2 +#| fig-asp: 1 +#| fig-cap: 'Examples of maps using two visual variables at the same time' +#| fig-subcap: +#| - size and fill +#| - fill and shape +<> +<> +``` + +For line data, we can present its qualitative and quantitative variables using colors and quantitative variables using sizes (line widths) (@fig-mixline). + +```{r} +#| label: fig-mixline +#| echo: false +#| message: false +#| fig-asp: 0.66 +#| fig-cap: A map using two visual variables, color, and size (line width), at the same time. +tm_shape(ei_roads) + + tm_lines(col = "type", + lwd = "strokelwd") +``` + +```{r} +#| eval: false +#| echo: false +tm_shape(ei_points) + + tm_symbols(fill = "elevation", + col = "elevation", + col.scale = tm_scale(values = "reds"), + col.legend = tm_legend_combine("fill")) +``` diff --git a/_bookdown.yml b/_bookdown.yml deleted file mode 100644 index 0d1893f..0000000 --- a/_bookdown.yml +++ /dev/null @@ -1,29 +0,0 @@ -book_filename: "tmap_book" -language: - label: - fig: "FIGURE " - tab: "TABLE " - ui: - edit: "Edit" - chapter_name: "Chapter " -new_session: yes #https://github.com/rstudio/bookdown/issues/15 -before_chapter_script: ["code/before_script.R"] -delete_merged_file: true -rmd_files: - - "index.Rmd" - - "01-intro.Rmd" - - "02-geodata.Rmd" - - "03-nutshell.Rmd" - - "04-tm-shape.Rmd" - - "05-layers.Rmd" - - "05b-visual-variables.Rmd" - - "06-other-types.Rmd" - - "07-layout.Rmd" - - "08-interactive.Rmd" - - "09-small-multiples.Rmd" - - "10-options.Rmd" - - "11-save.Rmd" - - "12-animations.Rmd" - - "13-shiny.Rmd" - - "14-good-maps.Rmd" - - "references.Rmd" diff --git a/_brand.yml b/_brand.yml new file mode 100644 index 0000000..c78af37 --- /dev/null +++ b/_brand.yml @@ -0,0 +1,2 @@ +color: + primary: "#73A7A8" \ No newline at end of file diff --git a/_build.sh b/_build.sh deleted file mode 100644 index 349b1f9..0000000 --- a/_build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -ev - -Rscript -e "bookdown::render_book('index.Rmd', 'bookdown::gitbook')" -Rscript -e "bookdown::render_book('index.Rmd', 'bookdown::pdf_book')" -Rscript -e "bookdown::render_book('index.Rmd', 'bookdown::epub_book')" - diff --git a/_output.yml b/_output.yml deleted file mode 100644 index 707f8c3..0000000 --- a/_output.yml +++ /dev/null @@ -1,23 +0,0 @@ -bookdown::gitbook: - css: style/style.css - config: - toc: - collapse: section - before: | -
  • tmap: elegant and effective thematic maps in R
  • - after: | -
  • Martijn Tennekes
  • -
  • Jakub Nowosad
  • - edit: - link: https://github.com/mtennekes/tmap_book/edit/master/%s - text: "Edit" - sharing: null - includes: - in_header: style/ga.html - after_body: style/after_body.html -bookdown::html_chapters: - css: [style/style.css, style/toc.css] -bookdown::pdf_book: - includes: - in_header: style/preamble.tex -keep_tex: yes diff --git a/_quarto.yml b/_quarto.yml new file mode 100644 index 0000000..918c7a5 --- /dev/null +++ b/_quarto.yml @@ -0,0 +1,88 @@ +project: + type: book + output-dir: docs + +execute: + cache: true + +book: + title: "Elegant and informative maps with **tmap**" + page-footer: "Elegant and informative maps with **tmap** by Martijn Tennekes and Jakub Nowosad." + # number-sections: true + author: "Martijn Tennekes and Jakub Nowosad" + description: | + This book teaches how to make elegant and informative maps with the R package tmap. + # cover-image: https://geocompx.org/static/img/book_cover_py.png + site-url: https://tmap.geocompx.org/ + repo-url: https://github.com/geocompx/tmap/ + date: now + date-format: iso + repo-branch: main + repo-actions: [edit] + # sharing: [twitter, facebook, linkedin] + google-analytics: G-ER2GMPEJNF + # favicon: favicon-32x32.png + chapters: + - "index.qmd" + - part: "Basics" + chapters: + - "XX-intro.qmd" + - "XX-geodata.qmd" + - "XX-nutshell.qmd" + - "XX-save.qmd" + - part: "Building blocks" + chapters: + - "XX-tm-shape.qmd" + - "XX-layers.qmd" + - "XX-visual-variables.qmd" + - "XX-scales.qmd" + - "XX-legends.qmd" + - "XX-attr-layers.qmd" + - "XX-layout.qmd" + - "XX-interactive.qmd" + - part: "Expanding the toolbox" + chapters: + - "XX-small-multiples.qmd" + - "XX-animations.qmd" + - "XX-shiny.qmd" + - "XX-other-types.qmd" + - part: "Advanced topics" + chapters: + - "XX-options.qmd" + - "XX-good-maps.qmd" + - "references.qmd" + appendices: + - "XX-data_processing.qmd" + +format: + html: + theme: [brand, flatly] + # linkcolor: "#73A7A8" + code-link: true + template-partials: [helpers/toc.html, helpers/title-block.html] + toc-title: "On this page" + code-overflow: wrap + toc-depth: 4 + # css: [helpers/mystyle.css] + # pdf: + # documentclass: krantz + # monofont: 'Source Code Pro' + # monofontoptions: + # - Scale=0.7 + # pdf-engine: xelatex + # keep-tex: true + # number-sections: true + # top-level-division: chapter + # include-before-body: helpers/before_body.tex + # include-in-header: + # text: | + # \AtBeginEnvironment{longtable}{\footnotesize} + # \usepackage{makeidx} + # \makeindex + # include-after-body: + # text: | + # \printindex + +bibliography: + - r-tmap.bib + - packages.bib diff --git a/code/before_script.R b/code/before_script.R index 81af501..0515e55 100644 --- a/code/before_script.R +++ b/code/before_script.R @@ -1,11 +1,11 @@ library(methods) library(webshot) -# knitr::knit_hooks$set(crop = knitr::hook_pdfcrop) + knitr::opts_chunk$set( background = "#FCFCFC", # code chunk color in latex comment = "#>", collapse = TRUE, - cache = TRUE, #https://github.com/rstudio/bookdown/issues/15#issuecomment-591478143 + # cache = TRUE, #https://github.com/rstudio/bookdown/issues/15#issuecomment-591478143 # fig.pos = "h", #"t" fig.path = "figures/", fig.align = "center", @@ -13,7 +13,7 @@ knitr::opts_chunk$set( fig.width = 6, fig.asp = 0.618, # 1 / phi fig.show = "hold", - out.width = "100%", + out.width = "100%", #70% dev.args = list(png = list(type = "cairo-png")), optipng = "-o1 -quiet", widgetframe_widgets_dir = "widgets", @@ -37,8 +37,11 @@ view_map = function(x, name){ tmap_save(x, tf) webshot2::webshot(tf, file = paste0("widgets/", name, ".png")) knitr::include_graphics(paste0("widgets/", name, ".png")) - } else if (knitr::is_html_output()){ - widgetframe::frameWidget(tmap::tmap_leaflet(x)) + } else { + # widgetframe::frameWidget(tmap::tmap_leaflet(x)) + # tmap::tmap_leaflet(x) + x } } +# Sys.setenv(CHROMOTE_CHROME = "/usr/bin/vivaldi") diff --git a/code/crs_examples.R b/code/crs_examples.R index eb8651a..e39050a 100644 --- a/code/crs_examples.R +++ b/code/crs_examples.R @@ -13,7 +13,7 @@ map_orange_world = function() { png("images/orange_world.png", width = 1213, height = 1213) - orange = png::readPNG("images/orange.png") %>% + orange = png::readPNG("images/orange.png") |> rasterGrob(interpolate=TRUE) grid.newpage() @@ -21,7 +21,7 @@ map_orange_world = function() { vp = grid::viewport(width = .84, height = .84, x = .48) print(tm_shape(World_ortho, bbox = ortho_bbx) + # tm_borders("grey30", lwd = 2) + - tm_polygons("red", border.col = "grey30", lwd = 4, alpha = .25) + + tm_polygons(fill = "red", col = "grey30", lwd = 4, fill_alpha = .25) + tm_graticules(x = seq(-180, 150, by = 30), y = seq(-90, 90, by = 30), labels.show = FALSE, lwd = 2) + tm_layout(frame = FALSE, bg.color = NA), vp = vp) @@ -58,11 +58,11 @@ map_goode = function() { 180 # close ) - bg <- list(cbind(longs, lats)) %>% - st_polygon() %>% + bg <- list(cbind(longs, lats)) |> + st_polygon() |> st_sfc( crs = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs" - ) %>% + ) |> st_transform(crs = crs_goode) land = st_intersection(land, bg) @@ -77,8 +77,8 @@ map_goode = function() { # # - grat = sf::st_graticule(lon = seq(-180, 150, by = 30), lat = seq(-90, 90, by = 30)) %>% - st_transform(crs = crs_goode) %>% + grat = sf::st_graticule(lon = seq(-180, 150, by = 30), lat = seq(-90, 90, by = 30)) |> + st_transform(crs = crs_goode) |> st_intersection(bg) list(bg = bg, land = land, grat = grat) @@ -97,9 +97,9 @@ map_4326 = function() { map_3857 = function() { data(World, package = "tmap") bbx = sf::st_bbox(c(xmin=-180,ymin=-85, xmax=180, ymax=85), crs = 4326) - land = World %>% - sf::st_transform(4326) %>% - st_crop(tmaptools::bb_earth(bbx = bbx)) %>% + land = World |> + sf::st_transform(4326) |> + st_crop(tmaptools::bb_earth(bbx = bbx)) |> sf::st_transform(3857) grat = sf::st_graticule(x = bbx, lon = seq(-180, 180, by = 30), lat = seq(-90, 90, by = 30)) bg = tmaptools::bb_earth(projection = 3857, bbx = bbx) @@ -128,21 +128,19 @@ map_4326_cyl = function() { c(lon_center + 90, -90), c(lon_center - 90, -90)))), crs = 4326) - - World_cyl = World %>% - st_transform(crs = 4326) %>% - st_intersection(crp) %>% + World_cyl = World |> + st_transform(crs = 4326) |> + st_intersection(crp) |> st_cast("MULTIPOLYGON") # ugly manual edits: - ant = World_cyl$geometry[World_cyl$name == "Antarctica"] - ant[[1]][[4]][[1]] = rbind(ant[[1]][[4]][[1]][1:262,], cbind(seq(101,-79, length.out = 30), rep(-90, 30)), ant[[1]][[4]][[1]][265:307,]) # add intermediate points to make the bottom curve (otherwise it would be a straight line) - World_cyl$geometry[World_cyl$name == "Antarctica"] = ant + # ant = World_cyl$geometry[World_cyl$name == "Antarctica"] + # ant[[1]][[4]][[1]] = rbind(ant[[1]][[4]][[1]][1:262,], cbind(seq(101,-79, length.out = 30), rep(-90, 30)), ant[[1]][[4]][[1]][265:307,]) # add intermediate points to make the bottom curve (otherwise it would be a straight line) + # World_cyl$geometry[World_cyl$name == "Antarctica"] = ant zaf = World_cyl$geometry[World_cyl$iso_a3 == "ZAF"] zaf[[1]][[1]] = zaf[[1]][[1]][1] # remove island of South-Africa, which caused problems. World_cyl$geometry[World_cyl$iso_a3 == "ZAF"] = zaf - World_cyl$geometry = st_sfc(lapply(World_cyl$geometry, function(g) { st_multipolygon(lapply(g, function(gi) { gi[[1]] = get_cylinder(gi[[1]], lon_center = lon_center) @@ -195,10 +193,10 @@ world_surface = function(datum = 4326, step = 2, nx = 360/step, ny = 180/step, p map_3035 = function() { data(World, package = "tmap") land = sf::st_transform(World, 3035) - grat = sf::st_graticule(lon = seq(-180, 180, by = 30), lat = seq(-90, 90, by = 30)) %>% st_transform(3035) + grat = sf::st_graticule(lon = seq(-180, 180, by = 30), lat = seq(-90, 90, by = 30)) |> st_transform(3035) # background approximation using many graticules - grat2 = sf::st_graticule(lon = seq(-180, 180, by = 1), lat = seq(-90, 90, by = 1)) %>% st_transform(3035) + grat2 = sf::st_graticule(lon = seq(-180, 180, by = 1), lat = seq(-90, 90, by = 1)) |> st_transform(3035) co = st_coordinates(grat2) xrange = range(co[,1]) yrange = range(co[,2]) @@ -221,10 +219,10 @@ map_eqdc = function() { crs = "+proj=eqdc +lat_0=0 +lon_0=0 +lat_1=60 +lat_2=60 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m no_defs" data(World, package = "tmap") - land = World %>% filter(iso_a3 != "ATA") %>% sf::st_transform(crs) + land = World |> filter(iso_a3 != "ATA") |> sf::st_transform(crs) bg = tmaptools::bb_earth(crs) - grat = sf::st_graticule(lon = seq(-180, 180, by = 30), lat = seq(-90, 90, by = 30)) %>% st_transform(crs) + grat = sf::st_graticule(lon = seq(-180, 180, by = 30), lat = seq(-90, 90, by = 30)) |> st_transform(crs) list(bg = bg, land = land, grat = grat) } @@ -259,14 +257,14 @@ map_3857_cyl = function() { c(lon_center + 20037508/2, -20037508), c(lon_center - 20037508/2, -20037508)))), crs = 3857) - World_cyl = m3857$land %>% - st_intersection(crp) %>% + World_cyl = m3857$land |> + st_intersection(crp) |> st_cast("MULTIPOLYGON") # ugly manual edits: - ant = World_cyl$geometry[World_cyl$name == "Antarctica"] - ant[[1]][[4]][[1]] = rbind(ant[[1]][[4]][[1]][1:262,], cbind(seq(11243268.38,-8794239.62, length.out = 30), rep(-20037508, 30)), ant[[1]][[4]][[1]][265:307,]) # add intermediate points to make the bottom curve (otherwise it would be a straight line) - World_cyl$geometry[World_cyl$name == "Antarctica"] = ant + # ant = World_cyl$geometry[World_cyl$name == "Antarctica"] + # ant[[1]][[4]][[1]] = rbind(ant[[1]][[4]][[1]][1:262,], cbind(seq(11243268.38,-8794239.62, length.out = 30), rep(-20037508, 30)), ant[[1]][[4]][[1]][265:307,]) # add intermediate points to make the bottom curve (otherwise it would be a straight line) + # World_cyl$geometry[World_cyl$name == "Antarctica"] = ant # zaf = World_cyl$geometry[World_cyl$iso_a3 == "ZAF"] # zaf[[1]][[1]] = zaf[[1]][[1]][1] # remove island of South-Africa, which caused problems. # World_cyl$geometry[World_cyl$iso_a3 == "ZAF"] = zaf diff --git a/code/data_model_figures.R b/code/data_model_figures.R index 5a07012..75f238d 100644 --- a/code/data_model_figures.R +++ b/code/data_model_figures.R @@ -190,39 +190,43 @@ draw_vector_data = function(scale = 1) { print({ tm_shape(sf_pnts, bbox = c(0, 0, 12.931, 10)) + - tm_dots(size = 0.2, col = "cols") + + tm_dots(size = 0.5, fill = "cols") + tm_text("ID", xmod = .5, ymod = .5) + - tm_layout(scale = scale, inner.margins = 0, outer.margins = 0) + tm_layout(scale = scale, inner.margins = 0, outer.margins = 0, + bg.color = "white") }, vp = viewport(layout.pos.row = 2:3, layout.pos.col = 2)) print({ tm_shape(sf_lns, bbox = c(0, 0, 12.931, 10)) + tm_lines(lwd = 2, col = "cols") + - tm_text("ID", xmod = .5, ymod = c(.5, 1, .5)) + + tm_text("ID", xmod = .5, ymod = .5) + tm_shape(sf_lns_pnts) + - tm_dots(size = 0.2, col = "cols") + - tm_layout(scale = scale, inner.margins = 0, outer.margins = 0) + tm_dots(size = 0.2, fill = "cols") + + tm_layout(scale = scale, inner.margins = 0, outer.margins = 0, + bg.color = "white") }, vp = viewport(layout.pos.row = 5:6, layout.pos.col = 2)) print({ tm_shape(sf_plg, bbox = c(0, 0, 12.931, 10), point.per = "segment") + - tm_polygons(lwd = 2, col = "cols", border.col = "grey30") + - tm_text("ID", col = "white") + + tm_polygons(lwd = 2, fill = "cols", col = "grey30") + + tm_text("ID", col = "white", + options = opt_tm_text(on_surface = TRUE, point_per = "segment")) + tm_shape(sf_plg_pnts) + tm_dots(size = 0.2, col = "grey30") + - tm_layout(scale = scale, inner.margins = 0, outer.margins = 0) + tm_layout(scale = scale, inner.margins = 0, outer.margins = 0, + bg.color = "white") }, vp = viewport(layout.pos.row = 8:9, layout.pos.col = 2)) cellplot(2, 4, e = { - draw_table(sf_pnts %>% st_drop_geometry() %>% select(ID, name, has, evergreen), col_rows = cols, scale = scale) + draw_table(sf_pnts |> st_drop_geometry() |> select(ID, name, has, evergreen), col_rows = cols, scale = scale) }) cellplot(5:6, 4, e = { - draw_table(sf_lns %>% st_drop_geometry() %>% select(ID, name, lanes, cycling), col_rows = cols, scale = scale) + draw_table(sf_lns |> st_drop_geometry() |> select(ID, name, lanes, cycling), col_rows = cols, scale = scale) }) cellplot(8, 4, e = { - draw_table(sf_plg %>% st_drop_geometry() %>% select(ID, name, population, touristic), col_rows = cols, scale = scale) + draw_table(sf_plg |> st_drop_geometry() |> select(ID, name, population, touristic), col_rows = cols, scale = scale) }) cellplot(1, 4, e = { @@ -253,16 +257,16 @@ draw_vector_cubes = function() { data(NLD_prov) - lu = read.csv("data/lan_use_ovw.tsv", sep = "\t", na.strings = ": ") %>% - rename(cat = 1) %>% - {suppressWarnings(separate(., cat, into = c("unit", "landuse", "geo")))} %>% + lu = read.csv("data/lan_use_ovw.tsv", sep = "\t", na.strings = ": ") |> + rename(cat = 1) %>% + {suppressWarnings(separate(., cat, into = c("unit", "landuse", "geo")))} |> filter(geo %in% paste0("NL", c(11:13, 21:23, 31:34, 41,42)), unit == "PC", - landuse %in% c("LUA", "LUB", "LUC", "LUD", "LUE", "LUF")) %>% - mutate(x1 = ifelse(is.na(X2009), ifelse(is.na(X2012), X2015, X2012), X2009)) %>% - mutate(x2 = ifelse(is.na(X2015), ifelse(is.na(X2012), X2009, X2012), X2015)) %>% + landuse %in% c("LUA", "LUB", "LUC", "LUD", "LUE", "LUF")) |> + mutate(x1 = ifelse(is.na(X2009), ifelse(is.na(X2012), X2015, X2012), X2009)) |> + mutate(x2 = ifelse(is.na(X2015), ifelse(is.na(X2012), X2009, X2012), X2015)) |> mutate(x1 = as.numeric(gsub("u", "", x1, fixed = TRUE)), - x2 = as.numeric(gsub("u", "", x2, fixed = TRUE))) %>% + x2 = as.numeric(gsub("u", "", x2, fixed = TRUE))) |> mutate(landuse2 = case_when(landuse %in% "LUA" ~ "Crops", landuse %in% "LUB" ~ "Forest", landuse %in% "LUC" ~ "Water", @@ -278,29 +282,29 @@ draw_vector_cubes = function() { geo == "NL33" ~ "Zuid-Holland", geo == "NL34" ~ "Zeeland", geo == "NL41" ~ "Noord-Brabant", - geo == "NL42" ~ "Limburg")) %>% - group_by(name, landuse2) %>% + geo == "NL42" ~ "Limburg")) |> + group_by(name, landuse2) |> summarize(x1 = sum(x1), x2 = sum(x2)) - lu1 = lu %>% - pivot_wider(id = name, names_from = landuse2, values_from = x1) %>% - replace_na(list(Water = 0)) %>% - mutate(Urban = 100 - Crops - Forest - Water) %>% - arrange(match(name, NLD_prov$name)) %>% - ungroup() %>% - mutate(name = NULL) %>% - select(Urban, Crops, Forest, Water) %>% + lu1 = lu |> + pivot_wider(id_cols = name, names_from = landuse2, values_from = x1) |> + replace_na(list(Water = 0)) |> + mutate(Urban = 100 - Crops - Forest - Water) |> + arrange(match(name, NLD_prov$name)) |> + ungroup() |> + mutate(name = NULL) |> + select(Urban, Crops, Forest, Water) |> as.matrix() - lu2 = lu %>% - pivot_wider(id = name, names_from = landuse2, values_from = x2) %>% - replace_na(list(Water = 0)) %>% - mutate(Urban = 100 - Crops - Forest - Water) %>% - arrange(match(name, NLD_prov$name)) %>% - ungroup() %>% - mutate(name = NULL) %>% - select(Urban, Crops, Forest, Water) %>% + lu2 = lu |> + pivot_wider(id_cols = name, names_from = landuse2, values_from = x2) |> + replace_na(list(Water = 0)) |> + mutate(Urban = 100 - Crops - Forest - Water) |> + arrange(match(name, NLD_prov$name)) |> + ungroup() |> + mutate(name = NULL) |> + select(Urban, Crops, Forest, Water) |> as.matrix() k = 8 diff --git a/code/generate_book_stats.R b/code/generate_book_stats.R index 8beca54..222fa9e 100644 --- a/code/generate_book_stats.R +++ b/code/generate_book_stats.R @@ -12,7 +12,7 @@ generate_book_stats = function(dir = ".") { chapters = lapply(rmd_files, readLines) chapters = lapply(chapters, function(x) tibble(line = 1:length(x), text = x)) - # chapters[[1]] %>% + # chapters[[1]] |> # unnest_tokens(words, text) n_words = sapply(chapters, function(x) nrow(unnest_tokens(x, words, text))) diff --git a/code/visual_variables.R b/code/visual_variables.R index 5d594d0..3708ff0 100644 --- a/code/visual_variables.R +++ b/code/visual_variables.R @@ -2,7 +2,7 @@ visual_variables = function() { library(tmap) library(grid) - data(World, rivers, metro, land) + data(World, World_rivers, metro, land) # adds heading and trailing spaces to make legend labels wider make_wider = function(x, n = 5) paste0(paste(rep(" ", n), collapse = ""), x, paste(rep(" ", n), collapse = "")) @@ -20,8 +20,8 @@ visual_variables = function() { World$x = runif(nrow(World), 0, num_max) World$y = rep(categories, length.out = nrow(World)) - rivers$x = runif(nrow(rivers), 0, num_max) - rivers$y = rep(categories, length.out = nrow(rivers)) + World_rivers$x = runif(nrow(World_rivers), 0, num_max) + World_rivers$y = rep(categories, length.out = nrow(World_rivers)) metro$x = runif(nrow(metro), 0, num_max) metro$y = rep(categories, length.out = nrow(metro)) @@ -29,20 +29,58 @@ visual_variables = function() { tml = tm_layout(legend.only = TRUE, legend.text.size = tmap_label_size) lf = list(scientific = TRUE, format = "f") - x11 = tm_shape(metro) + tm_dots("y", size = tmap_data_size, palette = cat_palette, title = "", legend.is.portrait = FALSE, legend.format = lf) + tml - x12 = tm_shape(metro) + tm_dots("x", size = tmap_data_size, style = "pretty", title = "", legend.is.portrait = FALSE, legend.format = lf) + tml - - x13 = tm_shape(metro) + tm_symbols(size = "x", scale = 1.5, sizes.legend = sizes_legend, sizes.legend.labels = make_wider(sizes_legend, 2), title.size = "", legend.size.is.portrait = FALSE, legend.format = lf) + tml - x14 = tm_shape(metro) + tm_symbols(shape = "y", size = tmap_data_size, title.shape = "", legend.shape.is.portrait = FALSE, legend.format = lf) + tml + x11 = tm_shape(metro) + tm_dots(fill = "y", + fill.scale = tm_scale(values = cat_palette, + label.format = lf), + fill.legend = tm_legend(orientation = "landscape", + title = ""), + size = tmap_data_size) + tml + x12 = tm_shape(metro) + tm_dots(fill = "x", + fill.scale = tm_scale(label.format = lf), + fill.legend = tm_legend(orientation = "landscape", + title = ""), + size = tmap_data_size) + tml + + x13 = tm_shape(metro) + tm_symbols(size = "x", + size.scale = tm_scale(label.format = lf), + size.legend = tm_legend(orientation = "landscape", + title = "")) + tml + + x14 = tm_shape(metro) + tm_symbols(shape = "y", + shape.scale = tm_scale(label.format = lf), + shape.legend = tm_legend(orientation = "landscape", + title = ""), + size = tmap_data_size) + tml + + x21 = tm_shape(World_rivers) + tm_lines(col = "y", + col.scale = tm_scale(values = cat_palette, + label.format = lf), + col.legend = tm_legend(orientation = "landscape", + title = ""), + lwd = tmap_data_lwd) + tml - x21 = tm_shape(rivers) + tm_lines(col = "y", lwd = tmap_data_lwd, palette = cat_palette, title.col = "", legend.col.is.portrait = FALSE, legend.format = lf) + tml - x22 = tm_shape(rivers) + tm_lines(col = "x", lwd = tmap_data_lwd, style = "pretty", title.col = "", legend.col.is.portrait = FALSE, legend.format = lf) + tml - - x23 = tm_shape(rivers) + tm_lines(lwd = "x", scale = tmap_data_lwd * 1.5, lwd.legend = sizes_legend, lwd.legend.labels = make_wider(sizes_legend, 2), title.lwd = "", legend.lwd.is.portrait = FALSE, legend.format = lf) + tml - - x31 = tm_shape(World) + tm_polygons("y", palette = cat_palette, title = "", legend.is.portrait = FALSE, legend.format = lf) + tml - x32 = tm_shape(World) + tm_polygons("x", style = "pretty", title = "", legend.is.portrait = FALSE, legend.format = lf) + tml - + x22 = tm_shape(World_rivers) + tm_lines(col = "x", + col.scale = tm_scale(label.format = lf), + col.legend = tm_legend(orientation = "landscape", + title = ""), + lwd = tmap_data_lwd) + tml + + x23 = tm_shape(World_rivers) + tm_lines(lwd = "x", + lwd.scale = tm_scale(label.format = lf, + values.scale = tmap_data_lwd * 1.5), + lwd.legend = tm_legend(orientation = "landscape", + title = "")) + tml + + x31 = tm_shape(World) + tm_polygons(fill = "y", + fill.scale = tm_scale(values = cat_palette, + label.format = lf), + fill.legend = tm_legend(orientation = "landscape", + title = "")) + tml + + x32 = tm_shape(World) + tm_polygons(fill = "x", + fill.scale = tm_scale(label.format = lf), + fill.legend = tm_legend(orientation = "landscape", + title = "")) + tml cellplot = function(row, col, e) { pushViewport(viewport(layout.pos.row = row, layout.pos.col = col)) @@ -50,6 +88,7 @@ visual_variables = function() { upViewport(1) } + # tmap_options(component.autoscale = FALSE) grid.newpage() @@ -78,4 +117,4 @@ visual_variables = function() { cellplot(1, 3, grid.text("Lines", just = "left", x = 0.1, gp = gpar(cex = label_size))) cellplot(1, 4, grid.text("Polygons", just = "left", x = 0.1, gp = gpar(cex = label_size))) -} \ No newline at end of file +} diff --git a/style/before_body.tex b/helpers/before_body.tex similarity index 64% rename from style/before_body.tex rename to helpers/before_body.tex index 8d4c07b..3ca5b1c 100644 --- a/style/before_body.tex +++ b/helpers/before_body.tex @@ -1,10 +1,13 @@ +% you may need to leave a few empty pages before the dedication page + %\cleardoublepage\newpage\thispagestyle{empty}\null %\cleardoublepage\newpage\thispagestyle{empty}\null %\cleardoublepage\newpage \thispagestyle{empty} -\begin{center} -% \includegraphics{images/dedication.pdf} -\end{center} + +\vspace*{\fill} + +% dedications separated by \vspace*{2cm} \setlength{\abovedisplayskip}{-5pt} \setlength{\abovedisplayshortskip}{-5pt} diff --git a/helpers/title-block.html b/helpers/title-block.html new file mode 100644 index 0000000..bbf2311 --- /dev/null +++ b/helpers/title-block.html @@ -0,0 +1,31 @@ +
    +
    +

    $title$

    +$if(subtitle)$ +

    $subtitle$

    +$endif$ +$if(categories)$ + $if(quarto-template-params.title-block-categories)$ +
    + $for(categories)$ +
    $categories$
    + $endfor$ +
    + $endif$ +$endif$ +
    + + + +$title-metadata.html()$ + +
    \ No newline at end of file diff --git a/helpers/toc.html b/helpers/toc.html new file mode 100644 index 0000000..1687788 --- /dev/null +++ b/helpers/toc.html @@ -0,0 +1,13 @@ + diff --git a/index.Rmd b/index.qmd similarity index 57% rename from index.Rmd rename to index.qmd index fcb322a..3832e30 100644 --- a/index.Rmd +++ b/index.qmd @@ -1,38 +1,9 @@ ---- -title: "Elegant and informative maps with **tmap**" -author: "Martijn Tennekes, Jakub Nowosad" -date: "`r Sys.Date()`" -description: "This book teaches how to make elegant and informative maps with the R package tmap." -knit: bookdown::render_book -site: bookdown::bookdown_site -output: bookdown::gitbook -documentclass: krantz -monofont: "Source Code Pro" -monofontoptions: "Scale=0.7" -bibliography: [r-tmap.bib, packages.bib] -biblio-style: apalike -link-citations: yes -github-repo: r-tmap/tmap-book -url: "https://r-tmap.github.io/tmap-book/" -cover-image: "" -colorlinks: yes -graphics: yes -links-as-notes: true ---- - -```{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() -``` -```{asis index-2, echo=is_html} -# Welcome {-} -This is the online home of *Elegant and informative maps with **tmap***, a work-in-progress book on geospatial data visualization with the R-package [tmap](https://github.com/mtennekes/tmap). -``` +::: {.content-visible when-format="html"} +# Welcome {.unnumbered} +This is the online home of *Elegant and informative maps with **tmap***, a work-in-progress book on geospatial data visualization with the R-package [tmap](https://github.com/r-tmap/tmap). -```{asis index-2-3, echo=is_html} ## How to contribute? {-} We encourage contributions on any part of the book, including: @@ -41,15 +12,18 @@ We encourage contributions on any part of the book, including: - changes to the code - suggestions on content (see [the project’s issue tracker](https://github.com/r-tmap/tmap-book/issues)) -``` - -```{asis index-2-4, echo=is_html} ## Additional information {-} Creative Commons License
    This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. -``` -```{r index-2-2, echo=FALSE, eval=is_html, results="asis"} +Creative Commons License
    The code samples in this book are licensed under Creative Commons CC0 1.0 Universal (CC0 1.0). + +```{r} +#| echo: false +#| results: asis +is_on_ghactions = identical(Sys.getenv("GITHUB_ACTIONS"), "true") +is_online = curl::has_internet() +is_html = knitr::is_html_output() if (is_on_ghactions){ cat(paste0("This version of the book was built on GH Actions on ", Sys.Date(), ".")) } else { @@ -57,4 +31,4 @@ if (is_on_ghactions){ } ``` - +::: diff --git a/meta/book-outline.Rmd b/meta/book-outline.Rmd index 4a7fdb8..cdead48 100644 --- a/meta/book-outline.Rmd +++ b/meta/book-outline.Rmd @@ -28,7 +28,7 @@ knitr::opts_chunk$set(echo = TRUE) 1. which data to use *polygons/points/lines/raster;classes;where to get them?* 2. CRS: how to project the earth on a flat screen *written to arouse curiosity and to understand the basic concept* 3. simplification *about simplifying polygons and downsampling rasters. In the viz community, people are often afraid to throw away information, while more information can be communicated with less detailed data (e.g. the coastline of Norway)* - 4. multiple datasets *stacking, is.master* + 4. multiple datasets *stacking, is.main* 5. Layer functions 1. overview *table of layer functions and their aesthetics* 2. polygons diff --git a/meta/book-style.Rmd b/meta/book-style.Rmd index b539670..354ca8c 100644 --- a/meta/book-style.Rmd +++ b/meta/book-style.Rmd @@ -28,7 +28,7 @@ knitr::opts_chunk$set(echo = TRUE) - `"text"` - double quotes for character values - `for (i in 1:9) {print(i)}` - use space separation for curly brackets. Also use space after `for`. - When indenting your code, use two spaces (tab in RStudio) -- When using pipes, pipe the first object (`x %>% fun() %>% ...` not `fun(x) %>% ...`) +- When using pipes, pipe the first object (`x |> fun() |> ...` not `fun(x) |> ...`) # Comments diff --git a/r-tmap.bib b/r-tmap.bib index a656c1d..334cd23 100644 --- a/r-tmap.bib +++ b/r-tmap.bib @@ -1,3 +1,12 @@ +@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} +} @article{birch_worldwide_2012, title = {Worldwide Prevalence of Red-Green Color Deficiency}, diff --git a/references.Rmd b/references.Rmd deleted file mode 100644 index 4e5a594..0000000 --- a/references.Rmd +++ /dev/null @@ -1,3 +0,0 @@ -`r if (knitr:::is_html_output()) ' -# References {-} -'` diff --git a/references.qmd b/references.qmd new file mode 100644 index 0000000..4cdf317 --- /dev/null +++ b/references.qmd @@ -0,0 +1,4 @@ +# References {.unnumbered} + +::: {#refs} +::: \ No newline at end of file diff --git a/sandbox/outline.txt b/sandbox/outline.txt index 9037e82..afe71ef 100644 --- a/sandbox/outline.txt +++ b/sandbox/outline.txt @@ -27,7 +27,7 @@ Basics of tmap - plot and view mode Specifying the data with tm_shape -- projection (CRS, is.master) +- projection (CRS, is.main) - bounding box - simplification? - raster: downsample etc diff --git a/sandbox/tmap4-ideas.md b/sandbox/tmap4-ideas.md index d841df8..cd90b2b 100644 --- a/sandbox/tmap4-ideas.md +++ b/sandbox/tmap4-ideas.md @@ -15,20 +15,20 @@ output: # Annotations and text improvements -1. Improving labels (text) locations - see **ggrepel** (https://cran.r-project.org/web/packages/ggrepel/vignettes/ggrepel.html) (also https://github.com/mtennekes/tmap/issues/337) +1. Improving labels (text) locations - see **ggrepel** (https://cran.r-project.org/web/packages/ggrepel/vignettes/ggrepel.html) (also https://github.com/r-tmap/tmap/issues/337) 2. Adding a new layer type - annotations (see how it works in **ggplot2** - https://ggplot2.tidyverse.org/reference/annotate.html) -3. Fonts improvements, including rotating (see https://github.com/mtennekes/tmap/issues/389) +3. Fonts improvements, including rotating (see https://github.com/r-tmap/tmap/issues/389) # Aesthetics -1. Make aesthetics extensible, but also more consistent (some ideas are already at https://github.com/mtennekes/tmap/issues/495#issuecomment-732189644) +1. Make aesthetics extensible, but also more consistent (some ideas are already at https://github.com/r-tmap/tmap/issues/495#issuecomment-732189644) # Legends 1. Unify legends API for inside and outside legends. Currently, there is a subset of settings for inside and a subset of settings for outside legends. 2. Improved specifying of horizontal and vertical legends. -Currently, horizontal legends are harder to customize (e.g., https://github.com/mtennekes/tmap/issues/350). +Currently, horizontal legends are harder to customize (e.g., https://github.com/r-tmap/tmap/issues/350). 3. (Additionally) Allow for changing positions of many legends separately # Attributes layers @@ -38,14 +38,14 @@ Currently, horizontal legends are harder to customize (e.g., https://github.com/ # Improve plots alignment 1. Allowing to convert **tmap** objects into `grob`s to make easier plot alignments and map insets (with **cowplot** or **gridExtra**). -More info at https://github.com/mtennekes/tmap/issues/338. +More info at https://github.com/r-tmap/tmap/issues/338. # Other 1. Revise default colors, sizes, etc. -1. Replace the `is.master` by `is.main` +1. Replace the `is.main` by `is.main` 1. Decide on the **tmap** hex logo -1. Bivariate/multivariate color palettes (https://github.com/mtennekes/tmap/issues/183) -1. Add possibility to fill polygons with pattern (https://github.com/mtennekes/tmap/issues/49) +1. Bivariate/multivariate color palettes (https://github.com/r-tmap/tmap/issues/183) +1. Add possibility to fill polygons with pattern (https://github.com/r-tmap/tmap/issues/49) 1. Close GitHub issues that are not going to be fix/implemented in the foreseeable future (declutter issue tracker) 1. Delete old git branches diff --git a/style/after_body.html b/style/after_body.html deleted file mode 100644 index e9ad279..0000000 --- a/style/after_body.html +++ /dev/null @@ -1,13 +0,0 @@ - \ No newline at end of file diff --git a/style/after_body.tex b/style/after_body.tex deleted file mode 100644 index 72a0faa..0000000 --- a/style/after_body.tex +++ /dev/null @@ -1,2 +0,0 @@ -\backmatter -\printindex diff --git a/style/ga.html b/style/ga.html deleted file mode 100644 index 27870f2..0000000 --- a/style/ga.html +++ /dev/null @@ -1,9 +0,0 @@ - - - \ No newline at end of file diff --git a/style/preamble.tex b/style/preamble.tex deleted file mode 100644 index a1c1b5e..0000000 --- a/style/preamble.tex +++ /dev/null @@ -1,93 +0,0 @@ -\usepackage{booktabs} -\usepackage{longtable} -\usepackage[bf,singlelinecheck=off]{caption} -\usepackage{tabu} -\usepackage{graphicx} - -% \setmainfont[UprightFeatures={SmallCapsFont=AlegreyaSC-Regular}]{Alegreya} - -\usepackage{framed,color} -\definecolor{shadecolor}{RGB}{255,255,255} - -\renewcommand{\textfraction}{0.05} -\renewcommand{\topfraction}{0.8} -\renewcommand{\bottomfraction}{0.8} -\renewcommand{\floatpagefraction}{0.75} - -\renewenvironment{quote}{\begin{VF}}{\end{VF}} -\let\oldhref\href -\renewcommand{\href}[2]{#2\footnote{\url{#1}}} - -\ifxetex - \usepackage{letltxmacro} - \setlength{\XeTeXLinkMargin}{1pt} - \LetLtxMacro\SavedIncludeGraphics\includegraphics - \def\includegraphics#1#{% #1 catches optional stuff (star/opt. arg.) - \IncludeGraphicsAux{#1}% - }% - \newcommand*{\IncludeGraphicsAux}[2]{% - \XeTeXLinkBox{% - \SavedIncludeGraphics#1{#2}% - }% - }% -\fi - -\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 - -\makeatletter -\@ifundefined{Shaded}{ -}{\renewenvironment{Shaded}{\begin{kframe}}{\end{kframe}}} -\makeatother - -\newenvironment{rmdblock}[1] - { - \begin{itemize} - \renewcommand{\labelitemi}{ - \raisebox{-.7\height}[0pt][0pt]{ - {\setkeys{Gin}{width=3em,keepaspectratio}\includegraphics{images/#1}} - } - } - \setlength{\fboxsep}{1em} - \begin{kframe} - \item - } - { - \end{kframe} - \end{itemize} - } -\newenvironment{rmdinfo} - {\begin{rmdblock}{compass}} - {\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 diff --git a/style/style.css b/style/style.css deleted file mode 100644 index 6beba39..0000000 --- a/style/style.css +++ /dev/null @@ -1,33 +0,0 @@ -p.caption { - color: #777; - margin-top: 10px; -} -p code { - white-space: inherit; -} -pre { - word-break: normal; - word-wrap: normal; -} -pre code { - white-space: inherit; -} -p.flushright { - text-align: right; -} -blockquote > p:last-child { - text-align: right; -} -blockquote > p:first-child { - text-align: inherit; -} - -/*adds blocks*/ -.rmdinfo { - padding: 1em 1em 1em 4em; - margin-bottom: 10px; - background: #f5f5f5 5px center/3em no-repeat; -} -.rmdinfo { - background-image: url("../images/compass.png"); -} \ No newline at end of file