Creating 2D/3D visuals with R has never been this easy. Thanks to the Rayshader package!
By Chinmay Deval
February 10, 2022
Impressive 2D/3D plots, using just R? Wait, What?!
Is #rstats a part of your workflow? Do you love creating 2D/3D visuals? Have you always wanted to create amazing 3D visuals like this 👇?
Then the Rayshader package is your friend. Thanks to this package, creating such visuals with R very easy and straight forward.
Rayshader
Rayshader is a powerful open-source R package developed by Tyler Morgan-Wall. You can learn more about the package here and also find a comprehensive documentation of different functions here . Rayshader simplifies visualizing data in 2D and 3D with amazing levels of details and using just R. Not just that! What amazes me is the ease at which one can customize almost anything in the visual.
Exploring Rayshader
Let’s start exploring some of the functionalities Rayshader has on offer. Here I will use the digital elevation model (DEM) data of my home town in India. Let’s jump right in and begin by loading the packages and the DEM layer. This is a gridded raster file. I will also crop it to the bounding box that I am interested in.
# pacakges we will need
library(raster)
library(rayshader)
library(tidyverse)
library(leaflet)
# import data
data = raster::raster("D:/GitHub/MyContributions_to_30DayMapChallenge/11_3d/data/srtm_51_09.tif")
# define bounding box
e = extent(c(xmax = 73.4, xmin = 74, ymin = 18.3, ymax = 18.72))
# clip raster to the bounding box
data = crop(data,e)
Here’s a quick look at the data:
# define color palette
pal = leaflet::colorNumeric(palette ="inferno", values(data),
na.color = "transparent")
# plot raster layer
leaflet() %>% addTiles() %>%
addRasterImage(data, colors = pal, opacity = 0.6) %>%
addLegend(pal = pal, values = values(data),
title = "elevation (m)")
2D mapping
Now, lets convert this data into a 2D matrix using the raster_to_matrix
function from Rayshader package.
data_matrix = rayshader::raster_to_matrix(data)
Generate 2D map using the code below:
data_matrix%>%
rayshader::sphere_shade(texture = "desert", sunangle = 90) %>%
rayshader::add_water(rayshader::detect_water(data_matrix), color = "imhof4") %>%
rayshader::add_shadow(rayshader::ray_shade(data_matrix), 0.5) %>%
rayshader::add_shadow(rayshader::ambient_shade(data_matrix), 0) %>%
rayshader::plot_map()
The detect_water
and add_water
functions detect and add the water layers to the plot. We can even shift around the sun direction using the sunangle
argument in the sphere_shade
function. The ray_shade
function creates ray-traced lighting layer from a given sun angle and the ambient_shade
function models and adds the lighting layer as a result of atmospheric scattering.
3D mapping
Generate 3D map using the code below:
data_matrix%>%
rayshader::sphere_shade(texture = "desert") %>%
rayshader::add_water(rayshader::detect_water(data_matrix), color = "imhof4") %>%
rayshader::add_shadow(rayshader::ray_shade(data_matrix, zscale = 3), 0.5) %>%
rayshader::add_shadow(rayshader::ambient_shade(data_matrix), 0) %>%
rayshader::plot_3d(data_matrix, zscale = 10, fov = 10, theta = 45, zoom = .65, phi = 45,
windowsize = c(1000, 800))
Sys.sleep(0.2)
# save snapshot to a file
render_snapshot("3d_snap.png")
Adding labels
What if you wanted to add labels on this map? That is easy too. you can use the render_label
function to add labels as shown below:
data_matrix%>%
rayshader::sphere_shade(texture = "desert") %>%
rayshader::add_water(rayshader::detect_water(data_matrix), color = "imhof4") %>%
rayshader::add_shadow(rayshader::ray_shade(data_matrix, zscale = 3), 0.5) %>%
rayshader::add_shadow(rayshader::ambient_shade(data_matrix), 0) %>%
rayshader::plot_3d(data_matrix, zscale = 10, fov = 10, theta = 65, zoom = .65, phi = 45,
windowsize = c(1000, 800))
render_label(data_matrix, x = 450, y = 340, z = 18000, zscale = 50,
text = "Khadakwasla", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 250, y = 390, z = 15000, zscale = 50,
text = "Panshet", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 100, y = 220, z = 10000, zscale = 50,
text = "Mulshi", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 100, y = 100, z = 9000, zscale = 50,
text = "Pawna", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
Sys.sleep(0.2)
# save snapshot to a file
render_snapshot("3d_snap_with_labels_featured.png")
Exporting a movie
This is how you can save the rendered 3D frames as a movie like the one displayed at the beginning of this blog:
data_matrix%>%
rayshader::sphere_shade(texture = "desert") %>%
rayshader::add_water(rayshader::detect_water(data_matrix), color = "imhof4") %>%
rayshader::add_shadow(rayshader::ray_shade(data_matrix, zscale = 3), 0.5) %>%
rayshader::add_shadow(rayshader::ambient_shade(data_matrix), 0) %>%
rayshader::plot_3d(data_matrix, zscale = 10, fov = 10, theta = 65, zoom = .65, phi = 45,
windowsize = c(1000, 800))
render_label(data_matrix, x = 450, y = 340, z = 18000, zscale = 50,
text = "Khadakwasla", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 250, y = 390, z = 15000, zscale = 50,
text = "Panshet", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 100, y = 220, z = 10000, zscale = 50,
text = "Mulshi", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_label(data_matrix, x = 100, y = 100, z = 9000, zscale = 50,
text = "Pawna", textsize = 2, linewidth = 5, freetype = FALSE,
linecolor = "#df7c4d",
textcolor = "#df7c4d",
dashed = TRUE)
render_snapshot()
angles= seq(0,360,length.out = 1441)[-1]
for(i in 1:1440) {
render_camera(theta=-45+angles[i])
render_snapshot(filename = sprintf("pune%i.png", i))
}
rgl::rgl.close()
system("ffmpeg -framerate 60 -s 1920x1080 -i pune%d.png -pix_fmt yuv420p pune_new.mp4")
- Posted on:
- February 10, 2022
- Length:
- 5 minute read, 899 words
- See Also: