mapview icon indicating copy to clipboard operation
mapview copied to clipboard

[feature request] use onRender or onRenderStaticComplete to read and display raster values

Open tim-salabim opened this issue 8 years ago • 10 comments

Is it possible to use onRender or onRenderStaticComplete to read and display (on mouse hover) a data matrix which we write to disk when adding a raster/stars image? @timelyportfolio @edzer we could trial this for an alpha version of a addStars function?

tim-salabim avatar Nov 24 '17 07:11 tim-salabim

With commit https://github.com/r-spatial/mapview/commit/f692478da443ea17cfb6c526dd086b2652584254 commit I am able to register the original values of the array as a data dependency to the map. The leaflet instance knows where to find the data (see screenshot) but the data is in the wrong format (console throws an error: "SyntaxError: unexpected token: identifier"). So it's basically a matter of a) getting the format of the data correct so that the leaflet instance accepts it as a valid var and b) enable some JS function to query this var when hovering over the image.

screenshot at 2017-11-25 13 16 26

tim-salabim avatar Nov 25 '17 12:11 tim-salabim

Commit https://github.com/r-spatial/mapview/commit/4956dc14c69e4a8fba1e949b9de9e5698933e6f2 now enables passing of valid JSON array to browser. With this code

library(mapview)
library(stars)

r <- raster::raster(matrix(1:9, 3, byrow = TRUE), crs = "+init=epsg:4326")
bounds <- raster::extent(raster::projectExtent(raster::projectExtent(r, crs = sp::CRS(leaflet:::epsg3857)), crs = sp::CRS(leaflet:::epsg4326)))
x = st_as_stars(r)
attr(x, "dimensions")$band = NULL
x[[1]] = x[[1]][, , 1]

map = leaflet() %>% addProviderTiles("OpenStreetMap") %>%
  mapview:::addStarsImage(x, bounds = bounds)


map <- htmlwidgets::onRender(
  map,
  paste0(
    "
    function(el, x, data) {
    
    // get the leaflet map
    var map = this; //HTMLWidgets.find('#' + el.id);
    
    // we need a new div element because we have to handle
    // the mouseover output separately
    // debugger;
    function addElement () {
    // generate new div Element
    var newDiv = $(document.createElement('div'));
    // append at end of leaflet htmlwidget container
    $(el).append(newDiv);
    //provide ID and style
    newDiv.addClass('lnlt');
    newDiv.css({
    'position': 'relative',
    'bottomleft':  '0px',
    'background-color': 'rgba(255, 255, 255, 0.7)',
    'box-shadow': '0 0 2px #bbb',
    'background-clip': 'padding-box',
    'margin': '0',
    'padding-left': '5px',
    'color': '#333',
    'font': '9px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif',
    'text-align': 'right',
    });
    return newDiv;
    }
    
    // check for already existing lnlt class to not duplicate
    var lnlt = $(el).find('.lnlt');
    
    if(!lnlt.length) {
    lnlt = addElement();
    map.on('mousemove', function (e) {
    lnlt.text(stars);
    });
    };
    }
    "
  )
)

map

we can now show the data array in the small strip at the top of the map. array 'stars' is recognised as a valid var (see console output on right).

screenshot at 2017-11-26 13 10 22

Which leaves us with a proper way of querying and displaying the data array when hovering over the image. This is where we really need help from JS experts such as @timelyportfolio or @bhaskarvk or anyone else who would be able to realise this.

tim-salabim avatar Nov 26 '17 12:11 tim-salabim

Why not add a control and change its contents on mouseover?

bhaskarvk avatar Nov 27 '17 01:11 bhaskarvk

Check example 5 http://rpubs.com/bhaskarvk/esri-leaflet-image-map-layers

bhaskarvk avatar Nov 27 '17 01:11 bhaskarvk

Will start taking a look into this.

timelyportfolio avatar Jan 01 '18 14:01 timelyportfolio

@timelyportfolio, no need. I have a working version of this. Will push as soon as I'm back from skiing. May need help to adapt to leaflet > 1 though at some stage.

tim-salabim avatar Jan 01 '18 14:01 tim-salabim

@tim-salabim ok great. I think we might need to make the data dependency a little more robust and I have some ideas but will wait to see what you have before proceeding.

timelyportfolio avatar Jan 01 '18 15:01 timelyportfolio

This commit provides a first implementation of addImageQuery to display image values on mouseover or click.

library(mapview)
library(stars)
library(raster)

kili_data <- system.file("extdata", "kiliNDVI.tif", package = "mapview")
kiliNDVI <- stack(kili_data)

# kiliNDVI = projectRaster(kiliNDVI, crs = "+init=epsg:4326")

kili_stars_1 = st_as_stars(kiliNDVI[[1]])
kili_stars_2 = st_as_stars(kiliNDVI[[12]])

map = leaflet() %>% addProviderTiles("OpenStreetMap") %>%
  mapview:::addStarsImage(kili_stars_1, colors = "Greens",
                          group = "k_1", layerId = "k_1") %>%
  mapview:::addStarsImage(kili_stars_2, colors = "Greens",
                          group = "kili_2", layerId = "kili_2") %>%
  mapview:::addImageQuery(kili_stars_1, group = "k_1", layerId = "k_1",
                          digits = 3, position = "bottomleft") %>%
  mapview:::addImageQuery(kili_stars_2, group = "kili_2", layerId = "kili_2",
                          position = "bottomleft") %>%
  addLayersControl(overlayGroups = c("k_1", "kili_2"),
                   position = "topleft") %>%
  addControl("test")

map

There is still an issue if we provide the data in geographic coordinates (commented out). The mousemove event doesn't stop properly when leaving the image.

@timelyportfolio I'd be greatful if you could have a look especially at the javascript part. This was developed by my cousin, nicknamed "joda", so I called it joda.js (which has a very nice ring to it).

tim-salabim avatar Jan 06 '18 16:01 tim-salabim

Great, having a look now and will respond over the week. Recruiting family to open source - love it.

timelyportfolio avatar Jan 07 '18 13:01 timelyportfolio

This is now by-default used when rendering raster layers with mapview. addImageQuery actually allows the user to choose whether the query should be done/displayed on click or mousemove, mapview implements the latter as default (which is currently not changeable). Especially for stacks this can be a bit much, as it will automatically query all available layers of the stack at once, even though only one is seen. I think that it would be ideal to have only the values of the visible layer displayed on mouseover and only show values of all layers of the stack on click. I don't consider this top priority, but it would be nice to have nonetheless. I will try to adapt the JS code accordingly which will be a challenge for me but also be a good way to learn more JS! Any help, thoughts more than welcome.

tim-salabim avatar Jan 23 '18 08:01 tim-salabim