Edit existing feature within Shiny and save
RossPitman opened this issue · comments
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)
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
@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.
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?
@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)
I didn't know there was a pull coming for this. Thanks so much @timelyportfolio. Really appreciate this amazing package!
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)
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)