Solved - Error when using mapshot with Shiny Leaflet

I'm trying to save an image of my Leaflet map in my Shiny app. I'm using the mapshot function from the mapview package The map is constantly updated with markers etc via leafletProxy("map") %>% <add markers etc>

I'm using a function:

observeEvent(input$exportMap, {
    mapshot("map", file=paste0(getwd(), '/exported_map.png'))
  })

However I get the following error:

Warning: Error in : $ operator is invalid for atomic vectors
Stack trace (innermost first):
    70: resolveSizing
    69: toHTML
    68: <Anonymous>
    67: do.call
    66: mapshot
    65: observeEventHandler [testFrontend/server.R#71]
     1: runApp
ERROR: [on_request_read] connection reset by peer

I also tried:

observeEvent(input$exportMap, {
    m <- leafletProxy("map")
    mapshot(m, file=paste0(getwd(), '/exported_map.png'))
  })

But then I get:

Warning: Error in system.file: 'package' must be of length 1
Stack trace (innermost first):
    75: system.file
    74: inherits
    73: yaml::yaml.load_file
    72: getDependency
    71: widget_dependencies
    70: htmltools::attachDependencies
    69: toHTML
    68: <Anonymous>
    67: do.call
    66: mapshot
    65: observeEventHandler [testFrontend/server.R#72]
     1: runApp
ERROR: [on_request_read] connection reset by peer

Any suggestions?

Thanks.

I've added a reprex here:

library(leaflet)
library(mapview)
library(shiny)

ui <- fluidPage(
  
   titlePanel("Mapshot error example"),

   sidebarLayout(
      sidebarPanel(
         actionButton("addMarkers", "Add markers"),
         actionButton("saveMapUsingID", "Screenshot map with ID"),
         actionButton("saveMapUsingProxy", "Screenshot map with Proxy")
      ),
      mainPanel(
         leafletOutput("map")
      )
   )
)

server <- function(input, output) {
  
  output$map <- renderLeaflet({
    leaflet() %>%
      addProviderTiles(providers$OpenStreetMap)
  })
  
  observeEvent(input$addMarkers, {
     # Somewhere in London
     randPoint1 <- runif(1, min=-0.34, max=0.16)
     randPoint2 <- runif(1, min=51.34, max=51.67)
     leafletProxy("map") %>%
       addMarkers(lng=randPoint1, lat=randPoint2) %>%
       setView(lng=randPoint1, lat=randPoint2, zoom=10)
   })
  
  observeEvent(input$saveMapUsingID, {
    mapshot("map", file=paste0(getwd(), '/exported_map.png'))
  })
  
  observeEvent(input$saveMapUsingProxy, {
    m <- leafletProxy("map")
    mapshot(m, file=paste0(getwd(), '/exported_map.png'))
  })
  
}

shinyApp(ui = ui, server = server)

Thanks for posting the reprex!

The issue appears to be with the fact the "map" is not actually a leaflet object, but rather a shiny output.

If you create the leaflet object in its own reactive expression and then pass that into the renderLeaflet and first mapshot observer then that first save button works.

For the proxy option, I am not able to get it to work as I am getting the following error:

Warning: Error in system.file: 'package' must be of length 1

This seems to be an issue with saveWidget which is what mapshot calls. I am also not entirely sure what you are accomplishing by saving it with the proxy option that is not already being captured by the first save option. But I may be missing something.

Anyway, Here is the app with a working save button for the first option:

library(leaflet)
library(mapview)
library(shiny)

ui <- fluidPage(
  
  titlePanel("Mapshot error example"),
  
  sidebarLayout(
    sidebarPanel(
      actionButton("addMarkers", "Add markers"),
      actionButton("saveMapUsingID", "Screenshot map with ID")
      # actionButton("saveMapUsingProxy", "Screenshot map with Proxy")
    ),
    mainPanel(
      leafletOutput("map")
    )
  )
)

server <- function(input, output, session) {
  
  map_reactive <- reactive({
    leaflet() %>%
      addProviderTiles(providers$OpenStreetMap)
  })
  
  output$map <- renderLeaflet({
    map_reactive()
  })
  
  observeEvent(input$addMarkers, {
    # Somewhere in London
    randPoint1 <- runif(1, min=-0.34, max=0.16)
    randPoint2 <- runif(1, min=51.34, max=51.67)
    leafletProxy("map") %>%
      addMarkers(lng=randPoint1, lat=randPoint2) %>%
      setView(lng=randPoint1, lat=randPoint2, zoom=10)
  })
  
  observeEvent(input$saveMapUsingID, {
    mapshot(map_reactive(), file=paste0(getwd(), '/exported_map.png'))
  })
  

  
}

shinyApp(ui = ui, server = server)

As a final note, please indicate when you have cross-posted your question (here is your question also on SO). This will prevent people from spending time trying to answer your question if you have already gotten an answer. Please see the FAQ about cross-posting questions:

1 Like

Thanks for the reply, apologies for the cross posting!

I've given your way a go but I still don't get the markers that are added to the map on the exported map. That's the main issue, I'd like the user to be able to add their data as they go along and then export the map.

I've uploaded a gif of what I'm trying to do, notice the output doesn't contain the markers, thanks again :smile:

Gif of process

Marking this as solved as @tbradley helped me get the mapshot actually generating a file. I've since found https://github.com/rowanwins/leaflet-easyPrint which seems to do a better job (For my needs) at producing an export.

Many thanks @tbradley for the help!

3 Likes

I was having the same issues as @ciaranevans. I used the leafletProxy function to give the user the flexibility to change the underlying data without losing all of the work they did navigating to a certain location, and you can't get that when the markers/shapefiles are added as part of the initial rendering as a reactive expression.

I was able to get the above example working by adding reactive values to store the points, and creating another reactive map that pulls from what's on the screen. It may be redundant, but it seems to work:

library(leaflet)
library(mapview)
library(shiny)

ui <- fluidPage(
  
  titlePanel("Mapshot error example"),
  
  sidebarLayout(
    sidebarPanel(
      actionButton("addMarkers", "Add markers"),
      actionButton("saveMapUsingID", "Screenshot map with ID")
      # actionButton("saveMapUsingProxy", "Screenshot map with Proxy")
    ),
    mainPanel(
      leafletOutput("map")
    )
  )
)

server <- function(input, output, session) {
  
  # Create placeholder for reactive values that we'll update
  v = reactiveValues()
  v$point = NULL
  
  # Create empty basemap of the world
  map_reactive <- reactive({
    leaflet() %>%
      addProviderTiles(providers$OpenStreetMap)
  })
  
  # Call the reactive map of the world to render on the screen
  output$map <- renderLeaflet({
    map_reactive()
  })
  
  # Create long/lat points in London if the button is pushed
  observeEvent(input$addMarkers, {
    # Somewhere in London
    p = data.frame(lng = runif(1, min=-0.34, max=0.16),
                   lat = runif(1, min=51.34, max=51.67))
    v$point = rbind(v$point,p)
  })
  
  # Add lat/long created with button push to map and zoom in
  observe({
    if (!is.null(v$point)) {
      leafletProxy("map") %>%
        clearMarkers() %>%
        addMarkers(lng=v$point[,1], lat=v$point[,2]) %>%
        setView(lng=mean(v$point[,1]), lat=mean(v$point[,2]), zoom=10)
    }
  })
  
  # Create a separate map object for printing - same properties as what's on the screen
  user_created_map <- reactive({
    m = map_reactive() %>%
      setView(lng = input$map_center$lng, lat = input$map_center$lat, 
              zoom = input$map_zoom)
    if (!is.null(v$point)) {
      m = m %>%
        addMarkers(lng=v$point[,1], lat=v$point[,2])
    }
    m
  })
  
  # Print the map to the working directory
  observeEvent(input$saveMapUsingID, {
    mapshot(user_created_map(), file=paste0(getwd(), '/exported_map.png'))
  })
  
}

shinyApp(ui = ui, server = server)

Hope this helps others who are running into the same issue.

2 Likes