GithubHelp home page GithubHelp logo

Comments (20)

lbusett avatar lbusett commented on August 27, 2024 2

Somehow related to this, a very useful feature could also to be able to edit the attribute table of a vector when editing it using "editFeature".

For example: consider I want to "split" an existing polygon into multiple polygons, thus creating new geometries, and that I have an "ID" column in the original vector's attributes table. It would be nice to be able to interactively assing a new "ID" to the newly created polygons before returning the updated object to "R".

I recon this may be complex, and maybe calls for a dedicated shiny/lealflet app, but I think it's a "use case" worth considering.

from mapedit.

tim-salabim avatar tim-salabim commented on August 27, 2024 1

Agreed! A very nice idea. I usually approach spatial data from the geometry side of things, not from he attribute side, so this perspective never occurred to me. But I do find this a very interesting option that I would love to see implemented.
I also agree that this aligns quite nicely with the objective of feature editing. Here's a first few points that pop into my head that (I think) need consideration:

  • do we require the user to have a sf object for this? I.e. will it be up to the user to provide some empty but valid sf geometry column? I would prefer to allow all sorts of tabular data.
    (kr) I think any tabular input as a starting point and would like for a user to not have to specify an empty geometry column. If input is sf could default to the current geometry column.
    (ta) Agreed, this will also align with the RConsortium suggestion to try to be as general as possible.

  • If we allow simple tabular data, how to go about rows with no added/drawn geometries? st_point(c(NA, NA)) as a default?
    (kr) You will know better here the best way to represent a null geometry in sf. What other options do we have?
    (ta) I think st_point(c(NA, NA)) is the easiest for mixed geometries. It would be nice to scan the table for uniqueness. E.g. in case there are only POLYGON features, we should also provide empty POLYGONs for the rows without added geometries.

  • I think we need to have methods to convert drawn leaflet geojson objects to sf class sfg/XY as in st_point(c(1, 2)) which comprise the set of simplest geometry classes in sf (e.g. POINT, LINESTRING, POLYGON and all the MULTI* variants + GEOMETRYCOLLECTION). Which, however could also help to structure the internals of mapedit more concisely.
    (kr) not sure I understand. Do you mean everything should be converted to sf?
    (ta) maybe it is I who doesn't understand. Let me wait for the poc...

  • can we come up with a general way of providing a editAttributes method that works for both plain table data as well as sf class data? Maybe some sort of edit attributes button in editMap/editFeatures to switch to editing-attributes-mode?

  • how do we layout? I.e. can we come up with a useful pane viewer layout to accommodate enough space for both map and table?
    (kr) will be tricky, since ui will hide much of the map. I wonder if the ui should exist to the side of the map.

As I said, these just popped into my head when thinking about this a little.
In any case, a draft poc would be fabulous to have some solid example case to progress from.

I very much like this idea, especially as it is something that standard GIS systems usually don't provide in an easy fashion (if at all),

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024 1

Thanks @tiernanmartin and @tim-salabim. See if this rough poc is headed in the right direction, but I just realized in this implementation if you draw first (likely) then successive edit and/or delete will be ignored. Will make the quick fix if I get positive feedback. Also, in this case, we will most likely want to turn off multiple mode in Leaflet.draw.

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

make_an_sf <- function(dat) {
  ui <- fluidPage(
    fluidRow(
      column(6,DT::dataTableOutput("tbl",width="100%", height="400px")),
      column(6,editModUI("map"))
    ),
    fluidRow(actionButton("donebtn", "Done"))
  )
  
  server <- function(input, output, session) {
    data_copy <- st_as_sf(
      dat,
      geometry = st_sfc(lapply(seq_len(nrow(dat)),function(i){st_point()}))
    )
    
    edits <- callModule(
      editMod,
      leafmap = mapview()@map,
      id = "map"
    )
    output$tbl <- DT::renderDataTable({
      DT::datatable(
        dat,
        options = list(scrollY="400px"),
        # could support multi but do single for now
        selection = "single"
      )
    })
    
    # unfortunately I did not implement last functionality
    #  for editMap, so do it the hard way
    # last seems useful, so I might circle back and add that
    EVT_DRAW <- "map_draw_new_feature"
    EVT_EDIT <- "map_draw_edited_features"
    EVT_DELETE <- "map_draw_deleted_features"
    
    nsm <- function(event="", id="map") {
      paste0(session$ns(id), "-", event)
    }
    
    observe({
      possible <- list(
        draw = input[[nsm(EVT_DRAW)]],
        edit = input[[nsm(EVT_EDIT)]],
        delete = input[[nsm(EVT_DELETE)]]
      )
      
      # get last event
      last <- Filter(Negate(is.null), possible)
      # really dislike that we need to do in R
      pos <- Position(Negate(is.null), possible)
      
      # get selected row
      selected <- isolate(input$tbl_rows_selected)
      
      skip = FALSE
      # ignore if selected is null
      #  not great but good enough for poc
      if(is.null(selected)) {skip = TRUE}
      
      # ignore if no event
      if(length(last) == 0) {skip = TRUE}
  
      # replace if draw or edit
      if(skip==FALSE && (names(possible)[pos] %in% c("edit","draw"))) {
        
        sf::st_geometry(data_copy[selected,]) <<- sf::st_geometry(
          mapedit:::st_as_sfc.geo_list(unname(last)[[1]])
        )
      }
      
      # remove if delete
      if(skip==FALSE && (names(possible)[pos] %in% c("delete"))) {
        sf::st_geometry(data_copy[selected,]) <<- sf::st_sfc(st_point())
      }
    })
    
    # provide mechanism to return after all done
    observeEvent(input$donebtn, {
      # convert to sf
      
      stopApp(st_sf(data_copy,crs=4326))
    })
  }
  
  return(runApp(shinyApp(ui,server)))
}


# let's act like breweries does not have geometries
brewsub <- breweries[,1:4,drop=TRUE]

brewpub <- make_an_sf(brewsub)

mapview(brewpub)

mapedit_poc

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024 1

@tiernanmartin oh good! I did not test with MULTI so not surpised if not working. I will play a little to see if I can get it to work, but the issue here is Leaflet.draw does not accumulate draw events, so each completed feature will be sent. To achieve multi, we would need to add some other UI to know when grouping should occur. edit and delete though are accumulated and sent as collection. I will also make change so edit and delete will work.

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024 1

Rather than copy/pasting with each iteration, I saved the code here so we can track changes. I was able to add zoom on datatable selected row.

Also, I think I have an idea for MULTI, but I need to do some testing.

@tim-salabim, I think lessons learned in this exercise will be very useful, but I am not sure how far we shoudl push this poc. Zoom/highlight will likely be necessary for edit attributes functionality.

mapedit_poc2

from mapedit.

tim-salabim avatar tim-salabim commented on August 27, 2024 1

@timelyportfolio this is coming along nicely I think! The zoom doesn't seem to work for points as, I guess, map.fitBounds(map._layers[layerid].getBounds()); doesn't find any bounds for 0 dimensional data.
Additionally, I am wondering whether a vertically split layout with 2 3rds map and 1 3rd DT would be better? Given that we work one row at a time in this setup, I feel that the table doesn't need so much space.

from mapedit.

mrjoh3 avatar mrjoh3 commented on August 27, 2024 1

I am coming to this discussion a bit late, but it is exactly the workflow I have been looking for.

Lately I have also been integrating editable DT (rstudio/DT#480) in some of my other shiny apps. I think integrating an editable DT with the app example does make the attributes editable.

I have created a pull request (fb2f63a) for the example but basically you just create a dataTableProxy take the value edits and apply them to the underlying data.frame.

    # update table with entered notes
    proxy = dataTableProxy('tbl')

    observeEvent(input$tbl_cell_edit, {

      info = input$tbl_cell_edit

      str(info)

      i = info$row
      j = info$col
      v = info$value

      info$value <- as.character(info$value)

      data_copy[i, j] <<- DT::coerceValue(v, data_copy[i, j])
      replaceData(proxy, data_copy, resetPaging = FALSE)  # important

    })

@lbusett and @timelyportfolio I am curious if this is the functionality you were after and if this experiment had progressed any further?

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

This is a very interesting idea, and I very much appreciate the suggestion. It seems like this functionality might align nicely with our yet untouched objective of attribute editing and offer some synergy with existing mapedit capabilities. I will try to assemble a quick poc.

@tim-salabim, would love to hear your thoughts.

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

@tim-salabim, I commented inline above. Thanks for listing out your thoughts. I will try to create the quickest poc I can to stimulate additional thought. http://geojson.io will be a good reference point and also

image

from mapedit.

tim-salabim avatar tim-salabim commented on August 27, 2024

Also added some comments inline above

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

One other idea would be we could use selectMap to join existing features to a data.frame. So select feature and select row to join. Return object would be sf with joined data and features.

from mapedit.

tiernanmartin avatar tiernanmartin commented on August 27, 2024

@timelyportfolio It's hard for me to imagine a more perfect poc - your gif illustrates the exact workflow I had in mind! Really nice stuff 👍

It seems that this implementation doesn't allow users to create MULTI*/GEOMETRY COLLECTION geometries - is that correct?

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

actually I am wrong. In multiple mode, the feature is returned as a collection, so might be relatively easy I think to add multi to draw. actually wrong each draw is separate

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

Ok, this allows draw, edit, delete, but I don't know a good way to easily handle multiple edit, deletes. It can be done but requires much more complicated logic. Also need to add zoom_to functionality on datatable select if a feature exists on the selected row, but that also requires more work :).

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

make_an_sf <- function(dat) {
  ui <- fluidPage(
    fluidRow(
      column(6,DT::dataTableOutput("tbl",width="100%", height="400px")),
      column(6,editModUI("map"))
    ),
    fluidRow(actionButton("donebtn", "Done"))
  )
  
  server <- function(input, output, session) {
    data_copy <- st_as_sf(
      dat,
      geometry = st_sfc(lapply(seq_len(nrow(dat)),function(i){st_point()}))
    )
    
    edits <- callModule(
      editMod,
      leafmap = mapview()@map,
      id = "map"
    )
    output$tbl <- DT::renderDataTable({
      DT::datatable(
        dat,
        options = list(scrollY="400px"),
        # could support multi but do single for now
        selection = "single"
      )
    })
    
    # unfortunately I did not implement last functionality
    #  for editMap, so do it the hard way
    # last seems useful, so I might circle back and add that
    EVT_DRAW <- "map_draw_new_feature"
    EVT_EDIT <- "map_draw_edited_features"
    EVT_DELETE <- "map_draw_deleted_features"
    
    nsm <- function(event="", id="map") {
      paste0(session$ns(id), "-", event)
    }
    
    addObserve <- function(event) {
      observeEvent(
        input[[nsm(event)]],
        {
          evt <- input[[nsm(event)]]
          # for now if edit, just consider, first feature
          #   of the FeatureCollection
          if(event == EVT_EDIT) {
            evt <- evt$features[[1]]
          }
          
          # get selected row
          selected <- isolate(input$tbl_rows_selected)
          
          skip = FALSE
          # ignore if selected is null
          #  not great but good enough for poc
          if(is.null(selected)) {skip = TRUE}
          
          # ignore if no event
          #if(length(evt) == 0) {skip = TRUE}
print(evt)      
          # replace if draw or edit
          if(skip==FALSE) {
            sf::st_geometry(data_copy[selected,]) <<- sf::st_geometry(
              mapedit:::st_as_sfc.geo_list(evt)
            )
          }
      })
    }
    
    addObserve(EVT_DRAW)
    addObserve(EVT_EDIT)
    
    observeEvent(
      input[[nsm(EVT_DELETE)]],
      {
        evt <- input[[nsm(EVT_DELETE)]]
        # get selected row
        selected <- isolate(input$tbl_rows_selected)
        
        skip = FALSE
        # ignore if selected is null
        #  not great but good enough for poc
        if(is.null(selected)) {skip = TRUE}
        
        # ignore if no event
        #if(length(last) == 0) {skip = TRUE}

        # remove if delete
        if(skip==FALSE) {
          sf::st_geometry(data_copy[selected,]) <<- st_geometry(sf::st_sfc(st_point()))
        }
      }
    )
    
    # provide mechanism to return after all done
    observeEvent(input$donebtn, {
      # convert to sf
      
      stopApp(st_sf(data_copy,crs=4326))
    })
  }
  
  return(runApp(shinyApp(ui,server)))
}


# let's act like breweries does not have geometries
brewsub <- breweries[,1:4,drop=TRUE]

brewpub <- make_an_sf(brewsub)

mapview(brewpub)

from mapedit.

tim-salabim avatar tim-salabim commented on August 27, 2024

ideally, zoom_to + highlight

from mapedit.

mdsumner avatar mdsumner commented on August 27, 2024

Wow, this is awesome.

from mapedit.

lbusett avatar lbusett commented on August 27, 2024

Hi. I just wish to add that this would be great functionality for the remote sensing community also. For example, it would allow selecting Areas of Interest over a satellite image on zones with different characteristics (e.g., different land use), to be used for example for plotting purposes (e.g., plotting time series), or as training / testing data for raster classification. These are common tasks, which are currently accomplished using QGIS, ENVI, or other RS software.

Any plans to develop it further? (sorry I can't help but I am not fluent (yet) with leaflet/shiny).

from mapedit.

tim-salabim avatar tim-salabim commented on August 27, 2024

@lbusett good point! This will be developed further, no doubt. We are currently not pushing mapedit development too much as we would like to wait until leaflet has been upgraded to use leafletjs 1.x (there's a lot of changes and a lot of new goodies). Nonetheless, I think it would be useful to have a working basic implementation of this functionality for users to test and report back so we can get a feel of what the expectations for such a tool are in real life.
Also note, that direct querying of raster data will likely not happen until package stars has been developed further so that we can query using sf (which is the infrastructure used by mapedit). Until then you will be restricted to use the raster imagery as a background map and do the extract yourself after digitizing.

from mapedit.

lbusett avatar lbusett commented on August 27, 2024

Thanks for the reply. Looking forward for this!

PS: I'm aware of the issue about not being able to "query" the raster data. Indeed, I recently developed some routines for "efficient" raster extraction starting from sf objects within a "spatial processing wrappers package" I'm currently developing.
Though the package is not yet publicly released - and is intended mostly as a "facilitator" for R-based analysis within my group, thus often replicating functionality of other packages with just a modified syntax - , maybe they can be of interest (at least until "stars" comes around):

https://lbusett.github.io/sprawl/reference/extract_rast.html
https://lbusett.github.io/sprawl/articles/articles/extract_rast_example.html

from mapedit.

timelyportfolio avatar timelyportfolio commented on August 27, 2024

@lbusett, thanks, and yes we plan to add attribute editing soon. As of now, we are waiting on Leaflet > 1. Hopefully, we will be back to work on this soon.

from mapedit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.