Exporting Leaflet Map to Image in the Browser
Tags: javascript, webdev, leaflet, maps
Published: 2019-07-03 13:04:58

---
title: Exporting Leaflet Map to Image in the Browser
published: true
description: How to configure a Leaflet map and use dom-to-image to create a static map image
tags: javascript,webdev,leaflet,maps
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ki5n3myycemz1145pssh.jpg
---

[Leaflet](https://leafletjs.com) is a popular JavaScript map component library with an extensive ecosystem of plugins and integrations. I have been using it in several projects and it works really well as a UI component. At one point, I needed to produce a static image of the map with some overlays, which is something that Leaflet doesn't support out of the box, nor has a plugin that worked for me. Since the images would be produced based on user input, they had to be generated on the spot and not created in advance. My options were:

* Use a third party service to generate the map
* Write server code that would run a headless browser (eg. with [Puppeteer](https://github.com/GoogleChrome/puppeteer)) and take a snapshot of the map
* Find some way to take a "snapshot" of the map in the client code running in the browser

The last option was preferable because it doesn't rely on anything outside the app scope running in the browser. The solution came from [dom-to-image](https://github.com/tsayen/dom-to-image), a JavaScript library which [magically](https://github.com/tsayen/dom-to-image#how-it-works) does exactly what I needed.

Trying to create a Leaflet image and use dom-to-image did not work straightforward, but it was relatively simple to get it to produce a proper image. In the following example, based on [an example](https://leafletjs.com/examples/quick-start/example-overlays.html) from Leaflet docs, I will explain the required steps.

## Creating and configuring the Leaflet map
By default Leaflet maps include zoom control and attribution. The zoom control is definitely not needed in the image, and you may want to have the attribution outside the image as text or not at all (depending on your tile provider requirements). Additionally by default, there are animations for tile loading and zooming, which we want to avoid and get only the "final" state of the map, so we need to disable the animations.

To achieve these, we create the map like so:

```javascript
const map = L.map(
  attributionControl: false,
  zoomControl: false,
  fadeAnimation: false,
  zoomAnimation: false
})
```

Then add whatever layers and overlays you need. In our example, we use [OpenStreetMap](https://www.openstreetmap.org) tiles and we add a marker, a circle and a polygon (see the complete example below).

## Waiting for the tiles to load

The synchronous DOM and Leaflet operations do not guarantee that the map content is in its final desired form. Right after the map is added to the DOM, the tiles begin to download and we need to produce the image only after all the tiles have finished downloading. Leaflet tile layers fire the `load` event when all the tiles have been downloaded and are displayed, so we can use that to know when we can continue:

```javascript
const tileLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
  .addTo(map)

// ... Additional map operations ...

tileLayer.on("load", () => { /* produce the image */ })

```

I prefer working with async/await rather than callbacks, so I "turned" the use of callback into a promise like so:

```javascript
await new Promise(resolve => tileLayer.on("load", () => resolve()))
```

## Using dom-to-image to produce the image

Now that we know the map component is in the desired state, we can export it to an image. To get a PNG Data URL we would use dom-to-image `toPng` function. Note that you need to specify the same width and height as you did for the Leaflet element:

```javascript
const dataURL = await domtoimage.toPng(mapElement, { width, height })
```

You can also export to a blob and download it using [FileSaver](https://github.com/eligrey/FileSaver.js/) for example:

```javascript
const blob = await domtoimage.toBlob(mapElement, { width, height })
saveAs(blob, 'map.png')
```

## The complete example

{%codesandbox c57w6 %}

An important point is to make sure the intermediate Leaflet map is not visible to the user, otherwise it will look like flicker. You can use absolute positioning and z-index to position the intermediate map element below the app UI elements (but don't try to place it outside the viewport or to set its display to none - these will prevent rendering the map content).

Although this post covered the case of Leaflet, a similar approach can be used with other map components and other components with no built-in image export functionality - make sure the component is fully loaded, use event listeners if needed, and then use dom-to-image to generate the image.