I am trying to map ~8000 polygons using leaflet and run into performance issues. As I am using the map within a shiny app, I was wondering if its possible to somehow cache or pre-render the map.
Note that in my case, I have different layers of polygons that are swapped following this approach.
A small MWE would be this:
The data can be downloaded from here
library(shiny)
library(leaflet)
library(sf)
## Download Shapefile
file <- "plz-gebiete.shp"
if (!file.exists(file)) {
url <- "https://www.suche-postleitzahl.org/download_files/public/plz-gebiete.shp.zip"
zipfile <- paste0(file, ".zip")
download.file(url, zipfile)
unzip(zipfile)
}
df <- st_read(file, options = "ENCODING=UTF-8")
# If possible: pre-render the map here!
library(shiny)
ui <- fluidPage(
leafletOutput("mymap", width = "700px", height = "700px")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addPolygons(data = df, weight = 1, color = "black")
})
}
shinyApp(ui, server)
It takes around 16 seconds on my machine to render the map with the polygons.
If possible, I would like to pre-render the map once, save it as an .rds
file, and load it on demand. Note that I know the width/height of the map within the app (here set to 700px). But something like
map <- renderLeaflet({leaflet() %>% ...})
saveRDS(map, "renderedmap.rds")
map <- readRDS("renderedmap.rds")
# within server()
output$mymap <- map
does not result in any performance gains.
Alternatively, I have tried to load the leaflet asynchronously so that other parts of the app can be rendered/interacted with but to no avail.
Any ideas how to solve or circumnavigate this problem?
The 2 following approaches dont exactly answer your question, but they are definitly more performant alternatives compared to leaflet::addPolygons
.
Using Flatgeobuf Format:
Based on the description from leafem::addFgb
:
Flatgeobuf can stream the data chunk by chunk so that rendering of the map is more or less instantaneous. The map is responsive while data is still loading so that popup queries, zooming and panning will work even though not all data has been rendered yet.
I think the dataset are linestrings, that is why fillColor
seems to be ignored.
library(leaflet)
library(leafem)
library(shiny)
# via URL (data around 13mb)
url = "https://raw.githubusercontent.com/bjornharrtell/flatgeobuf/3.0.1/test/data/UScounties.fgb"
ui <- fluidPage(
leafletOutput("mymap", width = "700px", height = "700px")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles() %>%
leafem:::addFgb(
url = url, group = "counties",
label = "NAME", popup = TRUE,
fillColor = "blue", fillOpacity = 0.6,
color = "black", weight = 1) %>%
addLayersControl(overlayGroups = c("counties")) %>%
setView(lng = -105.644, lat = 51.618, zoom = 3)
})
}
shinyApp(ui, server)
Using leafgl
(WebGL-Renderer):
library(sf)
library(shiny)
library(leaflet)
library(leafgl)
plz <- st_read("C:/Users/user/Downloads/plz-gebiete.shp", layer = "plz-gebiete")
ui <- fluidPage(
leafletOutput("mymap", width = "700px", height = "700px")
)
server <- function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addGlPolygons(data = plz, color = ~plz, popup = "note", group = "plz") %>%
addLayersControl(overlayGroups = "plz")
})
}
shinyApp(ui, server)