Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mouseHandler for addGeotiff (geoblaze.js) #28

Merged
merged 15 commits into from
Jun 4, 2024

Conversation

trafficonese
Copy link
Contributor

Using geoblaze.js allows to get the raster value at a certain lat/lng.
I modified leaflet's mouseHandler to allow click and mousemove for addGeotiff.
It could also be included in addCOG but so far I didn't manage to use that function successfully.

I'm not sure if you want to include the new dependency (~1MB) or if you have something else in mind already. So feel free to close it or request additional changes.

Shiny example!
library(leaflet)
library(leafem)
library(stars)
library(shiny)

tif = system.file("tif/L7_ETMs.tif", package = "stars")
x1 = read_stars(tif)
x1 = x1[, , , 3] # band 3
tmpfl = tempfile(fileext = ".tif")
write_stars(st_warp(x1, crs = 4326), tmpfl)
myCustomJSFunc = htmlwidgets::JS("
    pixelValuesToColorFn = (raster, colorOptions) => {
      const cols = colorOptions.palette;
      var scale = chroma.scale(cols);

      if (colorOptions.breaks !== null) {
        scale = scale.classes(colorOptions.breaks);
      }
      var pixelFunc = values => {
        let clr = scale.domain([raster.mins, raster.maxs]);
        if (isNaN(values)) return colorOptions.naColor;
        return clr(values).hex();
      };
      return pixelFunc;
    };")

ui <- fluidPage(
leafletOutput("map"),
splitLayout(cellWidths = c("50%", "50%"),
  div(h4("Click"),verbatimTextOutput("click")),
  div(h4("Mouseover"),verbatimTextOutput("mouseover"))
)
)

server <- function(input, output, session) {
output$map <- renderLeaflet({
  leaflet() %>%
    addTiles() %>%
    addGeotiff(
      file = tmpfl
      , group = "geotiffgroup"
      , layerId = "somelayerid"
      , opacity = 0.9
      , colorOptions = colorOptions(
        palette = grey.colors
        , na.color = "transparent"
      )
      , pixelValuesToColorFn = myCustomJSFunc
    )
})
output$click <- renderPrint({
  txt <- req(input$map_georaster_click)
  print(txt)
})
output$mouseover <- renderPrint({
  txt <- req(input$map_georaster_mousemove)
  print(txt)
})
}
shinyApp(ui, server)


@tim-salabim
Copy link
Member

Thanks! Yeah I've been planning to include geoblaze for exactly that use. I've added a comment in the code: "Why only for shiny?" We should provide this functionality also in standard interactive mode

@trafficonese
Copy link
Contributor Author

Do you mean to include the eventInfo in a legend similar to addImageQuery?

I'm not aware of any way to pass an object from JS to R in interactive mode, that's why HTMLWidgets.shinyMode is required.

@tim-salabim
Copy link
Member

Yeah, my intention would be to have an argument imageQuery = TRUE (or similar) and then update the innerHTML of some info div. I'll investigate in the next days... I want to get the whole addGeotiff implemented properly first, though (I've revamped pretty much all code for this yesterday) so bear with me.

@trafficonese
Copy link
Contributor Author

With fc88895 the raster values now show up in a leaflet-control, very similar to addImageQuery. The R arguments className, prefix, digits, position are still missing, but I'll wait for the revamped version.

I also included a check for the layerId to see if it contains any whitespaces. Using Shiny an error in addResourcePath appeared, but it worked in the RStudio Viewer.

@tim-salabim
Copy link
Member

Is this good to merge? I am planning to take care of all the things you've provided over the next two days.

@trafficonese
Copy link
Contributor Author

Not yet, I still have to adapt some things, but I wanted to wait for your revamped version. Is it already online?
And also enable this for addCOG.

Another thing I noticed, the examples using pixelValuesToColorFn dont work anymore, but maybe it's just my messy branch..
Otherwise mouseevents are working for addGeoTiff.

@tim-salabim
Copy link
Member

Is it already online?

No, I'll start working on cleaning the repo and merging things tomorrow. I'll let you know when I think everything is in a clean state.

@tim-salabim
Copy link
Member

@trafficonese I've merged and cleaned a few things (mainly around projecting rasters). I think the current master is in a clean enough state to start getting this PR up and running.

@trafficonese
Copy link
Contributor Author

trafficonese commented Jun 1, 2024

Nice! I got COG to work with the mouseHandler, but noticed that I have to load the COG differently.
Currently we have

parseGeoraster(url).then(georaster => {

and I changed it to

fetch(url)
  .then(response => response.arrayBuffer())
  .then(arrayBuffer => {
    parseGeoraster(arrayBuffer).then(georaster => {

to make it work (see GeoTIFF/georaster-layer-for-leaflet#104). I guess this loads the whole data in memory, so might not be desired?
I could use this approach only when imagequery is TRUE and leave the current method if no mouseevents are needed?


In 2b75a37 I added the following arguments to addGeotiff, addCOG, addGeoRaster directly. Should we wrap them up in an imagequeryOptions list? So the main function is not cluttered with arguments?

imagequery = TRUE,
className = "info legend",
position = c("topright", "topleft", "bottomleft", "bottomright"),
type = c("mousemove", "click"),
digits = NULL,
prefix = "Layer",

also.. type might not be the best word to describe the info panel trigger? I used it as addImageQuery uses the same.


Otherwise everything should work fine. 🎉

Shiny App with Mouseevents for addGeoRaster, addGeotiff and addCOG

library(shiny)  
library(leaflet)
library(leafem)

ndsi <- 'https://raw.githubusercontent.com/GeoTIFF/test-data/main/files/LisbonElevation.tif'
nsdi_stars <- stars::read_stars(ndsi)

ui <- fluidPage(
  leafletOutput("map", height = 600),
  splitLayout(cellWidths = c("50%", "50%"),
              div(h4("Click"),verbatimTextOutput("click")),
              div(h4("Mouseover"),verbatimTextOutput("mouseover"))
  )
)

server <- function(input, output, session) {
  output$map <- renderLeaflet({
    leaflet(options = leafletOptions(attributionControl = FALSE)) %>% 
      addProviderTiles("CartoDB.Positron") %>%
      addGeoRaster(
        nsdi_stars
        , group = "NSDI_stars"
        , opacity = 1
        , digits = 3
        , position = "bottomright"
        , prefix = "Local Stars:"
        , type = "click"
        , colorOptions = colorOptions(
          palette = grey.colors(256)
        )
      ) %>%
      addCOG(
        url = ndsi
        , group = "NDSI_COG"
        , opacity = 0.7
        , autozoom = TRUE
        , digits = 2
        , position = "bottomright"
        , prefix = "COG:"
        , type = "click"
        , className = "somelegend"
        , colorOptions = colorOptions(
          palette = grDevices::hcl.colors(3, "Inferno")
        )
      ) %>%
      addGeotiff(
        url = ndsi
        , group = "NDSI_geotiff"
        , opacity = 0.7
        , digits = 1
        , position = "bottomright"
        , prefix = "GeoTIFF:"
        , type = "click"
        , className = "somelegend"
        , autozoom = TRUE
        , colorOptions = colorOptions(
          palette = grDevices::hcl.colors(3, "Spectral")
        )
      ) %>%
      addMouseCoordinates() %>%
      addLayersControl(overlayGroups =  c("NDSI_COG", "NDSI_geotiff", "NSDI_stars"))
  })
  output$click <- renderPrint({
    txt <- req(input$map_georaster_click)
    print(txt)
  })
  output$mouseover <- renderPrint({
    txt <- req(input$map_georaster_mousemove)
    print(txt)
  })
}
shinyApp(ui, server)

@tim-salabim
Copy link
Member

tim-salabim commented Jun 1, 2024

Nice one! Yeah I think an imagequeryOptions list makes sense. I'd suggest to leave the type argument, but change mousemove to mouseover which is more like most other js libs refer to it. We probably need to change that in addImageQuery too then.

And regarding the fetch, I wouldn't worry for now. It may well be the preferred way of doing things now in georaster for leaflet. Let's investigate this further if people report degraded performance

@trafficonese
Copy link
Contributor Author

I'll push the changes in a bit for imagequeryOptions.

Regarding mouseover, that will not work correctly and only update when the whole map is hovered.

mouseover only fires when the bounds of an element are hovered, but leaflet doesnt recognize the events on images, so we have to continously check for the raster values with mousemove.

@tim-salabim
Copy link
Member

mouseover only fires when the bounds of an element are hovered, but leaflet doesnt recognize the events on images, so we have to continously check for the raster values with mousemove.

I understand, then mousemove is fine!

I'll take care of the merge tomorrow, if you say it's ready by then.

@trafficonese
Copy link
Contributor Author

Ok, I think I have it now.
I moved the whole addControl part on the JS-side and did a bit of cleanup.

We have the new argument imagequeryOptions with default NULL, according to how leaflet treats labelOptions.

ShinyApp

library(shiny)  
library(leaflet)
library(leafem)

ndsi <- 'https://raw.githubusercontent.com/GeoTIFF/test-data/main/files/LisbonElevation.tif'
nsdi_stars <- stars::read_stars(ndsi)

ui <- fluidPage(
  leafletOutput("map", height = 600),
  splitLayout(cellWidths = c("50%", "50%"),
              div(h4("Click"),verbatimTextOutput("click")),
              div(h4("Mouseover"),verbatimTextOutput("mouseover"))
  )
)

server <- function(input, output, session) {
  output$map <- renderLeaflet({
    leaflet(options = leafletOptions(attributionControl = FALSE)) %>% 
      addProviderTiles("CartoDB.Positron") %>%
      addGeoRaster(
        nsdi_stars
        , group = "NSDI_stars"
        , opacity = 1
        , imagequery = T
        # , imagequeryOptions = imagequeryOptions(position = "bottomright", digits = 3,
        #                                         prefix = "Local Stars:", type = "click")
        # , imagequeryOptions = NULL
        , colorOptions = colorOptions(
          palette = grey.colors(256)
        )
      ) %>%
      addCOG(
        url = ndsi
        , group = "NDSI_COG"
        , opacity = 0.7
        , autozoom = F
        # , imagequery = T
        # , imagequeryOptions = imagequeryOptions(position = "bottomright", digits = 2,
        #                                         prefix = "COG:")
        # , queryOptions = NULL
        , colorOptions = colorOptions(
          palette = grDevices::hcl.colors(3, "Inferno")
        )
      ) %>%
      addGeotiff(
        url = ndsi
        , group = "NDSI_geotiff"
        , opacity = 0.7
        , autozoom = F
        # , imagequery = T
        # , imagequeryOptions = imagequeryOptions(position = "bottomright", digits = 1,
        #                                         prefix = "GeoTIFF:", className = "somelegend")
        , colorOptions = colorOptions(
          palette = grDevices::hcl.colors(3, "Spectral")
        )
      ) %>%
      addMouseCoordinates() %>%
      addLayersControl(overlayGroups =  c("NDSI_COG", "NDSI_geotiff", "NSDI_stars"))
  })
  output$click <- renderPrint({
    txt <- req(input$map_georaster_click)
    print(txt)
  })
  output$mouseover <- renderPrint({
    txt <- req(input$map_georaster_mousemove)
    print(txt)
  })
}
shinyApp(ui, server)

@tim-salabim tim-salabim merged commit dbffd65 into r-spatial:master Jun 4, 2024
5 checks passed
@tim-salabim
Copy link
Member

Thanks! This is a really nice addition!

@trafficonese trafficonese deleted the mouseevent branch June 4, 2024 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants