24

I'd like to do a small program in R for interactive visualization and modification of some raster datasets, seen as colored images. The user should open a file (from the terminal it's OK), plot it, select the points to edit with mouse clicks, and insert the new values.

So far I achieved that easily. I use the plot() function from the raster package to visualize the plot, then click() to select the points and edit their value via the terminal.

I'd like to add the ability to show the values on mouse over. I've searched for ways on how to do this, but this doesn't seem to be possible with the standard R packages. Is this correct?

In this case, I may be forced to use external packages, such as gGobi, iPlots, Shiny or Plotly. However, I'd greatly prefer to KISS and use only "standard" graphics tools, such as the raster plot() function or maybe trellis graphics objects (e.g. from rasterVis).

I understand a Shiny app would probably be best, but it takes lots of time to learn and perfect.

AF7
  • 3,160
  • 28
  • 63
  • Maybe this is useful for you http://oscarperpinan.github.io/rastervis/#interaction – Oscar Perpiñán Jun 08 '15 at 08:44
  • @OscarPerpiñán: thanks, but I already looked at the interaction capabilities of rasterVis (which is why I mentioned it in the question) and as far as I can see `identifyRaster()` does not have many additional features compared to `raster::click`, which I already use. In particular, there is no way to display data on mouse over, which is specifically what I'm trying to do. Please correct me if I'm wrong, I'd be very happy to use such a nice solution (I love `rasterVis` and use it often). – AF7 Jun 08 '15 at 08:52
  • 1
    You are right. `identifyRaster` and `click` are more or less the same (for lattice and base graphics, respectively). As far as I know what you are trying is not possible with the graphical device produced by R. I am afraid you will need some javascript code. – Oscar Perpiñán Jun 08 '15 at 21:42
  • @Oscar: I'm afraid of that too. If you happen to have any suggestion of which method (I guess some D3.js interface) would be best advisable, i.e. which one is the easiest to adapt to my necessities, they would be most welcome. – AF7 Jun 09 '15 at 07:25
  • 2
    I have no information about a solution combining javascript with raster data. You may try the `gridSVG` package to build your own solution. Perhaps [these examples](http://oscarperpinan.github.io/spacetime-vis/spatial.html#sec-3) (with point data) are useful for you: . Besides, the [htmlwidgets package](http://www.htmlwidgets.org/index.html) creates R bindings to javascript libraries but, as far as I know, none of them work with raster data. – Oscar Perpiñán Jun 09 '15 at 16:02
  • 1
    I see that the github version of the excellent `ggiraph` now contains a function `geom_sf_interactive`, which works with the `sf` package. If I understand your needs correctly, I think that this could work for you and give you interaction without Shiny. – p0bs Jun 24 '18 at 09:37
  • @pObs thaks for the input! Things have moved very fast since I posted this question: we now have `sf`, `leaflet`, `mapview`, `mapedit` etc. I think the question is a bit outdated now. Anyway, I did not know `ggiraph` had implemented `sf` objects already, nice! – AF7 Jun 25 '18 at 09:55
  • why not add your solution then to the list below? – ClimateUnboxed Jun 27 '18 at 08:03
  • @AdrianTompkins what I did back then was very similar to what @SeGa implemented, which additionally implied re-drawing the raster image multiple times (I think `sf` was not out yet). It was slow, and clumsy. – AF7 Jun 28 '18 at 08:47

2 Answers2

16

With leaflet, mapview, and leafem you can achieve something like this:

library(raster)
library(mapview)
library(leaflet)
library(leafem)

f <- system.file("external/test.grd", package="raster")
r <- raster(f)

leaflet() %>% 
  addRasterImage(r, layerId = "values") %>% 
  addMouseCoordinates() %>%
  addImageQuery(r, type="mousemove", layerId = "values")

Putting that in a shiny app you get:

library(raster)
library(mapview)
library(leaflet)
library(shiny)

f <- system.file("external/test.grd", package="raster")
r <- raster(f)

ui <- fluidPage(
  leafletOutput("map")
)

server <- function(input, output){
  output$map <- renderLeaflet({
    leaflet() %>% 
      addRasterImage(r, layerId = "values") %>% 
      addMouseCoordinates() %>%
      addImageQuery(r, type="mousemove", layerId = "values")
  })
}

shinyApp(ui, server)

The following example illustrates the idea of converting the raster to Simple Features / Shapefiles. Its not realy useable for big Files, but the labels can be designed individually, the data is editable and can easily be shown in a Table.

library(raster)
library(leaflet)
library(shiny)
library(sf)
library(DT)
library(dplyr)

## DATA
f <- system.file("external/test.grd", package="raster")
r <- raster(f)
r1 = aggregate(r, 30)

sp = st_as_sf(rasterToPolygons(r1))
cn = st_coordinates(st_transform(st_centroid(sp),4326))
sp = st_transform(sp, 4326)
sp = cbind(sp, cn)
sp$id <- 1:nrow(sp)
colnames(sp)[1] <- "value"


## UI
ui <- fluidPage(
  leafletOutput("map"),
  uiOutput("newValueUI"),
  textInput("newVal", label = "Enter new value"),
  actionButton("enter", "Enter new value"),
  hr(),
  dataTableOutput("table")
)


## SERVER
server <- function(input, output){

  ## Reactive Shapefile
  sp_react <- reactiveValues(sp = sp)
  
  ## Leaflet Map
  output$map <- renderLeaflet({
    pal= colorNumeric(topo.colors(25), sp_react$sp$value)
    leaflet() %>% 
      addPolygons(data = sp_react$sp, label= paste(
        "Lng: ", as.character(round(sp_react$sp$X,4)),
        "Lat: ", as.character(round(sp_react$sp$Y,4)),
        "Val: ", as.character(round(sp_react$sp$value,4))),
        color = ~pal(sp_react$sp$value), 
        layerId = sp_react$sp$id
      )
  })
  
  ## Observe Map Clicks
  observeEvent(input$map_shape_click, {
    
    click_id = input$map_shape_click$id
    
    click_grid <- sp_react$sp[sp_react$sp$id == click_id,]

  })
  
  ## Observe Action Button
  observeEvent(input$enter, {
    click_id <- input$map_shape_click$id
    sp_react$sp[sp_react$sp$id == click_id,]$value <- as.numeric(input$newVal)
  })

  ## Data Table
  output$table <- DT::renderDataTable({
    sp_react$sp %>% st_set_geometry(NULL) %>% 
      dplyr::select(id,X,Y,value)
  })
  proxy = dataTableProxy('table')
  
  ## Table Proxy
  observeEvent(input$map_shape_click$id, {
    req(input$map_shape_click$id)
    proxy %>% selectRows(as.numeric(input$map_shape_click$id))
  })
}

shinyApp(ui, server)
SeGa
  • 9,454
  • 3
  • 31
  • 70
  • I know this does not exactly answer my question, but I'll accept this answer anyway. I believe that with today's R packages, one would really think of doing this in leaflet/shiny, not using base `raster` or similar. – AF7 Jun 26 '18 at 15:01
  • I dont know if `addRasterImage` supports mouse-events, I didnt find anything in the manuals. So 1 idea would be to create a grid with the same resolution as the raster and do the mouse-events based on that shapefile. Either do everything with shapefiles only or get only the center points of the clicked grid and filter the raster for that cell to include the values/coordinates in the labels. – SeGa Jun 26 '18 at 15:25
  • 1
    Yeah, I'd do that too. But it gets clumsy with large shapefiles. – AF7 Jun 26 '18 at 15:36
  • 1
    I think `mapview::addImageQuery` does what you are looking for. – TimSalabim Jun 27 '18 at 13:19
  • Indeed.. Thank you @TimSalabim. I hope you dont mind, that I included it in my answer. – SeGa Jun 27 '18 at 13:25
  • Not at all! It makes the answer more relevant IMO – TimSalabim Jun 27 '18 at 13:33
  • It does definitly! But editing the values would still not be possible with rasters or? I could think of some ways for shapefiles though – SeGa Jun 27 '18 at 13:35
  • 1
    No editing is currently not possible. Though we are working on this in mapedit. Maybe we can also provide something for raster data. I'll keep this in mind. – TimSalabim Jun 27 '18 at 13:52
  • I just changed the shapefile example. The grid cells can be edited now, but its not performant, as the whole shapefile is redrawn upon entering a new value. – SeGa Jun 27 '18 at 20:20
  • Yes, that's very similar to what I did in the past. I re-drew the raster image multiple times, every time a value was edited. It was not pretty. Maybe using `sf` polygons a neater solution could be achieved. In general it is much nicer to use polygons, since you do not have to interpolate the raster. – AF7 Jun 28 '18 at 08:49
2

I give you a simple example of how to do it in R without external Java libraries, if you want Javan's features you can adapt it, but each java graphics library is different and I have never done anything similar.

set.seed(123)
mydata <- data.frame(x = runif(10), y = runif(10))

edit_plot <- function(data) {
  plot(data)

  sel <- locator(n = 1)
  if(is.null(sel)) return(TRUE)
  dd <- (data$x - sel$x)^2 + (data$y - sel$y)^2

  data[which.min(dd),] <- edit(data[which.min(dd),])
  r <- edit_plot(data)
  if(r) return(TRUE)
}
edit_plot(mydata)

To exit press Esc when locator is active.