r-spatial / mapedit

Interactive editing of spatial data in R

Home Page:https://www.r-spatial.org/r/2019/03/31/mapedit_leafpm.html

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Click events for leafpm editToolbar buttons

chintanp opened this issue · comments

I am using mapedit in Shiny using the callModule as here

selections <- callModule(selectMod, "test-mod", m)

I want to run some code when a polygon is finished drawing and another code when polygon is deleted. I am using an observeEvent on reactives selections()$finished and selections()$deleted to achieve this. While this works for the first run to catch both events, the selections()$deleted is not NULL subsequently and therefore the corresponding observeEvent is fired even when just a shape is drawn.

Are there specific events tied to the draw/delete buttons in the leafpm edit toolbar? If so, an example usage would be helpful.

If not supported currently, any suggestions on how this can be achieved using for example, shinyjs?

@chintanp I see what you are saying. Isolating events is I think impossible using the reactiveValue inside the reactive since the parent reactive change will trigger the observer for drawn, deleted, etc. I had not considered this previously and will spend some time thinking through a better solution. For now you could observe the raw events and respond that way. Here is an example.

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

m <- mapview(breweries)@map

ui <- tagList(
  editModUI("test-edit", height=600)
)

server <- function(input, output, session) {
  crud <- callModule(
    editMod,
    "test-edit",
    leafmap = m,
    targetLayerId = "breweries"
  )

  observeEvent(crud()$deleted, {
    print('delete')
    str(crud()$deleted)
  })
  
  observeEvent(crud()$drawn, {
    print('drawn')
    str(crud()$drawn)
  })

  observeEvent(input[["test-edit-map_draw_deleted_features"]], {
    print('raw delete event will only update on delete')
  })
  
  
}
shinyApp(ui, server)

Note: I tried every iteration that I could to properly isolate and none seemed to work.

Code Examination

In lines we set up a reactiveValues container which would allow the isolation/separation desired. However, in the return in lines we use a reactive which will trigger observers for any non-null list item whether they are changed (dirty) or not.

@chintanp another option would be to track changes manually with identical. This is a lot to ask a user, and I'd like to find a better way, but for now:

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

m <- mapview(breweries)@map

ui <- tagList(
  editModUI("test-edit", height=600)
)

server <- function(input, output, session) {
  crud <- callModule(
    editMod,
    "test-edit",
    leafmap = m,
    targetLayerId = "breweries"
  )
  
  deleted <- isolate(crud()$deleted)
  drawn <- isolate(crud()$drawn)

  observeEvent(crud()$deleted, {
    if(!identical(crud()$deleted, deleted)) {
      print('deleted')
      str(crud()$deleted)
      deleted <<- crud()$deleted
    }
  })
  
  observeEvent(crud()$drawn, {
    if(!identical(crud()$drawn, drawn)) {
      print('drawn')
      str(crud()$drawn)
      drawn <<- crud()$drawn
    }
  })

  observeEvent(input[["test-edit-map_draw_deleted_features"]], {
    print('raw delete event will only update on delete')
  })
  
  
}
shinyApp(ui, server)

I ended up implementing something similar, using memoryCache. I create a unique key per session and update it with the edit_id of the sf df row, like so:

set(paste0("deleted", session$token), selections()$deleted$edit_id)

when the first time delete happens, and check in an if condition whether the edit_id is same.

For subsequent deletes, I use

set(paste0("deleted", session$token), dplyr::last(selections()$deleted$edit_id))

to do a similar check.

It is hacky and I do not feel good about it, as edit_id is undocumented. Further, I had to change this to use selections()$deleted$X_leaflet_id when using leaflet.extras drawToolbar.

The isolate - identical workflow is cleaner. Will post further if I have any issues.

@chintanp thanks for the report. I'd like to allow the isolation, but I think the change would require a breaking API change, so I'll leave this open to see if anyone expresses interest or proposes other solutions.

@timelyportfolio another (maybe associated) useful feature would be the ability to programmatically toggle the toolbar button states. For example, the issue #113 can be avoided if I can say only allow the "delete" button to be clickable when there are "drawn/editable" features on the map, and then become dormant as soon as all the "drawn/editable" features have been deleted.