r-tmap / tmap

R package for thematic maps
https://r-tmap.github.io/tmap
GNU General Public License v3.0
868 stars 121 forks source link

Adding `tmap` layers to Leaflet Proxy map #865

Open James-G-Hill opened 7 months ago

James-G-Hill commented 7 months ago

I was working on integrating tmap and leaflet this week in a package so that I can use tmap to create a static map then convert to leaflet when necessary & add any extra interactive elements on top. This is achievable with tmap_leaflet (although there were some annoyances such as the default converted Leaflet always showing 3 basemaps and then having to define the 1 basemap I want separately with tmap_options(basemaps).

I thought it would be great to define any layers that could be static in tmap and then convert to leaflet when I need to. This would reduce duplication of effort across tmap and leaflet.

However, one problem I came across is that I want to be able to use the layers I defined in leaflet with a proxy map. If I convert with tmap_leaflet I get a full leaflet map, not layers I can then add to a proxy map.

Is there any way to convert individual layers from tmap to leaflet format? Or extract layers from the output of tmap_leaflet and add them to another leaflet map.

I'm guessing this is not possible to do with the current version of tmap but maybe such an ability could be added in future?

Ultimately, I'd like to be able to define a tm_lines then convert to leaflet and attach it so that the lines and legend appear correctly on the leaflet map.

mtennekes commented 7 months ago

I still have to migrate the tmapProxy/shiny integration in v4.

Your input is welcome, so thanks for that. However, I don't understand exactly what you mean yet. Are you looking for something like tmap_leaflet(tm, layers = c(1,3,5))? If so, why don't you just create a tmap with those layers?

Could you perhaps share a minimal use case / example, of something you want to achieve?

mtennekes commented 4 months ago

shiny functions implemented for v4 :-) Please check if you have time @Nowosad @nickbearman @James-G-Hill @olivroy

If anything is missing or you have feature requests, please let me know

Nowosad commented 4 months ago

I've tried to modify the app from geocompr (https://github.com/geocompx/geocompr/blob/main/apps/CycleHireApp/app.R) to use the shiny features of tmap and it seems to be working fine (except the issue mentioned at https://github.com/r-tmap/tmap/issues/904):

library(shiny)
library(sf)
library(spData)
library(spDataLarge)
library(leaflet)
library(tmap)
library(units)
library(dplyr)
library(stringr)
tmap_mode("view")

# Based on input coordinates finding the nearest bicycle points
ui = fluidPage(
  # Application title
  titlePanel("CycleHireApp"),
  # Numeric Input from User
  bootstrapPage(
    div(style = "display:inline-block", 
        numericInput("x", ("Enter x-coordinate of your location"), value = 51.5, step = 0.001)),
    div(style = "display:inline-block", 
        numericInput("y", ("Enter y-coordinate of your location"), value = -0.1, step = 0.001)),
    div(style = "display:inline-block", 
        numericInput("num", "How many cycles are you looking for?", value = 1, step = 1))
  ),
  # Where leaflet map will be rendered
  fluidRow(
    tmapOutput("map", height = 300)
  )
)

server = function(input, output, session) {
  #Centering the leaflet map onto London - use if needed
  map_centre = matrix(c(-0.2574846, 51.4948089), nrow = 1, ncol = 2, 
                      dimnames = list(c("r1"), c("X", "Y")))

  #Based on input coords calculating top 5 closest stations to be displayed 

  #Making reactive object of input location coordinates
  input_pt = reactive({
    matrix(c(input$y, input$x), nrow = 1, ncol = 2,
           dimnames = list(c("r1"), c("X", "Y")))
  })
  input_pt_sf = reactive({
    st_as_sf(as.data.frame(input_pt()), coords = c("X", "Y"), crs = "EPSG:4326")
  })
  #Rendering the output map showing the input coordinates
  output$map = renderTmap({
    tm_shape() +
      tm_basemap() +
      tm_view(set.view = c(input_pt()[, "X"], input_pt()[, "Y"], 15)) 
  })
  #Finding the top distance between input coordinates and all other cycle stations, then sorting them.
  data = reactive({
    cycle_hire$dist = st_point(input_pt()) |>  
      st_sfc() |> 
      st_set_crs("EPSG:4326") |> 
      st_distance(cycle_hire$geometry) |>
      t() |> 
      set_units("km")

    cycle_hire[order(cycle_hire$dist), ]
  })
  #Filtering the distance data from above to show top 5 closest stations meeting requirement of # of bikes needed  
  filteredData = reactive({
    data() |> 
      filter(nbikes >= input$num) |> 
      head(5) |> 
      mutate(popup = str_c(str_c("Station:", name, sep = " "),
                           str_c("Available bikes:", nbikes, sep = " "), sep = "<br/>"))

  })
  #Making changes to the output leaflet map reflecting the cycle stations found above
  icons = tmap_icons(system.file("img/airplane.png", package = "tmap"))

  observe({
    proxy = tmapProxy("map", session, x = {
        tm_remove_layer(402) +
        tm_shape(input_pt_sf()) +
        tm_markers(size = 4, zindex = 402) +
        tm_remove_layer(401) +
        tm_shape(filteredData()) +
        tm_symbols(shape = icons, zindex = 401)
    })

  })
}
# Run the application
shinyApp(ui = ui, server = server)  
mtennekes commented 4 months ago

904 is fixed, let me know if there are any other issues

James-G-Hill commented 3 months ago

@mtennekes Sorry, I have been away from this for a long time; when I look at my maps again soon I'll see if what you've done has fixed what I was trying to do.