2

I want to add a feature for users to export a map, produced by Tmap, in Shiny. I understand that tmap converts the map to leaflet but it doesn't work with mapview::mapshot as many answers give for saving Leaflet maps in Shiny.

None of the following work:

map_expr <- reactive({
    tm_shape(water1) +
      tm_fill(col = "darkblue") +
    tm_shape(county_outlines) +
      tm_borders(col = "black") +
    tm_shape(herd_summary) +
      tm_symbols(col = "green", size = "REACTORS", scale = 0.15, alpha = 0.7) +
    tm_shape(water1) +
      tm_fill(col = "darkblue")
  })
  
  observe({
    output$map <- renderTmap({
      map_expr()
    })
  })

output$downloadData <- downloadHandler(
    filename = "test.png",
    content = function(file) {
      # mapshot(map_expr(), file = file, cliprect = "viewport")
      # tmap_save(map_expr(), file = file)
      tmapProxy("map", session, {}) %>%
        mapview::mapshot(file = file)
    }
  )
nd37255
  • 248
  • 1
  • 9
  • I am having the same problem - did you find a solution? – SLE Jun 16 '21 at 10:34
  • Unfortunately not; what I ended up doing was using renderPlot and plotOutput which uses static plots (tmap_mode("plot") if are familiar with tmap) and then using tmap_save within the download handler. This means the plots were non interactive, but it was more important for the users to be able to save the maps than to have interactive features in my case. – nd37255 Jun 17 '21 at 14:49
  • After having another look, I think I got it to work, I will add it as an answer – nd37255 Jun 17 '21 at 15:18

3 Answers3

2

After some trial and error, I eventually got it to work, and have included a reproducible example here as follows:

library(shiny)
library(tmap)
library(mapview)

data("World")
pal <- leaflet::colorBin(palette = grDevices::heat.colors(20), domain = World$HPI)

ui <- fluidPage(

    titlePanel("Tmap Example"),

    sidebarLayout(
        sidebarPanel(
            downloadButton("downloadData")
        ),
        mainPanel(
            tmapOutput("map")
        )
    )
)

server <- function(input, output) {
    map_expr <- reactive({
        tm_shape(World) +
            tm_polygons("HPI")
    })
    
    output$map <- renderTmap({
        map_expr()
    })
    
    output$downloadData <- downloadHandler(
        filename = "test.png",
        content = function(file) {
            mapview::mapshot(tmap_leaflet(map_expr()), file = file)
        }
    )
}

shinyApp(ui = ui, server = server)

So the tmap expression must be saved to an object (I am saving it to a reactive object called map_expr, so you must include the brackets when calling this object elsewhere within the code).

Using the function mapshot from the package mapview, you can save a leaflet object, and there is a function called tamp_leaflet within tmap that will convert the tmap object to a leaflet object.

Make sure that mapshot is working by first testing it outside of the application. To get it to work, I had to install the package webshot and had to install phantomJS which can be done via the following function: webshot::install_phantomjs()

Hope this helps!

nd37255
  • 248
  • 1
  • 9
  • Thank you so much. I can't use webshot::install_phantomjs() because of my work's security permissions but I can see that it would work. But like you, my user is more interested in the static images so your comment above (about renderPlot) worked like a treat! – SLE Jun 18 '21 at 07:15
  • Are you able to upload your original code too? I am really curious how you managed to add palettes into the tmap with shiny - I keep getting various errors such as: tmap_HMTC_map<-reactive({ df <-filedata() HMTC_map_df<-merge(map_HMTC,df,by="Region",all=T) custom_pal<-colorRampPalette(cols_df$Blue)(5) tm_shape(HMTC_map_df) + tm_polygons(input$var,pal=colorRampPalette(c("#DC0032","#FFF5AC","#00B0F0")) }) will get Error in $: object of type 'closure' is not sub-settable – SLE Jun 18 '21 at 08:03
  • I can't reproduce your error without your data and code, but the "pal" argument of the function tm_polygons will accept a character vector of hexcodes, so the following line works: pal = c("#DC0032","#FFF5AC","#00B0F0") – nd37255 Jun 18 '21 at 12:05
  • The function colorRampPalette returns a palette functionas opposed to a character vecotr; I will add my code as another answer so that it is easier to read – nd37255 Jun 18 '21 at 12:06
0
library(tmap)
data("World")

# pal accepts a character vector of colours
tm_shape(World) +
  tm_polygons("HPI", pal = c("#DC0032","#FFF5AC","#00B0F0"))

# You can also store the palette in pal, 
# and then call pal(3) to return three colours
pal = grDevices::colorRampPalette(c("#DC0032","#FFF5AC","#00B0F0"))
tm_shape(World) +
  tm_polygons("HPI", pal = pal(3))
nd37255
  • 248
  • 1
  • 9
0

use the tmap_save function

output$downloadData <- downloadHandler(
filename = "example.png",
content = function(file) {
tmap_save(map_expr(), file)
}
  • Thank you for the contribution, although it's not quite the solution I was looking for. This saves a copy of the map as it appears in tmap's "plot" mode. To save a copy of it as it appears in "view" mode, I am still using the code from my other answer which uses mapshot – nd37255 May 12 '22 at 23:36
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 16 '22 at 02:55
  • *"Any answer that gets the asker going in the right direction is helpful, but do try to mention any limitations, assumptions or simplifications in your answer. Brevity is acceptable, but fuller explanations are better."* - check [How do I write a good answer](https://stackoverflow.com/help/how-to-answer). – Kuro Neko May 19 '22 at 08:31