Plotting thousands of points in Leaflet - a way to improve the speed?

Hi friends,

I've created a dot-density map of a particular location, which involves around 60,000 points (each point = 100 people). I was wondering if there was a way to improve the speed with which the map renders when you zoom in and out.

The map is produced using Leaflet, which I want to publish on my blogdown site. However, with 60,000 points, the map is understandably quite slow.

Here's a simplified example of what my code and map look like:

library(tidyverse)
library(leaflet)
library(sf)

# Randomly generate 60,000 coordinate pairs
set.seed(1)
num <- 60000
df <- data.frame(
        lon = runif(num, min = -97.06, max = -96.5),
        lat = runif(num, min = 32.540, max = 32.957))

# Convert to a simple feature
df <- st_as_sf(df, coords = c("lon", "lat"), crs = 4326, agr = "constant")

# Create map
leaflet() %>% 
  addProviderTiles(providers$Esri.WorldGrayCanvas) %>% 
  addCircles(data = df, weight = 0)

I was wondering if there was a way to improve this, without reducing the number of dots or using clusters?

For example, could I make it so that the points appear as an image or a tile on the map instead? (I am probably using the wrong terminology here, but hopefully you get my meaning).

Grateful for any tips you can provide. Thanks for your time.

4 Likes

You're right: 60 000 is a lot of points. I have a couple of ideas, but be warned that I'm more familiar with Leaflet itself (in JavaScript) than the R wrapper for it, so it might take a bit of work on your part to translate the examples in the Leaflet reference to the R wrapper.

If it's only performance while zooming and panning that you're concerned about, you could try adding the options updateWhenZooming: false and updateWhenIdle: true to your base map layer (it's defined in GridLayers, not vector ones like your points, but it might help):

addProviderTiles(providers$Esri.WorldGrayCanvas, options = providerTileOptions(
  updateWhenZooming = FALSE,      # map won't update tiles until zoom is done
  updateWhenIdle = TRUE           # map won't load new tiles when panning
)) %>% ...

A heavier option might be try to force the map to render using canvas, since canvas apparently does better with large numbers of points:

leaflet(options = leafletOptions(preferCanvas = TRUE)) %>% ...

Hope that helps! Let me know :slight_smile:

EDIT (July 2020): having done a decent bit of work with Leaflet and other mapping technologies in R and JavaScript now, I'd probably encourage prospective users coming to this thread with tens of thousands of points, to explore competing packages, like mapdeck. mapdeck allows R users to use Mapdeck and DeckGL.

Although they take more customisation on the JavaScript end if you want as much flexibility as Leaflet (since the R API isn't as comprehensive), they're also far more performant: DeckGL won't blink even if you're pushing hundred of thousands or possibly millions of points at it (even if you need interactivity attached to them).

13 Likes

@rensa, thank you very much for this response!

I'm away from my computer for a couple of days but will take a look at these when I get back. I did try the preferCanvas option but it did not appear to make a difference - I will have another go to ensure I did it correctly. Otherwise, I will try the updateWhen... options you suggest. Thanks again for your time, much appreciated.

No worries, @SteveXD! I've been doing a big project with Leaflet over the last few months and started hitting performance problems with raster layers, so I'm interested to see how you go.

You can verify whether you're actually using the Canvas renderer by right-clicking on the map. If it gives you image-related menu options, you're working with Canvas. You might also want t overify that you're using an up-to-date version of the leaflet R package, since it only migrated to leaflet.js 1.x semi-recently (about a month ago, I think), and that changed a lot under the hood.

Also, if you're interested in points rather than _areas, you might want to add addCircleMarkers rather than addCircles. The former are a fixed size regardless of zoom (their radii are measured in pixels rather than metres).

@rensa - perfect! It runs like a dream compared to before.

I did all the three things you suggested:

  • Updated Leaflet (from v1.1.0 to v2.0.0)
  • Set the updateWhen... options to FALSE
  • Set preferCanvas to TRUE

The map handles nicely when I run it locally in my browser without freezing like before. Thanks again!

(Should it be of interest, I've attached an image of the end product below: a dot-density map of my local area, at the block group level, where each dot = 100 people. So ~6 million people = 60k dots.).

6 Likes

Nice! That map is intense :sunglasses:

I think you may want to update your answer by changing the updateWhenIdle argument's value to FALSE.