In this manual we will discuss an example of using
starsTileServer
for serving map tiles in a shiny app. It
shows the ability to visualize different layers and dynamically change
layers. To do this we will use a sample dataset from the ERA5 weather
model, that includes different variables and pressure levels.
library(starsTileServer)
The sample dataset can be retrieved with the following code using the
ecmwfr
R package:
require(ecmwfr)
<-
request list(
"dataset_short_name" = "reanalysis-era5-pressure-levels",
"product_type" = "reanalysis",
"variable" = c("temperature", "geopotential", "u_component_of_wind", "v_component_of_wind"),
"pressure_level" = c("875", "900", "925"),
"year" = "2000",
"month" = "04",
"day" = as.character(27:29),
"time" = sprintf("%02i:00", 0:23),
"area" = "64/-130/-64/144",
"format" = "netcdf",
"target" = "ecmwfData.nc"
)# make sure you use your own uid and key ( https://cds.climate.copernicus.eu/#!/home )
wf_set_key("uid_to_replace", "key_to_replace", service = "cds")
<- wf_request(
ncfile user = "uid_to_replace",
request = request,
transfer = TRUE,
path = "~",
verbose = FALSE
)
To set up the tile server we need the grid file loaded. Additionally
a color function can be added. This function needs to have the same
format as the color as the color mapping functions in
leaflet
.
<- stars::read_stars(tmpGridFile)
weatherData #> t, z, u, v,
::st_crs(weatherData) <- "+proj=longlat"
sf
<- function(x,
colFun alpha = 1,
max = 20,
min = -20) {
paste0(
suppressWarnings(leaflet::colorNumeric(
"RdYlBu",
domain = c(as.numeric(min), as.numeric(max))
)(x)),as.character(as.raw(as.numeric(alpha) * 255))
)
}attr(colFun, "colorType") <- "numeric"
The tileserver needs to run in a separate process. On a personal
computer this can easily be achieved by running two R processes at the
same time. An alternative approach is to use callr
to start
a separate process.
# note the process is ran in the background, do not forget to close it as it might use quite a bit of memory.
# I have made the experience that callr seems to work poorly if the process is rather verbose
require(callr)
<- r_bg(args = list(grid = weatherData, colFun = colFun), function(grid, colFun) {
rp ::starsTileServer$new(grid, colFun)$run(port = 3746, docs = TRUE)
starsTileServer })
The process will print an url where documentation an testing for the server is available:
message(rp$read_error())
#> Running plumber API at http://127.0.0.1:3746
#> Running swagger Docs at http://127.0.0.1:3746/__docs__/
Now a small example of some interaction based on a shiny example. First we create UI, this consists of a few selection options to change the map features.
require(shiny)
#> Loading required package: shiny
require(leaflet)
require(stars)
#> Loading required package: stars
#> Loading required package: abind
#> Loading required package: sf
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1; sf_use_s2() is TRUE
<- stars::read_stars(tmpGridFile, proxy = T)
weather #> t, z, u, v,
<- fluidPage(
ui # Application title
titlePanel("Web map"),
sidebarLayout(
# Sidebar with a slider input
sidebarPanel(
sliderInput("alpha", "Transparancy", 0, 1, .6, .01),
selectInput("attr", "Attribute", choices = c("u", "v")),
sliderInput(
"time",
"Time",
value = min(st_get_dimension_values(weather, 4)),
min = min(st_get_dimension_values(weather, 4)),
max = max(st_get_dimension_values(weather, 4)),
step = 3600,
timezone = "+0000",
animate = animationOptions(5000)
),selectInput("level", "level (mb)", choices = as.character(st_get_dimension_values(weather, 3))),
sliderInput("colRange", "Range", -50, 50, c(-20, 20))
),# Show a plot of the generated distribution
mainPanel(leafletOutput("map"))
) )
We use the following shiny server function:
<- function(input, output, session) {
server # This reactive creates the URL to the tileserver, it include the different input variables in requests to the server
# The debounce ensures the URL does not get updated to frequent
<- reactive({
url sprintf(
"http://127.0.0.1:3746/map/%s/{z}/{x}/{y}?level=%s&alpha=%f&time=%s&min=%f&max=%f",
$attr,
input$level,
input$alpha,
inputstrftime(input$time, tz = "UTC", format = "%Y-%m-%d %H:%M:%S"),
min(input$colRange),
max(input$colRange)
)%>% debounce(100)
}) # This reactive downloads the color function from the server and prepares it for adding as a legend to the leaflet map
<- reactive({
colorFunction <- readRDS(base::url(sprintf("http://127.0.0.1:3746/map/%s/colorfunctionnoalpha", input$attr)))
f <- attributes(f)
at if (is.finite(max(colrange()))) {
formals(f)$max <- max(colrange())
}if (is.finite(min(colrange()))) {
formals(f)$min <- min(colrange())
}attributes(f) <- at
f
})<- reactive(range(input$colRange))
colrange $map <- renderLeaflet({
outputleaflet() %>%
addTiles() %>%
fitBounds(-50, -30, 50, 50)
})# This observer changes the tile layer as soon as the url is updated
observe({
leafletProxy("map") %>%
clearGroup("wind") %>%
addTiles(url(),
group = "wind",
options = tileOptions(useCache = TRUE, crossOrigin = TRUE)
)
})# This observe changes the legend as soon as it is updated
observe({
<- seq(min(colrange()), max(colrange()), length.out = 20)
s leafletProxy("map") %>%
clearControls() %>%
addLegend(
pal = colorFunction(),
values = s,
title = input$attr,
position = "bottomleft"
)
}) }
The app can be create using the regular shiny functionality
<- shinyApp(ui, server) app
The result of this shiny app looks as follows:
To wrap up we close the tile server
$finalize() rp