r-spatial / mapedit

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

Edit existing feature within Shiny and save #105

Open RossPitman opened 4 years ago

RossPitman commented 4 years ago

I'm trying to create a shiny app with mapedit that will allow me to load a feature (in this example it is the nc.shp from the sf package as an example), and then edit that same feature using mapedit. Currently, I cannot edit it the original feature, but I can edit new features that I create. Is mapedit capable of editing the original feature? It would seem unfortunate if it couldn't, and if so, is there any way to get around this using mapedit?

Thanks very much for the great package!

Reproducible code below as an example. Many thanks!

library(shiny)
library(leaflet)
library(mapedit)
library(sf)

# Load the sf object
nc <- st_read(
  system.file(
    "shape/nc.shp", 
    package = "sf"
  )
)

# Project transformation
nc <- st_transform(
  nc, 
  crs = 4326
)

map <- leaflet() %>% 
  leaflet::addPolylines(
    data = nc,
    weight = 3,
    opacity = 1,
    fill = FALSE,
    color = 'black',
    fillOpacity = 1,
    smoothFactor = 0.01
  )

ui <- fluidPage(

  # Application title
  titlePanel("Test Shiny Leaflet Mapedit"),

  sidebarLayout(
    sidebarPanel(
      actionButton('save', 'Save edits')
    ),

    mainPanel(
      editModUI("map")
    )
  )
)

server <- function(input, output) {

  edits <- callModule(
    editMod,
    leafmap = map,
    id = "map"
  )

  observeEvent(input$save, {

    geom <- edits()$finished

    if (!is.null(geom)) {
      assign('new_geom', geom, envir = .GlobalEnv)
      sf::write_sf(
        geom, 
        'new_geom.geojson', 
        delete_layer = FALSE, 
        delete_dsn = TRUE
      )
    }

  })
}

# Run the application 
shinyApp(ui = ui, server = server)
ngfrey commented 4 years ago

This is exactly what I am trying to do. So far I have only found this link: https://gis.stackexchange.com/questions/203540/how-to-edit-an-existing-layer-using-leaflet

timelyportfolio commented 4 years ago

@ngfrey @RossPitman To edit an existing layer, we will need to add a group in addPolylines so that we can refer to this with mapedit. The group name can be anything, but I use group = "editable" below.

map <- leaflet() %>% 
  leaflet::addPolylines(
    data = nc,
    weight = 3,
    opacity = 1,
    fill = FALSE,
    color = 'black',
    fillOpacity = 1,
    smoothFactor = 0.01,
    group = "editable"
  )

Then in callModule, we will targetLayerId = "editable". One other suggestion I will offer is to try out the new leafpm editor, but this mechanism works for both editors.

  edits <- callModule(
    editMod,
    leafmap = map,
    id = "map",
    editor = "leafpm",
    targetLayerId = "editable"
  )

With polylines since there are so many points to edit, there will be a delay, but you should within a reasonable time see the below screenshot. You might wonder about the red. These are marked as invalid polylines.

image

davis3tnpolitics commented 4 years ago

While @timelyportfolio is definitely correct that it makes the map editable, it doesn't allow you to access the changes from what I can tell. Consider this:

ui <- fluidPage( editModUI("mapeditor"), leafletOutput("mapout"), DTOutput("selected") )

server <- function(input,output,session) { m = leaflet(data= data)%>% addProviderTiles(provider= "CartoDB.Positron")%>% addPolygons(data = somepolygonoverlay, group = "editable")%>% addCircleMarkers(color = data$color) turf_sf <- st_as_sf(data, coords = c("lon", "lat"), crs= 4326) edits <- callModule(editMod, "mapeditor", leafmap= m, editor= "leaflet.extras", targetLayerId = "editable") calc_sf <- reactiveValues() observe({ req(edits()$finished) calc_sf$intersection <- st_intersection(edits()$finished, turf_sf) }) output$mapout <- renderLeaflet({ req(calc_sf$intersection) (mapview(calc_sf$intersection) + mapview(edits()$finished))@map })

}

shinyApp(ui,server)

When you edit the target layer, there's no output. But, when you draw on a feature, this script will collect the intersection and display the shapes in mapview.

In in the EditMap module, edits are coded as $edited_all. But if you switch edits()$finished to edits()$edited_all, there's no capturing of your edited shapes.

How do I get that edited layer to respond downstream? Any ideas?

timelyportfolio commented 4 years ago

@davis3tnpolitics, I think pull request #98 just merged into master and hopefully soon on CRAN will solve the issue. If possible, please install newest with remotes::install_github("r-spatial/mapedit").

For leaflet.extras editor, there should now be an all that contains all features in the editable layer even if they have not been modified. If I understand correctly, we should be able to remove the intersect piece of the code now.

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

ui <- fluidPage(
  editModUI("mapeditor"),
  leafletOutput("mapout")
)

server <- function(input,output,session) {
  dat <- st_as_sf(leaflet::gadmCHE)[c(1,3,5),]

  m <- leaflet()%>%
    addProviderTiles(provider= "CartoDB.Positron")%>%
    addPolygons(data = dat, group = "editable")

  edits <- callModule(editMod,
    "mapeditor",
    leafmap= m,
    editor= "leaflet.extras",
    targetLayerId = "editable"
  )

  output$mapout <- renderLeaflet({
    req(edits()$all)
    mapview(edits()$all)@map
  })

}

shinyApp(ui,server)
davis3tnpolitics commented 4 years ago

I didn't know there was a pull coming for this. Thanks so much @timelyportfolio. Really appreciate this amazing package!

agronomofiorentini commented 2 years ago

The examples that are reported here are about data that it is loaded locally, but i would ask if it is possible to use this framework in order to update a reactive shapefile that is loaded from a remote Postgresdatabase?

I will try to explain me better.

After users have authenticated themselves, I would like to allow users to edit/add/delete shapefiles that are in a remote Postgres database?

all to update the database.

is it possible to do this with mapedit?

I have tried several ways but it didn't work so i will report here a code that could be used as start point in order to solve this.

library(shiny)
library(sf)
library(leaflet)
library(mapview)
library(mapedit)
library(DBI)
library(rpostgis)

ui <- fluidPage(
  editModUI("mapeditor"),
  leafletOutput("mapout")
)

server <- function(input,output,session) {

  remote_con <- dbConnect(RPostgres::Postgres(), 
                          dbname = "XXXX", 
                          host="XXXXXX", 
                          port="XXXXX", 
                          user="XXXXXXX", 
                          password="XXXXXXXX")

  onStop(function() {
    RPostgreSQL::dbDisconnect(remote_con)
  })

  dat<-reactive({
    pgGetGeom(conn = remote_con,
              name = c("user", "shape"),
              geom = "geom")
  })

  m <- leaflet()%>%
    addProviderTiles(provider= "CartoDB.Positron")%>%
    addPolygons(data = dat(), 
                group = "editable")

  edits <- callModule(editMod,
                      "mapeditor",
                      leafmap= m(),
                      editor= "leaflet.extras",
                      targetLayerId = "editable"
  )

  output$mapout <- renderLeaflet({
    req(edits()$all)
    mapview(edits()$all)@map
  })

}

shinyApp(ui,server)
agronomofiorentini commented 1 year ago

Dear all, I have make some improvement, but i still have some problems to solve the request that i have made.

Now my code can import the shapefile (reactive object) and visualize and edit it within the editmod of mapedit. Moreover i can also create a new spatialpolygonsdataframe.

But when I go to plot my result I can't get the polygons in the map.

Does anyone have a workflow that allows you to get the last polygons put in the map

library(shiny)
library(sp)
library(sf)
library(leaflet)
library(leaflet.extras)
library(leafpm)
library(FRK)
library(mapedit)
library(spData)

ui <- fluidPage(

  titlePanel("CRUD Spatial data & Attributes"),

  sidebarLayout(
    sidebarPanel(

      plotOutput("plot"),
      actionButton("plot_action",
                   label = "Plot")
    ),

    mainPanel(
      editModUI("editor")
    )
  )
)

server <- function(input, output) {

  world <- reactive({
    data<-spData::world
    data<-subset(data, name_long=="Italy")
    return(data)
  })

  map<-leaflet() %>%
    addTiles() %>%
    addProviderTiles(providers$OpenStreetMap,
                     options = tileOptions(minZoom = 2, maxZoom = 15)) %>%
    addProviderTiles(providers$Esri.WorldImagery,
                     options = tileOptions(minZoom = 15, maxZoom = 20),
                     group = "Esri.WorldImagery") %>%
    addPmToolbar(targetGroup = "name_long",
                 toolbarOptions = pmToolbarOptions(drawMarker = F,
                                                   drawPolygon = TRUE,
                                                   drawPolyline = F,
                                                   drawCircle = F,
                                                   drawRectangle = F,
                                                   editMode = TRUE,
                                                   cutPolygon = T,
                                                   removalMode = TRUE,
                                                   position = "topleft"))
  #addPmToolbar(targetGroup = "Campo")

  edits <- callModule(editMod,
                      "editor",
                      targetLayerId = "layerId",
                      leafmap = map)

  ns <- shiny::NS("editor")

  observe({
    req(world())
    proxy.lf <- leafletProxy(ns("map"))

    bounds <- world() %>%
      st_bbox() %>%
      as.character()

    proxy.lf %>%
      fitBounds(bounds[1], bounds[2], bounds[3], bounds[4]) %>%
      leaflet::addPolygons(data = world(),
                           weight = 3,
                           opacity = 1,
                           fill = FALSE,
                           color = 'red',
                           fillOpacity = 1,
                           smoothFactor = 0.01,
                           group = "name_long")
  })

  polygons<-eventReactive(input$plot_action, {
    req(edits()$finished)
    edits()$finished
    #edits()$edited
    # edits()$deleted
  })

  output$plot<-renderPlot({
    req(polygons())
    plot(polygons())
  })

}

# Run the application
shinyApp(ui = ui, server = server)