knitr::opts_chunk$set(echo = TRUE)
The R ecosystem offers a powerful set of packages for geospatial analysis. For a comprehensive list see the CRAN Task View: Analysis of Spatial Data. Yet, many geospatial workflows require interactivity for smooth uninterrupted completion. With new tools, such as htmlwidgets, shiny, and crosstalk, we can now inject this useful interactivity without leaving the R environment. In the first phase of the mapedit
project, we have focused on experimenting and creating proof of concepts for the following three objectives:
drawing, editing, and deleting features,
selecting and querying of features and map regions,
editing attributes.
To run the code in the following discussion, please install with devtools::install_github
. Please be aware that the current functionality is strictly a proof of concept, and the API will change rapidly and dramatically.
devtools::install_github("r-spatial/mapedit")
We would like to set up an easy process for CRUD (create, read, update, and delete) of map features. The function editMap
demonstrates a first step toward this goal.
To see how we might add some features, let's start with a blank map, and then feel free to draw, edit, and delete with the Leaflet.Draw
toolbar on the map. Once finished drawing simply press "Done".
library(leaflet) library(mapedit) library(mapview) what_we_created <- leaflet() %>% addTiles() %>% editMap()
editMap
returns a list
with drawn, edited, deleted, and finished features as GeoJSON
. In this case, if we would like to see our finished creation we can focus on what_we_created$finished
. Since this is GeoJSON
, the easiest way to see what we just created will be to use the addGeoJSON
function from leaflet
. This works well with polylines, polygons, rectangles, and points, but circles will be treated as points without some additional code. In future versions of the API it is likely that mapedit
will return simple features gemometries rather than geojson by default.
mapview(what_we_created$finished)
As an extension of the first proof of concept, we might like to edit and/or delete existing features. Let's play Donald Trump for this exercise and use the border between Mexico and the United States for California and Arizona. For the sake of the example, let's use a simplified polyline as our border. As we have promised we want to build a wall, but if we could just move the border a little in some places, we might be able to ease construction.
library(sf) # simplified border for purpose of exercise border <- st_as_sfc( "LINESTRING(-109.050197582692 31.3535554844322, -109.050197582692 31.3535554844322, -111.071681957692 31.3723176640684, -111.071681957692 31.3723176640684, -114.807033520192 32.509681296831, -114.807033520192 32.509681296831, -114.741115551442 32.750242384668, -114.741115551442 32.750242384668, -117.158107738942 32.5652527715121, -117.158107738942 32.5652527715121)" ) %>% st_set_crs(4326) # plot quickly for visual inspection plot(border)
Since we are Trump, we can do what we want, so let's edit the line to our liking. We will use mapview
for our interactive map since it by default gives us an OpenTopoMap layer and the develop
branch includes preliminary simple features support. With our new border and fence, we will avoid the difficult mountains and get a little extra beachfront.
# use develop branch of mapview with simple features support # devtools::install_github("environmentalinformatics-marburg/mapview@develop") library(mapview) new_borders <- mapview(border)@map %>% editMap("border")
Now, we can quickly inspect our new borders and then send the coordinates to the wall construction company.
mapview(new_borders$drawn)
If you played enough with the border example, you might notice a couple of glitches and missing functionality. This is a good time for a reminder that this is alpha and intended as a proof of concept. Please provide feedback, so that we can insure a quality final product. In this case, the older version of Leaflet.Draw
in RStudio Viewer has some bugs, so clicking an existing point creates a new one rather than allowing editing of that point. Also, the returned list
from editMap
has no knowledge of the provided features.
The newest version of leaflet
provides crosstalk
support, but support is currently limited to addCircleMarkers
. This functionality is enhanced by the sf
use of list columns and integration with dplyr
verbs. Here is a quick example with the breweries91
data from mapview
.
library(crosstalk) library(mapview) library(sf) library(shiny) library(dplyr) # convert breweries91 from mapview into simple features # and add a Century column that we will use for selection brew_sf <- st_as_sf(breweries91) %>% mutate(century = floor(founded/100)*100) %>% filter(!is.na(century)) %>% mutate(id=1:n()) pts <- SharedData$new(brew_sf, key = ~id, group = "grp1") ui <- fluidPage( fluidRow( column(4, filter_slider(id="filterselect", label="Century Founded", sharedData=pts, column=~century, step=50)), column(6, leafletOutput("leaflet1")) ), h4("Selected points"), verbatimTextOutput("selectedpoints") ) server <- function(input, output, session) { # unfortunatly create SharedData again for scope pts <- SharedData$new(brew_sf, key = ~id, group = "grp1") lf <- leaflet(pts) %>% addTiles() %>% addMarkers() not_rendered <- TRUE # hack to only draw leaflet once output$leaflet1 <- renderLeaflet({ if(req(not_rendered,cancelOutput=TRUE)) { not_rendered <- FALSE lf } }) output$selectedpoints <- renderPrint({ df <- pts$data(withSelection = TRUE) cat(nrow(df), "observation(s) selected\n\n") str(dplyr::glimpse(df)) }) } shinyApp(ui, server)
With mapedit
, we would like to enhance the geospatial crosstalk
integration to extend beyond leaflet::addCircleMarkers
. In addition, we would like to provide an interactive interface to the geometric operations of sf
, such as st_intersects()
, st_difference()
, and st_contains()
.
As a select/query proof of concept, assume we want to interactively select some US states for additional analysis. We will build off Bhaskar Karambelkar's leaflet projection example using Bob Rudis albersusa
package.
# use @bhaskarvk USA Albers with leaflet code # https://bhaskarvk.github.io/leaflet/examples/proj4Leaflet.html #devtools::install_github("hrbrmstr/albersusa") library(albersusa) library(sf) library(leaflet) library(mapedit) spdf <- usa_composite() %>% st_as_sf() pal <- colorNumeric( palette = "Blues", domain = spdf$pop_2014 ) bounds <- c(-125, 24 ,-75, 45) (lf <- leaflet( options= leafletOptions( worldCopyJump = FALSE, crs=leafletCRS( crsClass="L.Proj.CRS", code='EPSG:2163', proj4def='+proj=laea +lat_0=45 +lon_0=-100 +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs', resolutions = c(65536, 32768, 16384, 8192, 4096, 2048,1024, 512, 256, 128) ))) %>% fitBounds(bounds[1], bounds[2], bounds[3], bounds[4]) %>% setMaxBounds(bounds[1], bounds[2], bounds[3], bounds[4]) %>% addPolygons( data=spdf, weight = 1, color = "#000000", # adding group necessary for identification group = ~iso_3166_2, fillColor=~pal(pop_2014), fillOpacity=0.7, label=~stringr::str_c(name,' ', format(pop_2014, big.mark=",")), labelOptions= labelOptions(direction = 'auto')#, #highlightOptions = highlightOptions( # color='#00ff00', bringToFront = TRUE, sendToBack = TRUE) ) ) # test out selectMap with albers example selectMap( lf, styleFalse = list(weight = 1), styleTrue = list(weight = 4) )
The selectMap()
function will return a data.frame
with an id
/group column and a selected
column. selectMap()
will work with nearly all leaflet overlays and offers the ability to customize the styling of selected and unselected features.
A common task in geospatial analysis involves editing or adding feature attributes. While much of this can be accomplished in the R console, an interactive UI on a reference map can often help perform this task. Mapbox's geojson.io
provides a good reference point for some of the features we would like to provide in mapedit
.
As a proof of concept, we made a Shiny app that thinly wraps a slightly modified geojson.io
. Currently, we will have to pretend that there is a mechanism to load R feature data onto the map, since this functionality does not yet exist.
library(shiny) edited_features <- runGitHub( "geojson.io", "timelyportfolio", ref="shiny" )
mapedit
hopes to add useful interactivity to your geospatial workflows by leveraging powerful new functionality in R with the interactivity of HTML, JavaScript, and CSS. mapedit
will be better with your feedback, requests, bug reports, use cases, and participation. We will report on progress periodically with blog posts on this site, and we will develop openly on the mapedit
Github repo.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.