10 Overlaying 2D rayshader
Maps
In this chapter, we will explore in more detail how we can add overlays to rayshader
two-dimensional maps such as water feature overlays, map tiles or stage routes.
10.1 Load in Base Data
As ever, let’s load in our stage data:
= 'montecarlo_2021.geojson'
geojson_filename = sf::st_read(geojson_filename) geojson_sf
## Reading layer `montecarlo_2021' from data source `/Users/tonyhirst/Documents/GitHub/visualising-rally-stages/montecarlo_2021.geojson' using driver `GeoJSON'
## Simple feature collection with 9 features and 2 fields
## geometry type: LINESTRING
## dimension: XY
## bbox: xmin: 5.243488 ymin: 43.87633 xmax: 6.951953 ymax: 44.81973
## geographic CRS: WGS 84
And also load in the elevation data raster:
library(raster)
library(rayshader)
# Previously downloaded TIF digital elevation model (DEM) file
= "stage_elevation.tif"
stage_tif
# Load in the previously saved image raster
= raster(stage_tif)
elev_img
# Note we can pass in a file name or a raster object
= raster_to_matrix(stage_tif) elmat
10.2 Adding overlays
The rayshader
package includes various tools for styling the rendering of the 2d image.
10.2.1 Adding Shadows to rayshader
Maps
It can often be convenient to construct shadow layers for a particular rayshader
map that we can add to the rendering as required:
# Add shade
<- ray_shade(elmat, zscale = auto_zscale, lambert = TRUE)
raymat # And "ambient occlusion shadow"
<- ambient_shade(elmat, zscale = auto_zscale) ambmat
The shading functions accept sunaltitude
and sunangle
paramters to specify the sun / light source position. The suncalc
R package can be used to determine sun altitude and and angle settings for a particular date and time at a specified location.
We can also create a composite shadow layer constructed from several shadow operators piped together:
# More shadow style from:
# https://www.tylermw.com/adding-open-street-map-data-to-rayshader-maps-in-r/
# We can build up layers that can be generated once then added
# as overlays to construct views from basic parts
= height_shade(elmat) %>%
shadow_layer add_overlay(sphere_shade(elmat, texture = "desert",
zscale=auto_zscale, colorintensity = 5), alphalayer=0.5) %>%
add_shadow(lamb_shade(elmat, zscale = auto_zscale),0) %>%
add_shadow(texture_shade(elmat,detail=8/10,contrast=9,brightness = 11), 0.1)
The layers can be added to the rayshader
map in the normal way:
# Add water
<- detect_water(elmat, zscale = 8)
watermap
= elmat %>%
demo_map add_overlay(shadow_layer) %>%
add_water(watermap, color = "desert")
%>%
demo_map plot_map()
10.2.1.1 Retrieving Shadow Data Along a Route
If we can access just the shadow layer data, one interesting possibility is that we could produced a “shadow depth” profile of a route identifying which bit are in shadow at a particular time and date.
10.2.2 Adding Water Features
We can detect water (and even modify the height of the water to model raising water levels) to create a layer that we can later add to the rendered map.
# Add water
<- detect_water(elmat, zscale = 8) watermap
Let’s add that layer to the 2d map:
# Texture palettes:
# `imhof1`,`imhof2`,`imhof3`,`imhof4`,`bw`,`desert`, and `unicorn`
%>%
elmat #sphere_shade(texture = "imhof1") %>%
# Optionally set sun angle
sphere_shade(sunangle = -45, texture = "imhof1") %>%
add_water(watermap, color = "bw") %>%
plot_map()
10.2.3 Overlaying Map Tiles
We can also add tiles as an overlay.
For example, the geoviz
package provides a way for us to retrieve popular map tilesets and then overlay them onto the rayshader
map.
Let’s grab some tiles covering the extent of our original raster image and create overlays from them:
# via https://github.com/neilcharles/geoviz
library(geoviz)
# stamen: toner, watercolor, terrain
<-
overlay_image_watercolor slippy_overlay(elev_img,
image_source = "stamen",
image_type = "watercolor",
png_opacity = 0.5)
<-
overlay_image_tonerlite slippy_overlay(elev_img,
image_source = "stamen",
image_type = "toner",
png_opacity = 0.5)
<-
overlay_image_terrain slippy_overlay(elev_img,
image_source = "stamen",
image_type = "terrain",
png_opacity = 0.9)
We can now overlay a tileset onto the rendered map.
In the following example, notice how I have further separated out the map creation phase from the map plotting phase:
<- elmat %>%
scene sphere_shade(sunangle = 270, texture = "desert") %>%
add_overlay(overlay_image_watercolor)
%>%
scene plot_map()
10.2.3.1 Setting Elevation Sensitive Tile Transparancy
We can also specify different elevations at which we want to apply an overlay as a transparency.
For example, we can turn mountainous parts of the overlay transparent, showing the tiles overlaid on the lower altitude areas but not on the higher regions, which in this case are rendered with a black and white texture:
<-
overlay_image_tonerlite_low_alt elevation_transparency(overlay_image_terrain,
elev_img,pct_alt_high = 0.5,
alpha_max = 0.9)
<- elmat %>%
scene sphere_shade(sunangle = 270, texture = "bw") %>%
add_overlay(overlay_image_tonerlite_low_alt) %>%
plot_map()
10.3 Adding a Stage Route Layer
When adding overlays, we ofter need to specify the extent of the area covered by the overlay.
For our stage route, we can take the extent directly from the geojson loaded data:
library(sf)
extent(st_bbox(geojson_sf[1,]))
## class : Extent
## xmin : 5.883753
## xmax : 5.940715
## ymin : 44.73562
## ymax : 44.81973
We can then create a route layer as a line overlay:
= generate_line_overlay(geojson_sf[1,],
yellow_route extent = extent(geojson_sf[1,]),
heightmap = elmat,
linewidth = 5, color="yellow")
We can add the line overlay to our map to show the path followed by the stage route:
= elmat %>%
mapped_route_yellow sphere_shade(sunangle = -45, texture = "bw") %>%
#add_water(watermap, color = "bw") %>%
add_overlay(yellow_route)
%>%
mapped_route_yellow plot_map()
The geoviz::add_gps_to_rayshader()
can also add a route to a rayshader
model from longitude, latitude and elevation vectors.
10.3.1 Buffering the Stage Route View
One problem with that view is that the route extent borders on the edge of the rendered image. It would be easier to see the route if the extent of the image was increased to provide a bordered area around the route.
We can create a “buffered boundary box” around the stage extent as follows:
library(spatialEco)
= SpatialPoints(extent(geojson_sf[1,]))
bb_sp proj4string(bb_sp) <- st_crs(geojson_sf[1,])$proj4string
# Create a new, buffered region
= st_bbox(geo.buffer(x=bb_sp, r=500))
stage_bbox_buffered stage_bbox_buffered
## xmin ymin xmax ymax
## 5.877431 44.731117 5.947037 44.824231
We can then use those extended bounds to retrieve a buffered elevation raster image:
<- data.frame(x= c(stage_bbox_buffered[['xmin']],
ex.df2 'xmax']]),
stage_bbox_buffered[[y= c(stage_bbox_buffered[['ymin']],
'ymax']]))
stage_bbox_buffered[[
library(elevatr)
# The zoom level, z, impacts on how long it takes to download the imagery
# z ranges from 1 to 14
# https://www.rdocumentation.org/packages/elevatr/versions/0.3.4/topics/get_elev_raster
<- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"
prj_dd <- get_elev_raster(ex.df2, prj = prj_dd, z = 12, clip = "bbox") elev_img2
## Mosaicing & Projecting
## Clipping DEM to bbox
## Note: Elevation units are in meters.
## Note: The coordinate reference system is:
## GEOGCRS["unknown",
## DATUM["World Geodetic System 1984",
## ELLIPSOID["WGS 84",6378137,298.257223563,
## LENGTHUNIT["metre",1]],
## ID["EPSG",6326]],
## PRIMEM["Greenwich",0,
## ANGLEUNIT["degree",0.0174532925199433],
## ID["EPSG",8901]],
## CS[ellipsoidal,2],
## AXIS["longitude",east,
## ORDER[1],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]],
## AXIS["latitude",north,
## ORDER[2],
## ANGLEUNIT["degree",0.0174532925199433,
## ID["EPSG",9122]]]]
# Save out the buffered image
= "buffered_stage_elevation.tif"
stage_tif # Write the data to an elevation data raster tif
::writeRaster(elev_img2, stage_tif, overwrite= TRUE)
raster
= raster_to_matrix(elev_img2) elmat_buffered
To create a buffered yellow route, we generate the line overlay with an extend matching that of the buffered image and the heightmap associated with that image:
= generate_line_overlay(geojson_sf[1,],
yellow_route_b extent = extent(elev_img2),
heightmap = elmat_buffered,
linewidth = 5, color="yellow")
Plotting the buffered route in the buffered image now provides us with a marginal border around the edge of the route:
= elmat_buffered %>%
mapped_route_yellow sphere_shade(sunangle = -45, texture = "bw") %>%
add_overlay(yellow_route_b) %>%
plot_map()
10.4 Modeling Increased Water Levels
The rayshader::plot_3d()
function takes a water_depth parameter that allows us to model changing water levels. This is not really relevant to rally route analysis and visualisation, although it may be of interest in other contexts (eg raising sea levels, historical views over land that as since been reclaimed from the sea, dam bursts, etc.)