Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit existing feature within Shiny and save #105

Open
RossPitman opened this issue Oct 9, 2019 · 7 comments
Open

Edit existing feature within Shiny and save #105

RossPitman opened this issue Oct 9, 2019 · 7 comments

Comments

@RossPitman
Copy link

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
Copy link

ngfrey commented Oct 25, 2019

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
Copy link
Contributor

timelyportfolio commented Nov 10, 2019

@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
Copy link

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
Copy link
Contributor

timelyportfolio commented Feb 2, 2020

@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
Copy link

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

@agronomofiorentini
Copy link

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
Copy link

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants