Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting bounding box from leaflet in R

Tags:

r

leaflet

shiny

I am using R, RStudio and the leaflet package to visualise a map.

I would like to get the the min and max lat-longs of of the bounding box of a leaflet object. I think this can be done using Shiny (by using something like input$mapobj_bounds) but is there a non-shiny method to do this.

m <- leaflet(width=500,height=400) %>% 
   addTiles() %>% 
   setView(lng = -0.106831, lat = 51.515328, zoom = 18) %>%
   addCircleMarkers(lng = -0.106831, lat = 51.515328)

What i need is a function to get the bounding box using the input argument m.

Can this be done?

Also, the parameter values when looking into the object m look incorrect.

e.g.

> m$x$limits
$lat
[1] 51.51533 51.51533

$lng
[1] -0.106831 -0.106831

EDIT

I think the javascript function map.getBounds() may be of help here...as suggested here (Get the bounding box of the visible leaflet map?), but do not know how to apply this to our problem. Any help on this would be much appreciated.

like image 482
h.l.m Avatar asked Mar 09 '23 00:03

h.l.m


2 Answers

If you adapt Jeremys original answer a bit you can actually do it without javascript:

Reproducible example:

library(magrittr)
library(leaflet)

m <- leaflet(width = 500,height = 400) %>% 
  addTiles() %>% 
  setView(lng = -0.106831, lat = 51.515328, zoom = 18) %>%
  addCircleMarkers(lng = -0.106831, lat = 51.515328)
m
getBox <- function(m){
  view <- m$x$setView
  lat <- view[[1]][1]
  lng <- view[[1]][2]
  zoom <- view[[2]]
  zoom_width <- 360 / 2^zoom
  lng_width <- m$width / 256 * zoom_width
  lat_height <- m$height / 256 * zoom_width
  return(c(lng - lng_width/2, lng + lng_width/2, lat - lat_height/2, lat + lat_height/2))
}
getBox(m)

In shiny you can simply you use: input$MAPID_bounds

Reproducible example:

library(shiny)
library(leaflet)
library(magrittr)

app <- shinyApp(

  ui = fluidPage(leafletOutput('myMap')),

  server = function(input, output) {

    output$myMap = renderLeaflet({
      leaflet() %>% 
        addTiles() %>% 
        setView(
          lng = 50, 
          lat = 10, 
          zoom = 17
        )
    })

    observeEvent(input$myMap_bounds, {
      print(input$myMap_bounds)
    })

  }
)

for more info see here: https://rstudio.github.io/leaflet/shiny.html.

Here a javscript version (initial workaround). For the better version, see above.

  leaflet() %>% addTiles()  %>% 
  setView(lng = -0.106831, lat = 51.515328, zoom = 18) %>%
  addEasyButton(easyButton(
    states = list(
      easyButtonState(
        stateName="unfrozen-markers",
        icon="ion-toggle",
        title="Get Bounding box",
        onClick = JS("
                     function(btn, map) {
                        alert(map.getBounds().getEast());
                        alert(map.getBounds().getWest());
                        alert(map.getBounds().getNorth());
                        alert(map.getBounds().getSouth());
                     }")
      )
    )
  )
)
like image 53
Tonio Liebrand Avatar answered Mar 24 '23 04:03

Tonio Liebrand


Thanks to @BigDataScientist's answer for pointing out, that width & height are available!

It is possible to calculate the bounding boxes, as long as you know the leaflet widget's dimensions. See leafletjs.com/examples/zoom-levels

Given that this is specified with leaflet(width=500,height=400), this will work.

if (is.null(m$width) | is.null(m$height)) {
    print("Leaflet width and height must be speciied")
} else {
       width <- m$width 
       height <- m$height 
       zoom <- m$x$setView[[2]]
       lng <- m$x$setView[[1]][2]
       lat <- m$x$setView[[1]][1]
       lng_width <- 360 * width / 2^(zoom + 8)
       lng_east <- lng - lng_width/2
       lng_west <- lng + lng_width/2
       lat_height <- 360 * height * cos(lat/180 * pi) / 2^(zoom + 8)
       lat_north <- lat + lat_height/2
       lat_south <- lat - lat_height/2
}

> lng_east
[1] -0.1081721
> lng_west
[1] -0.1054899
> lat_north
[1] 51.516
> lat_south
[1] 51.51466

Comparing to @BigDataScientist, this gives the same answer as map.getBounds to 3 decimal places.

enter image description here

EDIT I based my answer on the documentation from leaflet referenced. It would seem that this is a simplification. I have added the cos(lat/180 * pi) term which improves accuracy. For example, this now gives north-boundary of 51.516, which is only a difference of 0.0000029 from leaflet's 51.51599707.

I have tested this at a few different latitudes and zooms. Accuracy decreases at lower zoom levels.

like image 39
Jeremy Voisey Avatar answered Mar 24 '23 03:03

Jeremy Voisey