Search code examples
rleafletr-leaflet

Adding just an arbitrary image to a leaflet map


I am trying to use leaflet to show a smaller map than usual so I don't want to use the normal tiling system. I don't care about smooth zooming and loading higher resolution tiles when needed. Instead I am trying to add a raster image from an image file. Lets say this file that comes up when I google "hand drawn map"

So I try

download.file('https://external-preview.redd.it/7tYT__KHEh8FBKO6bsqPgC02OgLCHAFVPyjdVZI4bms.jpg?auto=webp&s=ff2fa2e448bb92c4ed6c049133f80370f306acb3',
              destfile = 'map.jpg')
map = raster::raster('map.jpg')

# it seems like i need a projection to use a raster image.
# not sure what controls do I have over this, especially in
# absence of a proper map layer and it's likely
# part of the solution
crs(map) = CRS("+init=epsg:4326")

leaflet() %>%
    leaflet::addRasterImage(map)

The resulting output is nothing like the input image

crappy map

How do I take an arbitrary image and place in on a leaflet map?


Solution

  • I failed to find the exact reason why addRasterImage fails here but I found reports that it doesn't behave well on L.CRS.Simple projection, which is what you'll want to use to show a simple rectangle image.

    Using htmlwidgets::onRender makes it possible to directly use the javascript function L.imageOverlay to add the image you want

    library(leaflet)
    
    # minimal custom image
    imageURL = 'https://external-preview.redd.it/7tYT__KHEh8FBKO6bsqPgC02OgLCHAFVPyjdVZI4bms.jpg?auto=webp&s=ff2fa2e448bb92c4ed6c049133f80370f306acb3'
    
    # get image data. we'll use this to set the image size
    imageData = 
        magick::image_read(imageURL) %>% image_info()
    
    leaflet(options = leafletOptions(crs = leafletCRS('L.CRS.Simple'))) %>% 
                htmlwidgets::onRender(glue::glue("
          function(el, x) {
            var myMap = this;
            var imageUrl = '<imageURL>';
            var imageBounds = [[<-imageData$height/2>,<-imageData$width/2>], [<imageData$height/2>,<imageData$width/2>]];
    
            L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
          }
          ",.open = '<', .close = '>'))
    

    For a large image like this if you want to make the image smaller you can either scale down using the imageBounds in javascript side or set minZoom to a negative value and use setView to start out zoomed out.

    leaflet(options = 
                leafletOptions(crs = leafletCRS('L.CRS.Simple'),
                               minZoom = -1)) %>% 
        setView(0,0,zoom = -1) %>%
                htmlwidgets::onRender(glue::glue("
          function(el, x) {
            var myMap = this;
            var imageUrl = '<imageURL>';
            var imageBounds = [[<-imageData$height/2>,<-imageData$width/2>], [<imageData$height/2>,<imageData$width/2>]];
    
            L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
          }
          ",.open = '<', .close = '>'))