--- title: "Data Maps" author: "Mark Padgham" date: "`r Sys.Date()`" output: html_document: toc: true toc_float: true theme: flatly vignette: > %\VignetteIndexEntry{Data Maps} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- This vignette extends from the vignette ([Basic maps](https://cran.r-project.org/package=osmplotr)) to demonstrate how `osmplotr` enables the graphical properties of OpenStreetMap objects to be modified according to user-provided data. Categorical data can be plotted by highlighting defined regions with different colours using `add_osm_groups`, while continuous data can be plotted with `add_osm_surface`. ```{r load, message = FALSE, eval = TRUE} library (osmplotr) ``` ```{r, echo = FALSE, message = FALSE} map_dpi <- 72 # dpi res for all maps ``` As in the first vignette, maps produced in this vignette contain data for a small portion of central London, U.K. ```{r} bbox <- get_bbox (c (-0.13, 51.51, -0.11, 51.52)) ``` ```{r, echo = FALSE, message= FALSE} dat_B <- rbind (london$dat_BR, london$dat_BNR) dat_H <- rbind (london$dat_H, london$dat_HP) dat_HP <- london$dat_HP ``` # 1. Categorical data: `add_osm_groups` The function `add_osm_groups` enables spatially-defined groups to be plotted in different colours. The two primary arguments are `obj`, which defines the OSM structure to be used for plotting the regions, and `groups` which is a list of geometric coordinates defining the desired regions. An example of an `obj` is the Simple Features ([`sf`](https://cran.r-project.org/package=sf)) `data.frame` of building polygons downloaded in the first vignette with the following line ```{r, eval = FALSE} dat_B <- extract_osm_objects (key = "building", bbox = bbox) ``` These data may be obtained by simply combining the data provided with the package of residential and non-residential buildings to give all buildings as ```{r} dat_B <- rbind (london$dat_BNR, london$dat_BR) ``` The most direct way to define `groups` is through specifying coordinates of boundary points: ```{r map1, eval = FALSE} pts <- cbind ( c (-0.115, -0.125, -0.125, -0.115), c (51.513, 51.513, 51.517, 51.517) ) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = pts, cols = "orange", bg = "gray40" ) print_osm_map (map) ``` ```{r map1-print, echo = FALSE} pts <- cbind ( c (-0.115, -0.125, -0.125, -0.115), c (51.513, 51.513, 51.517, 51.517) ) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = pts, cols = "orange", bg = "gray40" ) print_osm_map (map, filename = "map_b1.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b1.png) Multiple groups can be defined by passing a list of multiple sets of point coordinates to the `groups` argument of `add_osm_groups`, and specifying corresponding colours. ```{r map2, eval = FALSE} pts2 <- cbind ( c (-0.111, -0.1145, -0.1145, -0.111), c (51.517, 51.517, 51.519, 51.519) ) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato"), bg = "gray40" ) print_osm_map (map) ``` ```{r map2-print, echo = FALSE} pts2 <- cbind ( c (-0.111, -0.1145, -0.1145, -0.111), c (51.517, 51.517, 51.519, 51.519) ) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato"), bg = "gray40" ) print_osm_map (map, filename = "map_b2.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b2.png) The `bg` argument specifies the colour of any objects lying outside the boundaries of the specified groups. If this argument is not given, then all objects are assigned to the nearest group, so that the groups fill the entire map. ```{r map3, eval = FALSE} map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato") ) print_osm_map (map) ``` ```{r map3-print, echo = FALSE} map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato") ) print_osm_map (map, filename = "map_b3.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b3.png) Now that you've seen the general workflow of `osmplotr`, let's repeat the previous code, but streamline it with `magrittr`'s `%>%` function. This allows us to pipe the functions together instead of re-assigning the `map` variable. ```{r map_b3_pipe, eval = FALSE} library (magrittr) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato") ) %>% print_osm_map () ``` ```{r map_b3_pipe-print, echo = FALSE} library (magrittr) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) map <- add_osm_groups (map, dat_B, groups = list (pts, pts2), cols = c ("orange", "tomato") ) print_osm_map (map, filename = "map_b3_pipe.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b3_pipe.png) ## 1.1 Hulls around groups `add_osm_groups` includes the argument `make_hull` which specifies whether convex hulls should be fitted around the points defining the provided `groups`, or whether the `groups` already define their own boundaries (the default behaviour). If a point is added internal to the four points defining the first of the above groups, then the group boundary will connect to that point and create a concave shape. ```{r map5, eval = FALSE} pts <- rbind (pts, c (-0.12, 51.515)) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = pts, cols = "orange", bg = "gray40" ) %>% print_osm_map () ``` ```{r map5-print, echo = FALSE} pts <- rbind (pts, c (-0.12, 51.515)) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = pts, cols = "orange", bg = "gray40" ) %>% print_osm_map ( filename = "map_b5.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b5.png) The previous points started in the south-east and ended in the north-east, and thus the concave boundary extends in between the two easterly points. Setting `make_hull = TRUE` defines groups by the convex hulls surrounding them, which in this case would revert this map to the initial map with the group defined by a regular, convex perimeter. ## 1.2 Inclusive, exclusive, and bisected polygons The highlighted regions of the previous maps are irregular because the default behaviour of `add_osm_groups` is to include within a group only those OSM objects which lie entirely within a group boundary. `add_osm_groups` has a `boundary` argument which defines whether objects should be assigned to groups inclusively (`boundary > 0`) or exclusively (`boundary < 0`), or whether they should be precisely bisected by a group boundary (`boundary = 0`). The previous maps illustrate the default option (`boundary = -1`), while the two other options produce the following maps. ```{r map6, eval = FALSE} osm_basemap (bbox = bbox, bg = "gray20") %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 1 ) %>% print_osm_map () ``` ```{r map6-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 1 ) %>% print_osm_map ( filename = "map_b6.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b6.png) The inclusive option (`boundary>0`) includes all objects which have any points lying within a boundary, meaning more objects are included resulting in larger regions than the previous default exclusive option. Precisely bisecting boundaries produces the following map. ```{r map7, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) %>% print_osm_map () ``` ```{r map7-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) %>% print_osm_map ( filename = "map_b7.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b7.png) The ability to combine different kinds of boundaries is particularly useful when highlighting areas which partially contain large polygons such as parks. The parks within the following maps were downloaded with ```{r, eval = FALSE} dat_P <- extract_osm_objects (key = "park", bbox = bbox) ``` (Noting that, as described in the first vignette, [Basic maps](https://cran.r-project.org/package=osmplotr), both `extract_osm_objects` and `make_osm_map` convert several common keys to appropriate `key-value` pairs, so ```{r} osm_structures (structure = "park") ``` reveals that this `key` is actually converted to `key = "leisure"` and `value = "park"`.) These data are also provided with the package as `london$dat_P`. ```{r, echo = FALSE} dat_P <- london$dat_P ``` Plotting buildings inclusively within each group and overlaying parks bisected by the group boundaries produces the following map: ```{r map8, eval = FALSE} col_park_in <- rgb (50, 255, 50, maxColorValue = 255) col_park_out <- rgb (50, 155, 50, maxColorValue = 255) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) %>% add_osm_groups (dat_P, groups = list (pts, pts2), cols = rep (col_park_in, 2), bg = col_park_out, boundary = 0 ) %>% print_osm_map () ``` ```{r map8-print, echo = FALSE} col_park_in <- rgb (50, 255, 50, maxColorValue = 255) col_park_out <- rgb (50, 155, 50, maxColorValue = 255) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) %>% add_osm_groups (dat_P, groups = list (pts, pts2), cols = rep (col_park_in, 2), bg = col_park_out, boundary = 0 ) %>% print_osm_map ( filename = "map_b8.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b8.png) Bisection divides single polygons to form one polygon of points lying within a given boundary and one polygon of points lying outside the boundary. The two resultant polygons are often separated by visible gaps between locations at which they are defined. Because the layers of a plot are progressively overlaid, such gaps can be avoided by initially plotting underlying layers using `add_osm_objects` prior to grouping objects: ```{r map9, eval = FALSE} map <- osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_objects (dat_P, col = col_park_out ) %>% add_osm_groups (dat_P, groups = list (pts, pts2), cols = rep (col_park_in, 2), bg = col_park_out, boundary = 0 ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) map %>% print_osm_map () ``` ```{r map9-print, echo = FALSE} map <- osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_objects (dat_P, col = col_park_out ) %>% add_osm_groups (dat_P, groups = list (pts, pts2), cols = rep (col_park_in, 2), bg = col_park_out, boundary = 0 ) %>% add_osm_groups (dat_B, groups = list (pts, pts2), make_hull = TRUE, cols = c ("orange", "tomato"), bg = "gray40", boundary = 0 ) map %>% print_osm_map ( filename = "map_b9.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b9.png) Bisections with `boundary = 0` will only be as accurate as the underlying OSM data. This example was chosen to highlight that bisection may be inaccurate if actual OSM points do not lie near to a desired bisection line. The larger a map, the less visually evident are likely to be any such inaccuracies. Finally, note that the plot order was changed to allow the building within the park to be overlaid upon the grass surfaces. Plot order, whether controlled manually or with `make_osm_map`, may often have to be tweaked to appropriately visualise all objects. The `boundary` argument has no effect if `bg` is not given, because in this case all objects will be assigned to a group and there will be no boundaries between groups and other, non-grouped objects. ## 1.3 Adjusting colours with `adjust_colours` The `adjust_colours` function allows different groups to be highlighted with slightly different colours for different kinds of OSM objects. For example, the following code adds highways to the above map in slightly darkened versions of the highlight colours (using `boundary = 1`, so any highways with any points lying within the bounding box are included in the groups): ```{r map10, eval = FALSE} # create separate data for all highways and primary highways dat_H <- rbind (london$dat_H, london$dat_HP) dat_HP <- london$dat_HP # darken colours by aboud 20% cols_adj <- adjust_colours (c ("orange", "tomato"), adj = -0.2 ) map %>% add_osm_groups (dat_HP, groups = list (pts, pts2), make_hull = TRUE, cols = cols_adj, bg = adjust_colours ("gray40", adj = -0.4 ), boundary = 1, size = 2 ) %>% add_osm_groups (dat_H, groups = list (pts, pts2), make_hull = TRUE, cols = cols_adj, bg = adjust_colours ("gray40", adj = -0.2 ), boundary = 1, size = 1 ) %>% print_osm_map () ``` ```{r map10-print, echo = FALSE} # darken colours by aboud 20% cols_adj <- adjust_colours (c ("orange", "tomato"), adj = -0.2 ) map %>% add_osm_groups (dat_HP, groups = list (pts, pts2), make_hull = TRUE, cols = cols_adj, bg = adjust_colours ("gray40", adj = -0.4 ), boundary = 1, size = 2 ) %>% add_osm_groups (dat_H, groups = list (pts, pts2), make_hull = TRUE, cols = cols_adj, bg = adjust_colours ("gray40", adj = -0.2 ), boundary = 1, size = 1 ) %>% print_osm_map ( filename = "map_b10.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b10.png) And of course `adjust_colours ("gray40", adj = -0.2)` is nothing other than "gray32", and `adj = -0.4` gives "gray24". ## 1.4 Dark-on-Light Highlights A particularly effective way to highlight single regions within a map is through using dark colours upon otherwise light coloured maps. ```{r map11, eval = FALSE} osm_basemap (bbox = bbox, bg = "gray95") %>% add_osm_groups (dat_B, groups = pts, cols = "gray40", bg = "gray85", boundary = 1 ) %>% add_osm_groups (dat_H, groups = pts, cols = "gray20", bg = "gray70", boundary = 0 ) %>% add_osm_groups (dat_HP, groups = pts, cols = "gray10", bg = "white", boundary = 0, size = 1 ) %>% print_osm_map () ``` ```{r map11-print, echo = FALSE} osm_basemap (bbox = bbox, bg = "gray95") %>% add_osm_groups (dat_B, groups = pts, cols = "gray40", bg = "gray85", boundary = 1 ) %>% add_osm_groups (dat_H, groups = pts, cols = "gray20", bg = "gray70", boundary = 0 ) %>% add_osm_groups (dat_HP, groups = pts, cols = "gray10", bg = "white", boundary = 0, size = 1 ) %>% print_osm_map ( filename = "map_b11.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b11.png) ## 1.5 Visualising clustering data One of the most likely uses of `add_osm_groups` is to visualise statistical clusters. Clustering algorithms will generally produce membership lists which may be mapped onto spatial locations. Each cluster can be defined as a matrix of points in a single list of `groups`. A general approach is illustrated here with `groups` defined by single, randomly generated points. ```{r} set.seed (2) ngroups <- 12 x <- bbox [1, 1] + runif (ngroups) * diff (bbox [1, ]) y <- bbox [2, 1] + runif (ngroups) * diff (bbox [2, ]) groups <- as.list (data.frame (t (cbind (x, y)))) ``` (The last line just transforms each row of the matrix into a list item.) Having generated the points, a map of corresponding clusters can be generated by the following simple code. ```{r map12, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray95" ) %>% add_osm_groups (dat_B, groups = groups, cols = rainbow (length (groups)) ) %>% print_osm_map () ``` ```{r map12-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray95" ) %>% add_osm_groups (dat_B, groups = groups, cols = rainbow (length (groups)) ) %>% print_osm_map ( filename = "map_b12.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b12.png) Although individual groups will generally be defined by collections of multiple points, this example illustrates that they can also be defined by single points. In such cases, the `bg` option should of course be absent, so that all remaining points are allocated to the nearest groups. This map also illustrates the kind of visual mess that may arise in attempts to specify colours, particularly because the sequence of colours passed to `add_osm_groups` will generally not map on to any particular spatial order, so even if a pleasing colour scheme is submitted, the results may still be less than desirable. Although it may be possible to devise pleasing schemes for small numbers of groups, manually defined colour schemes are likely to become impractical for larger numbers of groups. ```{r map13, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray95" ) %>% add_osm_groups (dat_B, groups = groups, border_width = 2, cols = heat.colors (length (groups)) ) %>% print_osm_map () ``` ```{r map13-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray95" ) %>% add_osm_groups (dat_B, groups = groups, border_width = 2, cols = heat.colors (length (groups)) ) %>% print_osm_map ( filename = "map_b13.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b13.png) Note the submitting any positive values to the additional `border_width` argument causes `add_osm_groups` to drawn convex hull borders around the different groups. Even this is not sufficient, however, to render the result particularly visually pleasing or intelligible. To overcome this, `add_osm_groups` includes an option described in the following section to generate spatially sensible colour schemes for colouring distinct groups. ## 1.6 The Colour Matrix: Colouring Several Regions An additional argument which may be passed to `add_osm_groups` is `colmat`, an abbreviation of 'colour matrix'. If set to true (the default is `FALSE`), group colours are specified by the function `colour_mat`. This function takes a vector of four or more colours as input, wraps them around the four corners of a rectangular grid, and spatially interpolates a chromatically regular grid between these corners. To visual different schemes, it has a `plot` argument: ```{r, eval = FALSE} cmat <- colour_mat (rainbow (4), plot = TRUE) ``` ```{r, fig.width = 4, echo = FALSE} plot.new () cmat <- colour_mat (rainbow (4), plot = TRUE) ``` This grid illustrates the default colours, `rainbow (4)`. The two-dimensional colour field produced by `colour_mat` may also be rotated by a specified number of degrees using the `rotate` argument. ```{r, eval = FALSE} cmat <- colour_mat (rainbow (4), n = c (4, 8), rotate = 90, plot = TRUE) ``` ```{r, fig.width = 4, echo = FALSE} plot.new () cmat <- colour_mat (rainbow (4), n = c (4, 8), rotate = 90, plot = TRUE) ``` This example also illustrates that the size of colour matrices may also be arbitrarily specified. Using the `colmat` option in `add_osm_groups` enables the previous maps to be redrawn like this: ```{r map14, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray95" ) %>% add_osm_groups (dat_B, groups = groups, border_width = 2, colmat = TRUE, cols = c ("red", "green", "yellow", "blue"), rotate = 180 ) %>% print_osm_map () ``` ```{r map14-print, echo = FALSE} osm_basemap (bbox = bbox, bg = "gray95") %>% add_osm_groups (dat_B, groups = groups, border_width = 2, colmat = TRUE, cols = c ("red", "green", "yellow", "blue"), rotate = 180 ) %>% print_osm_map ( filename = "map_b14.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b14.png) Note both that when `add_osm_groups` is called with `colmat = TRUE`, then `cols` need only be of length 4, to specify the four corners of the colour matrix, and also that the `rotate` argument can be submitted to `add_osm_groups` and passed on to `colour_mat`. ## 1.7 Bounding areas within named highways As explained in the first vignette, [Basic maps](https://cran.r-project.org/package=osmplotr), the function `connect_highways` takes a list of OSM highway names and a bounding box, and returns the boundary of a polygon encircling the named highways. This can be used to highlight selected regions simply by naming the highways which encircle them, producing maps which look like this: ```{r connect-highways, eval = FALSE} highways <- c ( "Monmouth.St", "Short.?s.Gardens", "Endell.St", "Long.Acre", "Upper.Saint.Martin" ) highways1 <- connect_highways (highways = highways, bbox = bbox) highways <- c ("Endell.St", "High.Holborn", "Drury.Lane", "Long.Acre") highways2 <- connect_highways (highways = highways, bbox = bbox) highways <- c ("Drury.Lane", "High.Holborn", "Kingsway", "Great.Queen.St") highways3 <- connect_highways (highways = highways, bbox = bbox) ``` Note the use of the [regex](https://en.wikipedia.org/wiki/Regular_expression) character `?` in the first list of highway names, denoting the previous character as optional. This is necessary here because there are OSM sections named both "Shorts Gardens" and "Short's Gardens". ```{r, echo = FALSE} load (system.file ("extdata", "hwys.rda", package = "osmplotr")) highways1 <- hwys [[1]] highways2 <- hwys [[2]] highways3 <- hwys [[3]] con_h <- function (hwy) { hwy <- osmplotr:::connect_single_ways (hwy) hwy <- osmplotr:::get_highway_cycle (hwy) conmat <- osmplotr:::get_conmat (hwy) cycles <- try (ggm::fundCycles (conmat), TRUE) cyc <- cycles [[which.max (sapply (cycles, nrow))]] osmplotr:::sps_through_cycle (hwy, cyc) } highways1 <- con_h (highways1) highways2 <- con_h (highways2) highways3 <- con_h (highways3) ``` ```{r} class (highways1) nrow (highways1) nrow (highways2) nrow (highways3) ``` `connect_highways` returns a list of `SpatialPoints` representing the shortest path that sequentially connects all of the listed highways. (Connecting all listed highways may not necessarily be possible, in which case warnings will be issued. As described in the first vignette, [Basic maps](https://cran.r-project.org/package=osmplotr), `connect_highways` also has a `plot` option allowing problematic cases to be visually inspected and hopefully corrected.) These lists of highway coordinates can then be used to highlight the areas they encircle. First group the highways and establish a colour scheme for the map: ```{r, echo = TRUE} groups <- list (highways1, highways2, highways3) cols_B <- c ("red", "orange", "tomato") # for the 3 groups cols_H <- adjust_colours (cols_B, -0.2) bg_B <- "gray40" bg_H <- "gray60" ``` And then plot the map. ```{r map15, eval = FALSE} osm_basemap (bbox = bbox, bg = "gray20") %>% add_osm_objects (dat_P, col = col_park_out ) %>% add_osm_groups (dat_B, groups = groups, boundary = 1, bg = bg_B, cols = cols_B ) %>% add_osm_groups (dat_H, groups = groups, boundary = 1, bg = bg_H, cols = cols_H ) %>% add_osm_groups (dat_HP, groups = groups, boundary = 0, cols = cols_H, bg = bg_H, size = 1 ) %>% print_osm_map () ``` ```{r map15-print, echo = FALSE} osm_basemap (bbox = bbox, bg = "gray20") %>% add_osm_objects (dat_P, col = col_park_out ) %>% add_osm_groups (dat_B, groups = groups, boundary = 1, bg = bg_B, cols = cols_B ) %>% add_osm_groups (dat_H, groups = groups, boundary = 1, bg = bg_H, cols = cols_H ) %>% add_osm_groups (dat_HP, groups = groups, boundary = 0, cols = cols_H, bg = bg_H, size = 1 ) %>% print_osm_map ( filename = "map_b15.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b15.png) These encircling highways are included in the `london` data provided with `osmplotr`. # 2. Continuous data: `add_osm_surface` The `add_osm_surface` function enables a continuous data surface to be overlaid on a map. User-provided data is spatially interpolated across a map region and OSM items coloured according to a specified continuous colour gradient. The data must be provided as a data frame with three columns, '(x,y,z)', where '(x,y)' are the coordinates of points at which data are given, and 'z' are the values to be spatially interpolated across the map. A simple data frame can be constructed as ```{r} n <- 5 x <- seq (bbox [1, 1], bbox [1, 2], length.out = n) y <- seq (bbox [2, 1], bbox [2, 2], length.out = n) dat <- data.frame ( x = as.vector (array (x, dim = c (n, n))), y = as.vector (t (array (y, dim = c (n, n)))), z = x * y ) head (dat) ``` And then passed to `add_osm_surface` ```{r map16, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% print_osm_map () ``` ```{r map16-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% print_osm_map ( filename = "map_b16.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b16.png) At present, `add_osm_surface` generates an warning if it is applied more than once to any one kind of `Spatial` object (polygons or lines), as illustrated in the following code (in which both `dat_H` and `dat_HP` are of class `SpatialLinesDataFrame`: ```{r, eval = TRUE} osm_basemap (bbox = bbox, bg = "gray20") %>% add_osm_surface (dat_HP, dat = dat, cols = heat.colors (30) ) %>% add_osm_surface (dat_H, dat = dat, cols = heat.colors (30) ) ``` This is because `add_osm_surface` creates new `ggplot2` aesthetic schemes for each kind of object, and these schemes are not intended to be modified or replaced within a single plot. The above map may still be printed, but the warning means that the last provided colour scheme will be applied to all objects of that class. This means that `osmplotr` can only overlay two distinct colour schemes: one for all objects of class `SpatialLines`, and a potentially different one for all objects of class `SpatialPolygons`. Of course, any number of additional objects may be overlaid with `add_osm_objects`, for example, ```{r map17, eval = FALSE} cols_adj <- adjust_colours (heat.colors (30), -0.2) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = 1.5 ) %>% add_osm_objects (dat_P, col = rgb (0.1, 0.3, 0.1) ) %>% add_osm_objects (dat_H, col = "gray60" ) map %>% print_osm_map () ``` ```{r map17-print, echo = FALSE} cols_adj <- adjust_colours (heat.colors (30), -0.2) map <- osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = 1.5 ) %>% add_osm_objects (dat_P, col = rgb (0.1, 0.3, 0.1) ) %>% add_osm_objects (dat_H, col = "gray60" ) map %>% print_osm_map ( filename = "map_b17.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b17.png) ## 2.1 Colourbar legends for data surfaces A colourbar legend for the surface may be added with `add_colourbar`. As with `add_axes`, this function is provided separately to allow colourbars to be overlaid only after all desired map items have been added. The only parameters required for `add_colourbar` are the limits of the data (`zlims`) and the colours (along with the `map`, a modified version of which is returned). ```{r map18, eval = FALSE} map %>% add_colourbar ( cols = terrain.colors (100), zlims = range (dat$z) ) %>% print_osm_map () ``` ```{r map18-print, echo = FALSE} map %>% add_colourbar ( cols = terrain.colors (100), zlims = range (dat$z) ) %>% print_osm_map ( filename = "map_b18.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b18.png) Note that the colours submitted to `add_colourbar` need not be the same as those used to plot the surface. (Although using different colours is rarely likely to be useful.) As for `add_axes`, and explained in the first vignette, [Basic maps](https://cran.r-project.org/package=osmplotr), the transparency of the boxes surrounding the elements of the colourbar may be controlled by specifying the value of `alpha`. Both alignment and position may also be adjusted, as illustrated in this example. ```{r map19, eval = FALSE} cols_adj <- adjust_colours (heat.colors (30), -0.2) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = 1.5 ) %>% add_colourbar ( cols = heat.colors (100), zlims = range (dat$z), alpha = 0.9, vertical = FALSE, barwidth = c (0.1, 0.12), barlength = c (0.5, 0.9), text_col = "blue", fontsize = 5, fontface = 3, fontfamily = "Times" ) %>% add_axes ( colour = "blue", fontsize = 5, fontface = 3, fontfamily = "Times" ) %>% print_osm_map () ``` ```{r map19-print, echo = FALSE} cols_adj <- adjust_colours (heat.colors (30), -0.2) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30) ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = 1.5 ) %>% add_colourbar ( cols = heat.colors (100), zlims = range (dat$z), alpha = 0.9, vertical = FALSE, barwidth = c (0.1, 0.12), barlength = c (0.5, 0.9), text_col = "blue", fontsize = 5, fontface = 3, fontfamily = "Times" ) %>% add_axes ( colour = "blue", fontsize = 5, fontface = 3, fontfamily = "Times" ) %>% print_osm_map ( filename = "map_b19.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b19.png) Both `barwidth` and `barlength` can be specified in terms of one or two numbers. A single value for `barwidth` determines its relative width (0-1) from the border (the right side if `vertical = TRUE` or the top if `vertical = FALSE`), while two values determine the relative start and end positions of the sides of the bar. A single value for `barlength` produces a bar of the given length centred in the middle of the map, while two values determine its respective upper and lower points (for `vertical = TRUE`) or left and right points (for `vertical = FALSE`). This example also demonstrates how colours, sizes, and other font characteristics of text labels can be specified (with `text_col` determining the colour of all elements of the colourbar other than the gradient itself). Finally, as for `add_axes`, the text labels of colourbars are not currently able to be rotated because `ggplot2` does not permit rotation for the `geom_label` function used to produce these labels. ## 2.1 Surfaces and data perimeters It may often be that user-provided data only extend across a portion of a map, leaving a perimeter beyond the data boundary for which interpolation should not be applied. `add_osm_surface` has a `bg` parameter specifying a background colour for objects beyond the perimeter of the data surface. Passing this parameter to `add_osm_surface` causes objects beyond the data perimeter to be coloured within this 'background' colour. To illustrate, trim the above data to within a circular range of the centre of the map. ```{r} d <- sqrt ((dat$x - mean (dat$x))^2 + (dat$y - mean (dat$y))^2) range (d) ``` Remove from `dat` all rows translating to `d>0.01`: ```{r map20, eval = FALSE} dat <- dat [which (d < 0.01), ] cols_adj <- adjust_colours (heat.colors (30), -0.2) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30), bg = "gray40" ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = c (1.5, 0.5), bg = "gray70" ) %>% print_osm_map () ``` ```{r map20-print, echo = FALSE} dat <- dat [which (d < 0.01), ] cols_adj <- adjust_colours (heat.colors (30), -0.2) osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_B, dat = dat, cols = heat.colors (30), bg = "gray40" ) %>% add_osm_surface (dat_HP, dat = dat, cols = cols_adj, size = c (1.5, 0.5), bg = "gray70" ) %>% print_osm_map ( filename = "map_b20.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b20.png) (The perimeter is irregular because of the positions of the points in `dat`.) ## 2.3 Further control of surface appearance The final `add_osm_surface` call in the above code (for `dat_HP`) illustrates additional parameters that may be passed for further control of map appearance. In this case, the two `size` parameters control the size of the lines within the data surface and beyond its perimeter. Single values may also be passed, in which case they determine the width of lines in both cases. One or two `shape` parameters may also be passed, with these also determining the shapes of `SpatialPoints`, as illustrated in the next example, which overlays trees on the map. Both lines and points use the same `ggplot2` colour gradient, and so adding the second of these again generates an error and means that the actual colour scheme will be determined by the final call to add either lines or points. ```{r, eval = FALSE} dat_T <- extract_osm_objects (key = "tree", bbox = bbox) ``` ```{r, echo = FALSE} dat_T <- london$dat_T ``` ```{r map21, eval = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_HP, dat = dat, cols = terrain.colors (30), size = c (1.5, 0.5), bg = "gray70" ) %>% add_osm_surface (dat_H, dat = dat, cols = terrain.colors (30), size = c (1, 0.5), bg = "gray70" ) %>% add_osm_surface (dat_T, dat = dat, cols = heat.colors (30), bg = "lawngreen", size = c (3, 2), shape = c (8, 1) ) %>% print_osm_map () ``` ```{r map21-print, echo = FALSE} osm_basemap ( bbox = bbox, bg = "gray20" ) %>% add_osm_surface (dat_HP, dat = dat, cols = terrain.colors (30), size = c (1.5, 0.5), bg = "gray70" ) %>% add_osm_surface (dat_H, dat = dat, cols = terrain.colors (30), size = c (1, 0.5), bg = "gray70" ) %>% add_osm_surface (dat_T, dat = dat, cols = heat.colors (30), bg = "lawngreen", size = c (3, 2), shape = c (8, 1) ) %>% print_osm_map ( filename = "map_b21.png", width = 600, units = "px", dpi = map_dpi ) ``` ![](map_b21.png) The first two colour specifications (`terrain.colors`) have been ignored, and all added items are coloured according to the final value of `heat.colors (30)`. Other aspects such as line sizes and point shapes are nevertheless respected.