r-spatial / mapedit

Interactive editing of spatial data in R
https://www.r-spatial.org/r/2019/03/31/mapedit_leafpm.html
Other
218 stars 33 forks source link

Turn editing on/off in Shiny module #61

Closed mstrimas closed 7 years ago

mstrimas commented 7 years ago

As far as I can tell, the current Shiny modules don't allow any interaction with the map beyond the editing. You essentially hard code what's displayed on the map and there doesn't seem to be a way to modify the map interactively as with a normal leaflet map via leafletProxy(). In addition, I want to be able to add and remove the editing toolbar. As it stands, I need two map interfaces, one to collect the data (using editModUI) and another to do something with it (using leafletOutput).

Here's an example of what I want to. The only difference is I want to save the edits in a reactive object.

library(shiny)
library(sf)
library(leaflet)
library(leaflet.extras)

ui <- fluidPage(
  actionButton("start", "Start Editing"),
  actionButton("end", "End Editing"),
  selectInput("dropdown", "Draw Points?", choices = c(Yes = "yes", No = "no")),
  leafletOutput("map")
)
server <- function(input, output, session) {
  output$map <- renderLeaflet({
    leaflet() %>%
      addTiles() %>% 
      setView(-90, 42, zoom = 10)
  })
  observeEvent(input$start, {
    leafletProxy("map") %>% 
      addDrawToolbar()
  })
  observeEvent(input$end, {
    leafletProxy("map") %>% 
      removeDrawToolbar()
  })
  observe({
    if (input$dropdown == "yes") {
      pts <- data.frame(lng = rnorm(100, -90), lat = rnorm(100, 42)) %>% 
        st_as_sf(coords = c("lng", "lat"))
      leafletProxy("map") %>% 
        addCircleMarkers(data = pts, group = "points")
    } else {
      leafletProxy("map") %>% 
        leaflet::clearGroup("points")
    }
  })
}
shinyApp(ui, server)
timelyportfolio commented 7 years ago

Thanks for filing issue, and I want to make sure that I fully understand your question.

Are you asking if leafletProxy can be used with the edit module? If so, here is an example that might help. leafletProxy with modules is slightly more complicated. We will need to use shiny::NS() with the id provided in editModUI ("editme" in example below).

library(shiny)
library(leaflet)
library(leaflet.extras)
library(mapedit)
library(mapview)

ui <- tagList(
  editModUI("editme"),
  actionButton("clear", "clear points"),
  actionButton("remove", "remove draw")
)

server <- function(input, output, session) {
  # to use the namespace that we gave
  ns <- NS("editme")

  callModule(
    editMod,
    "editme",
    leaflet() %>% addFeatures(breweries)
  )

  observeEvent(
    input$clear,
    {
      print(ns("map"))
      # editMod uses "map" as the id for the map
      leafletProxy(ns("map")) %>%
        clearMarkers()
    }
  )

  observeEvent(
    input$remove,
    {
      leafletProxy(ns("map")) %>%
        removeDrawToolbar()
    }
  )
}

shinyApp(ui,server)

If your question is more in regards to leafletProxy + Leaflet.draw, then this very recent issue https://github.com/bhaskarvk/leaflet.extras/issues/96 might be helpful.

mstrimas commented 7 years ago

Thanks, @timelyportfolio! In hindsight my question was a bit vague, and I've edited the title to better reflect what I was actually asking. Despite the vagueness, the example you gave mostly answers what I'm looking to do. In case anyone else stumbles across this, the following slight modification allows adding and removing the editing toolbar:

library(shiny)
library(leaflet)
library(leaflet.extras)
library(mapedit)
library(mapview)

ui <- tagList(
  editModUI("editor"),
  actionButton("clear", "Clear Points"),
  actionButton("start", "Start Drawing"),
  actionButton("end", "Finish Drawing")
)

server <- function(input, output, session) {
  # to use the namespace that we gave
  ns <- NS("editor")

  base_map <- leaflet() %>% 
    addTiles() %>% 
    addFeatures(breweries)
  drawn <- callModule(editMod, "editor", base_map)
  # start with drawing off
  leafletProxy(ns("map")) %>%
    removeDrawToolbar()

  observe({print(drawn())})

  observeEvent(input$clear, {
    leafletProxy(ns("map")) %>%
      clearMarkers()
  })

  observeEvent(input$start, {
    leafletProxy(ns("map")) %>%
      addDrawToolbar(editOptions = editToolbarOptions())
  })

  observeEvent(input$end, {
    leafletProxy(ns("map")) %>%
      removeDrawToolbar(clearFeatures = TRUE)
  })
}

shinyApp(ui, server)
timelyportfolio commented 7 years ago

Great and glad you asked since I think leafletProxy in general with Shiny modules does not have enough examples. Please let us know all the great things you create and please keep asking.

SarahBauduin commented 6 years ago

Dear Matt and Kent, Sorry to post my question here as the issue is now closed and my question is not (completely) related to the original question. (Let me know if I should move this post somewhere more appropriate). I want to interactively modify a shapefile on Shiny (add and remove parts) and then save it. I've been struggling for weeks to do it but your post gave me awesome clues. Now I can do half of what I want, I can add new features on the shapefile and save it. However, I'm still struggling with removing features of the original shapefile I loaded. I would like to be able to modify my loaded shapefile by clicking on the bin icon and then on road segments to remove them and then save the new version of the road network (with the new parts added and without the ones removed). Do you know if it is possible and do you have any hint on how to do this? Here is my code. Thanks in advance!

ui <- fluidPage(
  editModUI("editor"),
  downloadButton("saveChanges", "Save map modifications")
)

server <- function(input, output){

  roadShp <- shapefile("data/roads.shp")
  roadShpCRS <- spTransform(roadShp, CRS("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"))
  roadConvert <- st_as_sfc(roadShpCRS)

  ns <- NS("editor")

  base_map <- leaflet() %>% 
    addTiles() %>% 
    addPolylines(data = roadConvert)
  drawn <- callModule(editMod, "editor", base_map)

  leafletProxy(ns("map")) %>%
    addDrawToolbar(editOptions = editToolbarOptions())

  newRoads <- reactiveValues()
  observe({
    if(!is.null(drawn()))
      isolate(
        newRoads <<- drawn()
      )
  })

  output$saveChanges <- downloadHandler(
    filename = "newRoads.RData",
    content = function(file) {
      save(newRoads, file = file)
      }
    )
}
timelyportfolio commented 6 years ago

@SarahBauduin, no need to apologize and happy you are trying mapedit. Let's see if I might be able to help.

Do you need to specifically add the drawing toolbar? The previous example only did this since Matt wanted to be able to add and remove the toolbar. If you want it always there, then the leafletProxy part should not be necessary.

Also, leaflet and leaflet.extras just got massive upgrades, so I would recommend updating to their newest versions on CRAN.

Does editFeatures() accomplish the objective, or are you trying to integrate into a larger Shiny app?

SarahBauduin commented 6 years ago

Thanks @timelyportfolio for your time! I used the leafletProxy part because I later added some options to only keep the line drawing tool:

leafletProxy(ns("map")) %>%
    addDrawToolbar(editOptions = editToolbarOptions(),
      polygonOptions = FALSE,
      circleOptions = FALSE,
      rectangleOptions = FALSE,
      markerOptions = FALSE)

Can I add these options somewhere else without using leafletProxy?

I installed the latest versions of leaflet and leaflet.extras! Thanks for the info!

The code I shared with you will be integrated into a larger Shiny app where I will, among other things, add a selectInput to select a shapefile among severals (e.g., highways, roads, rails). Then I want the user to be able to modify the selected layer by adding or removing linear segments and then save it to use the modified layer in a model later. I tried to reproduce this example but I couldn't figure out how to use editMap() inside a Shiny app and then save the result in an R object.

SarahBauduin commented 6 years ago

Found my solution! I used

  output$mapRoadOrigin <- renderLeaflet({
    leaflet() %>%
      addProviderTiles("OpenMapSurfer.Roads") %>%
      addPolylines(data = roadNetwork, layerId = ~ID, color = "black", opacity = 1) %>%
      addDrawToolbar(
        polygonOptions = FALSE,
        circleOptions = FALSE,
        rectangleOptions = FALSE,
        markerOptions = FALSE,
        circleMarkerOptions = FALSE
      )
  })

  # Segment selection by click
  observeEvent(input$mapRoadOrigin_shape_click, {
    click <- input$mapRoadOrigin_shape_click
    newRoad$segmToModif <- click$id # selected segment
  })

Then, I used newRoad$segmToModif with another map in another tab to modify my road network!

vorpalvorpal commented 3 years ago

Hi,

The examples given above by @timelyportfolio and @mstrimas doesn't seem to work anymore. Adding and removing markers with leafletproxy still works, but removeDrawToolbar() doesn't seem to do anything.

Also, library(leafem) is required for addFeatures() but isn't listed in the example code.