【问题标题】:Exporting Leaflet map with geojson layers to png in Angular 5在Angular 5中将带有geojson图层的传单地图导出为png
【发布时间】:2018-10-10 01:22:06
【问题描述】:

我有一张带有大量 geojson 的传单地图。我希望能够将地图图像导出为 png。我已经尝试过leaflet-image插件,它可以导出基本地图,我也尝试过(独立地)html2canvas库将地图div的内容输出到png文件。

不幸的是,使用leaflet-image 输出只显示背景层(地图)而不显示任何geojson 区域,而使用html2canvas 输出结果显示层而不显示背景图。

我认为 Leaflet-image 插件不可行,因为它有太多限制。它需要将所有图层渲染到画布上,但这样做会导致性能下降——它似乎无法处理这么多的地理区域。我对服务器也没有太多的控制权,并且没有安装nodejs。

html2canvas 更有希望。使用它和文件保护程序,我能够输出完整的图像和区域,如下所示:

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

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

这非常有效,除了一个问题 - 区域在画布上呈现在错误的位置。也就是说,区域偏移是错误的。这是一个例子。地图的底部和右侧应覆盖有彩色等值线区域的边缘:

如何让区域在地图上正确渲染,而不是被剪辑到错误的位置?

编辑:我查看了 html2canvas 网站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]);
}

但它也没有工作,所以我仍在努力解决这个问题。我遇到的并不是图层偏移问题,而是图层裁剪问题。

【问题讨论】:

    标签: javascript angular leaflet html2canvas


    【解决方案1】:

    以防有人仍然像我一样寻求解决这个问题,最后能够通过传单地图图像导出解决这个 htmltocanvas 问题,如下所示, 您所要做的就是,将主 svg 的变换设置为无,然后在变换中使用相同的值重置左侧和顶部值。请参阅下面的代码示例(Angular11+)。

    import html2canvas from "html2canvas";
    ....
    html2canvas("YOUR MAP CONTAINER DIV REF".nativeElement, {
              allowTaint: true,
              useCORS: true,//important to avoid CORS related errors
              width: "YOUR MAP CONTAINER DIV REF".nativeElement.clientWidth,
              height: "YOUR MAP CONTAINER DIV REF".nativeElement.clientHeight,
              onclone: (doc) => {
                   //this is the part that solved the displacement of highlighted portion on the map
                **let svg = doc.querySelector<HTMLElement>('svg.leaflet-zoom-animated');
                const matrixValues = svg.style.transform.match(/matrix.*\((.+)\)/)[1].split(', ');
            svg.style.transform = `none`;
            svg.style.left = `${matrixValues[4]}px`;
            svg.style.top = `${matrixValues[5]}px`;**
          }
        }).then(canvas => {
          canvas.toBlob((blob) => {
            saveAs(blob);
          });
        });
    

    【讨论】:

      【解决方案2】:

      我最终使用了 dom-to-image 库,因为我将平铺图像剪切到边界框,而小册子图像没有使用 css 剪辑属性。

        // 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)`;
        };
      

      我得到了一张更大的地图图像,里面有黑框。我认为这是地图窗格的缓冲区。即使使用

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

      不适合我。

      我发现我必须设置图像宽度和高度选项以匹配地图容器客户端的宽度/高度。

        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;
        }
      

      这个库明显比 leaflet-image 慢,但它也做得更多。

      【讨论】:

        【解决方案3】:

        自从写下这个问题以来,已经过去了相当长的一段时间。最终对我有用的解决方案是购买 Telerik Reporting 并在其中实施我需要的任何映射解决方案。这是一个非常好的选择,并且一直对我有用,所以如果你有机会这样做,我会强烈推荐它。您还可以获得许多开箱即用的不同文件格式,并且该解决方案看起来更加专业。只是一个可供考虑的选项。

        【讨论】:

          【解决方案4】:

          尝试从地图中删除去渲染器填充,然后导出。它对我有用。

          在你的情况下,类似的事情

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

          【讨论】:

            【解决方案5】:

            语气说:

            地图的底部和右侧应该用彩色等值线区域覆盖到边缘:

            我找到了解决这个问题的简单方法!

            1. 设置渲染器填充值 (https://leafletjs.com/reference-1.4.0.html#renderer-padding) = 0
            2. 设置“windowWidth”值 (http://html2canvas.hertzen.com/configuration) = document.documentElement.clientWidth。

            希望对您有所帮助!祝你好运。

            【讨论】:

              【解决方案6】:

              从上面的 html2canvas 链接中,我已经做了很多工作,将 geojson 转换为将视口与背景图块结合起来。如果我移动地图,我确实让它正确映射,但当我放大时却没有。这非常棘手,如果可以避免的话,我宁愿不做。这可能是更糟糕的方法,如果传单更改,解决方案就会中断。

              dom-to-image 为我提供了从 dom 生成图像所需的一切。与 dom-to-image 给我的东西相比,Leaflet Easy Print 没有给我任何额外的东西。使用 dom-to-image 的控制台有错误,使用小册子简易打印时也会出现同样的错误。我也想自己控制图像导出。 dom-to-image 的缺点是它只适用于 chrome 和 firefox。没有 IE,没有 Edge,没有 Safari。因此,我将此作为一种可能的解决方案发布给不需要担心这些限制的人,同时希望以后能提出更好的解决方案。

              dom-to-image 是一个很好的发现,因为我也可以将它与其他对象一起使用,例如图表。感谢 Potemkin 帮助我沿着这条路走下去。

              这是一个使用 domtoimage 和文件保护程序的解决方案:

              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");
                      });
              

              Util.encodeByteArray 方法是我自己将图像字符串转换为字节数组以创建可用于导出的 blob 的方法。如果图像明显变大,就会出现问题,尽管这从未发生在我身上。可能有更好的方法来做到这一点,我目前不知道。

              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;
                  }
              }
              

              顺便说一句,我正在研究 leaflet.browser.print 的工作原理。它似乎能够产生相同的结果并且是跨浏览器的,但只提供打印。如果我能弄清楚如何导出内部图像,在 GitHub 上对其代码进行调查可能会提供一个更跨浏览器的解决方案。

              【讨论】:

                【解决方案7】:

                最近遇到同样的问题,在尝试管理 html2canvas 时也去了相同的引用主题。

                在我的情况下(也许你也是),地图是动态的,并且包含太多的图块和元素(markeclusters、饼图等)。使用 CraigVA 给出的技巧搞砸了。

                最后,我尝试了其他几个插件,发现这个很满意:

                Leaflet Easy Print

                没有故障,干净的 png 导出。

                【讨论】:

                • 好的,我去看看。
                • 好的,看一下,它在内部使用 dom-to-image。我想我会先看看这是否能解决我的问题,然后试试这个。
                • 嗯。 dom-to-image 让我担心,因为 docos 状态它无法在 IE、Safari 或 Edge 上运行。
                • 抱歉,它并不能完全解决您的问题。对于这个 Leaflet 特定情况,Dom to image 似乎是比 html2canvas 更好的解决方案,但是据我所知,要获得完美的解决方案,svg 操作似乎很痛苦。 CraigVA 技巧(在另一个提到的问题中)在我尝试时看起来几乎是相关的,有相同的故障(移位的瓷砖)。我没有很努力地修复它,但也许这是一个可以考虑的选择。
                猜你喜欢
                • 2016-12-18
                • 2019-05-03
                • 2016-02-25
                • 2012-12-17
                • 1970-01-01
                • 1970-01-01
                • 2022-07-21
                • 2016-10-20
                • 1970-01-01
                相关资源
                最近更新 更多