Plot brushing or accessing drawn shape geometry fo

2020-07-24 05:31发布

I've been looking at the nice added functionality of addDrawToolbar in Leaflet/Shiny, which allows for custom drawing of shapes etc.

But i'm interested in sub-setting spatial data by accessing the geometry of the drawn shape, how is this done? There are some efforts here and here but I can't get them to work at any rate.

I thought perhaps that the clever functionality of plot brush might work with leaflet maps but Joe Cheng suggested last year that it wasn't in place.

So, are there any developments or workarounds on this? or has anyone managed to access the geometry of a drawn rectangle using addDrawToolbar?

2条回答
你好瞎i
2楼-- · 2020-07-24 05:51

You could use the addDrawToolbar from the leaflet.extras package.

The docs are sparse but this page has the code for the Leaflet.draw shiny bindings. You can look for the lines that have Shiny.onInputChange and in your the server part of your app, use the corresponding input$event to get the data passed to the Shiny.onInputChange.

Here's a minimal example, you can draw polygons around cities and the names of the cities in the polygon will be displayed below the map:

rm(list=ls())
library(shiny)
library(leaflet)
library(leaflet.extras)

cities <- structure(list(AccentCity = c("Saint Petersburg", "Harare", "Qingdao", 
                                        "Addis Abeba", "Xian", "Anshan", "Rongcheng", "Kinshasa", "New York", 
                                        "Sydney", "Lubumbashi", "Douala", "Bayrut", "Luanda", "Ludhiana"
), Longitude = c(30.264167, 31.0447222, 120.371944, 38.749226, 
                 108.928611, 122.99, 116.364159, 15.3, -74.0063889, 151.205475, 
                 27.466667, 9.7, 35.5097222, 13.233174, 75.85), Latitude = c(59.894444, 
                                                                             -17.8177778, 36.098611, 9.024325, 34.258333, 41.123611, 23.528858, 
                                                                             -4.3, 40.7141667, -33.861481, -11.666667, 4.0502778, 33.8719444, 
                                                                             -8.836804, 30.9)), class = "data.frame", row.names = c(NA, -15L
                                                                             ), .Names = c("AccentCity", "Longitude", "Latitude"))



cities_coordinates <- SpatialPointsDataFrame(cities[,c("Longitude","Latitude")],cities)

ui <- fluidPage(
  leafletOutput("mymap"),
  textOutput("selected_cities")
)


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

  output$mymap <- renderLeaflet({
    leaflet() %>%
      setView(0,0,2) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      addMarkers(data=cities,lat=~Latitude,lng=~Longitude,label=~AccentCity) %>%
      addDrawToolbar(
        targetGroup='draw',
        polylineOptions=FALSE,
        markerOptions = FALSE,
        circleOptions = TRUE)  %>%
      addLayersControl(overlayGroups = c('draw'), options =
                         layersControlOptions(collapsed=FALSE)) 
  })

  output$selected_cities <- renderText({
    #use the draw_stop event to detect when users finished drawing
    req(input$mymap_draw_stop)
    print(input$mymap_draw_new_feature)
    feature_type <- input$mymap_draw_new_feature$properties$feature_type

    if(feature_type %in% c("rectangle","polygon")) {

      #get the coordinates of the polygon
      polygon_coordinates <- input$mymap_draw_new_feature$geometry$coordinates[[1]]

      #transform them to an sp Polygon
      drawn_polygon <- Polygon(do.call(rbind,lapply(polygon_coordinates,function(x){c(x[[1]][1],x[[2]][1])})))

      #use over from the sp package to identify selected cities
      selected_cities <- cities_coordinates %over% SpatialPolygons(list(Polygons(list(drawn_polygon),"drawn_polygon")))

      #print the name of the cities
      cities[which(!is.na(selected_cities)),"AccentCity"]
    } else if(feature_type=="circle") {
      #get the coordinates of the center of the cirle
      center_coords <- matrix(c(input$mymap_draw_new_feature$geometry$coordinates[[1]],input$mymap_draw_new_feature$geometry$coordinates[[2]]),ncol=2)

      #calculate the distance of the cities to the center
      dist_to_center <- spDistsN1(cities_coordinates,center_coords,longlat=TRUE)

      #select the cities that are closer to the center than the radius of the circle
      cities[dist_to_center < input$mymap_draw_new_feature$properties$radius/1000,"AccentCity"]
    }


  })

}

shinyApp(ui, server)

Edit: Added support in case the user draws a circle.

查看更多
女痞
3楼-- · 2020-07-24 05:54

drawing on and adapting NicE's answer above (accepted), this is what I did to subset and zonal sum a raster, using a drawn rectangle.

Crucially i had to swap over drawn_polygon <- Polygon(do.call(rbind,lapply(polygon_coordinates,function(x){**c(x[[2]][1],x[[1]][1]**)}))) to drawn_polygon <- Polygon(do.call(rbind,lapply(polygon_coordinates,function(x){**c(x[[1]][1],x[[2]][1]**)}))) to make the extract work, otherwise the extent was the wrong way around.

leaf.proj <- "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +a=6378137 +b=6378137 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"
LL <- "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"

r <- raster("myraster.tif")

ui <- fluidPage(
  leafletOutput("mymap"),
  textOutput("selected_cities")
)


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

  output$mymap <- renderLeaflet({
    leaflet() %>%
      setView(0,0,4) %>%
      addProviderTiles(providers$CartoDB.Positron) %>%
      addRasterImage(r,project=FALSE, layerId="rasimg") %>%
      addDrawToolbar(
        targetGroup='draw',
        polylineOptions=FALSE,
        markerOptions = FALSE,
        circleOptions = FALSE,
        editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions()))%>%
        addLayersControl(overlayGroups = c('draw'), options =
                         layersControlOptions(collapsed=FALSE)) 
  })

  output$rastersum <- renderText({
    #use the draw_stop event to detect when users finished drawing
    req(input$mymap_draw_stop)

    # get the coordinates of the polygon and make SpatialPolygons object
    polygon_coordinates <- input$mymap_draw_new_feature$geometry$coordinates[[1]]

    drawn_polygon <- Polygon(do.call(rbind,lapply(polygon_coordinates,function(x){c(x[[1]][1],x[[2]][1])})))
    sp <- SpatialPolygons(list(Polygons(list(drawn_polygon),"drawn_polygon")))

    # set coords as latlong then transform to leaflet projection
    proj4string(sp) <- LL
    polyre <- spTransform(sp, leaf.proj)

    e <- extract(r,polyre)
    sum(unlist(e),na.rm=T)

  })

}

runApp(shinyApp(ui, server), launch.browser = TRUE)

i included editOptions = editToolbarOptions(selectedPathOptions = selectedPathOptions()) to allow me to delete the drawn polygon but ideally i want it to auto delete when i draw a new one, which i will build in at some point.

查看更多
登录 后发表回答