简体   繁体   中英

Exporting Leaflet map with geojson layers to png in Angular 5

I have a leaflet map with a large amount of geojson. I want to be able to export the map image as a png. I have tried the leaflet-image plugin, which exports the base map, and I have also tried (independently) the html2canvas library to output the contents of the map div to a png file.

Unfortunately, outputting using leaflet-image only shows the background layer (the map) and not any of the geojson regions, whereas the outputting the results using html2canvas shows the layers but not the background map.

I don't think the leaflet-image plugin will be viable, as it has too many constraints. It requires all layers to be rendered to the canvas, but in doing so, the performance dives - it doesn't appear to be able to handle so many geo regions. I also don't have much control over the server, and nodejs is not installed there.

html2canvas is far more promising. Using that and file-saver, I was able to output the full image and regions, as follows:

html2canvas(myMap, {
            allowTaint:false,
            useCORS: true,
            width: 650,
            height: 500,

        }).then(canvas => {
            canvas.toBlob((blob) => {
                saveAs(blob, "my-map.png");
            });
        });

This worked brilliantly, except for one problem - the regions are rendering on the canvas clipped in the wrong position. That is, the region offset is wrong. Here is an example. The bottom and right of the map should be covered to the edge with coloured choropleth regions: 带有区域的传单图像,已剪裁

How do I get the regions to render correctly on the map, and not be clipped in the wrong place?

Edit: I had a look at an issue on the html2canvas site here which looked promising. Mine doesn't seem to have a transform problem as such, but there was someone that experienced the what I thought was a layer offset problem. I implemented their solution here:

function redraw() {
    var lat_tmp = map.getCenter().lat;
    var lng_tmp = map.getCenter().lng;
    map.setView([-66.22149259832975, -1.142578125]);
    setTimeout(function () {
        waitForTilesToLoad()
    }, 50000);
    map.setView([lat_tmp, lng_tmp]);
}

but it also didn't work, so I'm still trying to solve this problem. It's not really a layer offset problem I'm experiencing, but a layer clipping problem.

Had the same issue recently, also went to the same quoted topic while trying to manage html2canvas.

In my case (and maybe so do you), map was dynamic and carrying too many tiles and kind of elements (markeclusters, piecharts, etc). It was messed up using the trick given by CraigVA.

Finally, I tried several other plugins, and found this one quite satisfying:

Leaflet Easy Print

No glitch, clean png export.

I have done a fair bit of work on shifting the geojson to marry up the viewport with the background tiles, from the html2canvas link above. I did get it to correctly map if I moved the map around, but not when I zoomed. It's very tricky and something I'd prefer not to do if I can avoid it. It's probably the worse way to do this, as if leaflet changes, the solution will break.

dom-to-image gives me everything that I need with respect to producing the image from the dom. Leaflet Easy Print gives me nothing extra over what dom-to-image gives me. There are errors in the console with dom-to-image, and the same errors appear when using leaflet easy print. I also want to control the image export myself. The down side to dom-to-image is that it only works in chrome and firefox. No IE, no Edge, no Safari. So I have posted this as a possible solution for people that don't need to worry about those contraints, while hopefully coming up with a better solution later.

dom-to-image is an excellent find, because I can also use it with other objects as well, such as charts. Kudos to Potemkin for help sending me down that path.

Here is a solution using domtoimage and file-saver:

import { saveAs } from "file-saver";
import domtoimage from "dom-to-image";

domtoimage.toPng(myMapDomObject).then((myImage: string) => {
            const commaPos = myImage.indexOf(",");
            if (commaPos > 0) myImage = myImage.substring(commaPos + 1);
            const byteArray = Util.encodeByteArray(myImage);
            const blob = new Blob([byteArray], { type: "image/png" });
            saveAs(blob, "mymap.png");
        });

The Util.encodeByteArray method is my own method to convert the image string to a byte array for creating the blob that can be used for export. It has problems if the images get huge apparently, although that's never happened to me. There's probably a better way to do that, that I'm currently not aware of.

export class Util {
    static encodeByteArray(bytes: string) {
        const byteCharacters = atob(bytes);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers);
        return byteArray;
    }
}

As an aside, I am investigating how leaflet.browser.print works. It seems to be able to produce the same result and is cross browser, but only provides for printing. An investigation of its code on GitHub might provide a more cross-browser solution if I can work out how to export the internal image.

tone says:

The bottom and right of the map should be covered to the edge with coloured choropleth regions:

I found a simple solution to this problem!

  1. Set renderer padding value ( https://leafletjs.com/reference-1.4.0.html#renderer-padding ) = 0
  2. Set the "windowWidth" value ( http://html2canvas.hertzen.com/configuration ) = document.documentElement.clientWidth.

I hope this helps you! Good luck.

Try to remove de renderer padding from your map and then export. It worked for me.

In your case, something like that

myMap.getRenderer(myMap).options.padding = 0;
html2canvas(...

Since writing this question, a fair bit of time has passed. The solution that ultimately worked for me was to purchase Telerik Reporting and implement any mapping solution I needed in there. It's a very good option and has always worked for me, so if you have the chance to do so, I would highly recommend it. You also get a number of different file formats out of the box, and the solution looks significantly more professional. Just an option to consider.

I wound up using the the dom-to-image library because I am clipping tile images to a bounding box and leaflet-image was not using the css clip property.

  // Need an arrow function to preserve this context since this is being used as a callback.
  private _maskTileLayer = (layer: TileLayer, bounds: LatLngBounds): void => {
    const northWest = bounds.getNorthWest(); // top-left px latLng
    const southEast = bounds.getSouthEast(); // bottom-right px latlng
    const northWestPoint = this._map.latLngToLayerPoint(northWest);
    const southEastPoint = this._map.latLngToLayerPoint(southEast);
    const container = layer.getContainer();
    container.style.position = 'absolute';
    container.style.clip = `rect(${northWestPoint.y}px, ${southEastPoint.x}px, ${southEastPoint.y}px, ${northWestPoint.x}px)`;
  };

I was getting a larger map image with black boxes in it. I presume this was the buffering area of the map pane. Even using

map.getRenderer(map as any).options.padding = 0;

Did not work for me.

I found that I had to set the image width and height options to match the map container client width/height.

  public mapAsJpegDataUrl(map: Map): Promise<string> {
    const mapPane = map.getPane('mapPane');
    const container = map.getContainer();
    const width = container.clientWidth;
    const height = container.clientHeight;
    const dataUrl = domToImage.toJpeg(mapPane, { width, height });
    return dataUrl;
  }

This library is noticeably slower than leaflet-image but it is also doing more.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM