将带有SVG图层的leaflet地图导出为图片

3
我正在使用 Leaflet-D3's hexbin。 我想要将地图与 hexbin 图层一起导出为图像。到目前为止,我一直在使用 Leaflet-image 导出地图至图像。 然而,由于 hexbin 叠加层是 SVG 图层,因此当我使用相同的库导出地图+hexbin 时,只有地图出现在图像上。
我该怎么办才能将 hexbin 图层导出到 Leaflet-image 生成的同一图像中呢?例如,我已经看到了将 SVG 导出为图像的工具,但这在我的情况下并不完全适用,因为它只能处理 hexbin 而无法处理地图。
1个回答

3

这真的非常酷。使用此处描述的代码,您实际上可以将生成的leaflet-image与从SVG创建的图像组合在一起。

由于代码比言语更有说服力,这里是我准备的一个示例:

<!DOCTYPE html>
<html>

<head>
  <script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="https://rawgit.com/d3/d3-plugins/master/hexbin/hexbin.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css">
  <script type="text/javascript" src="https://rawgit.com/Asymmetrik/leaflet-d3/master/dist/leaflet-d3.js"></script>
  <script src="https://rawgit.com/mapbox/leaflet-image/gh-pages/leaflet-image.js"></script>
</head>

<body>

  <div id="map" style="width: 600px; height: 400px; border: 1px solid #ccc"></div>
  <button onclick="generateImage()">Create Image</button>
  <div id="images"></div>

  <script>
    var center = [39.4, -78];

    var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
      osm = L.tileLayer(osmUrl, {
        maxZoom: 18,
        attribution: osmAttrib
      });

    map = new L.Map('map', {
      layers: [osm],
      center: new L.LatLng(center[0], center[1]),
      zoom: 7
    });

    var options = {
      radius: 12,
      opacity: 0.5,
      duration: 500,
      lng: function(d) {
        return d[0];
      },
      lat: function(d) {
        return d[1];
      },
      value: function(d) {
        return d.length;
      },
      valueFloor: 0,
      valueCeil: undefined
    };

    var hexLayer = L.hexbinLayer(options).addTo(map)
    hexLayer.colorScale().range(['white', 'blue']);

    var latFn = d3.random.normal(center[0], 1);
    var longFn = d3.random.normal(center[1], 1);
    
    var generateData = function() {
      var data = [];
      for (i = 0; i < 1000; i++) {
        data.push([longFn(), latFn()]);
      }
      hexLayer.data(data);
      
      d3.selectAll('.hexbin-hexagon')
        .style({
            "stroke": '#000',
            "stroke-width": '1px'
        });
    };
    
    generateData();
    
    var getOverlay = function(){
        // Select the first svg element
        var svg = d3.select('.leaflet-overlay-pane>svg'),
            img = new Image(),
            serializer = new XMLSerializer(),
            svgStr = serializer.serializeToString(svg.node());
            
        img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
        
        return {
          img: img,
          w: +svg.attr('width'),
          h: +svg.attr('height')
        }
    };

    var generateImage = function() {
      leafletImage(map, function(err, canvas) {
        
        var d3O = getOverlay();
        canvas.getContext("2d").drawImage(d3O.img,0,0,d3O.w,d3O.h);
        
        // now you have canvas
        // example thing to do with that canvas:
        var img = document.createElement('img');
        var dimensions = map.getSize();
        img.width = dimensions.x;
        img.height = dimensions.y;
        img.src = canvas.toDataURL();
        document.getElementById('images').innerHTML = '';
        document.getElementById('images').appendChild(img);
      });
    };
  </script>
</body>

</html>

我最初的代码没有考虑缩放和平移。这是一次重写。它有点疯狂,仅在 Chrome 测试过。说实话,此时我会使用 phantomJS 在服务器端渲染,并将其捕获为 JPEG 并返回给浏览器。

<!DOCTYPE html>
<html>

<head>
  <script type="text/javascript" src="https://d3js.org/d3.v3.min.js"></script>
  <script type="text/javascript" src="https://rawgit.com/d3/d3-plugins/master/hexbin/hexbin.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css">
  <script type="text/javascript" src="https://rawgit.com/Asymmetrik/leaflet-d3/master/dist/leaflet-d3.js"></script>
  <script src="https://rawgit.com/mapbox/leaflet-image/gh-pages/leaflet-image.js"></script>
</head>

<body>

  <div id="map" style="width: 600px; height: 400px; border: 1px solid #ccc"></div>
  <button onclick="generateImage()">Create Image</button>
  <div id="images"></div>

  <script>
    var center = [39.4, -78],
        width = 600,
        height = 400;

    var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
      osm = L.tileLayer(osmUrl, {
        maxZoom: 18,
        attribution: osmAttrib
      });

    map = new L.Map('map', {
      layers: [osm],
      center: new L.LatLng(center[0], center[1]),
      zoom: 7
    });

    var options = {
      radius: 12,
      opacity: 0.5,
      duration: 500,
      lng: function(d) {
        return d[0];
      },
      lat: function(d) {
        return d[1];
      },
      value: function(d) {
        return d.length;
      },
      valueFloor: 0,
      valueCeil: undefined
    };

    var hexLayer = L.hexbinLayer(options).addTo(map)
    hexLayer.colorScale().range(['white', 'blue']);

    var latFn = d3.random.normal(center[0], 1);
    var longFn = d3.random.normal(center[1], 1);
    
    var generateData = function() {
      var data = [];
      for (i = 0; i < 1000; i++) {
        data.push([longFn(), latFn()]);
      }
      hexLayer.data(data);
    };
    
    generateData();

    var getOverlay = function(){
        // Select the first svg element
        var svg = d3.select('.leaflet-overlay-pane>svg'),
            img = new Image(),
            serializer = new XMLSerializer();
           
        svg.select("g").attr("transform", null);
        svg.style("margin-top", null);
        svg.style("margin-left", null);
        svg.attr("height", null);
        svg.attr("width", null);
        var svgStr = serializer.serializeToString(svg.node());

        img.src = 'data:image/svg+xml;base64,'+window.btoa(svgStr);
        
        return img;
    };

    var generateImage = function() {
      leafletImage(map, function(err, canvas) {
        
        var t = d3.select('.leaflet-map-pane').style('transform').split(", "),
           img = getOverlay(),
           x = parseInt(t[4]),
           y = parseInt(t[5]);

        canvas.getContext("2d").drawImage(img,
          x,
          y,
          width,
          height
        );
        
        // now you have canvas
        // example thing to do with that canvas:
        var img = document.createElement('img');
        var dimensions = map.getSize();
        img.width = dimensions.x;
        img.height = dimensions.y;
        img.src = canvas.toDataURL();
        document.getElementById('images').innerHTML = '';
        document.getElementById('images').appendChild(img);
      });
    };
  </script>
</body>

</html>


喜欢你的想法!但是它还没有完全运作起来...除了不够灵敏(看起来好像只有每两次点击才能捕捉到),最终的图像似乎与地图+六边形网格捕捉到的不同,就像你可以在这个截图中看到的那样(关注巴尔的摩)。还有另一个问题,就像你可以在这张截图中看到的那样。这是否与你的特定示例有关? - flapas
1
好的,我知道这个解决方案的问题所在。整个SVG图层被捕获,并放置在画布上方。这样做是没问题的,如果你不移动地图。如果你将地图平移到两侧,整个SVG将会“粘贴”在“非居中”的地图顶部,导致一些像我给你展示的图片那样的情况。所以我们需要和地图一样平移SVG图层。怎么做呢?没有主意... - flapas
@pavlag,这个问题应该是可以解决的。我最近几天一直在旅行。等我回来后,我会在本周内查看它。 - Mark
@pavlag,还有一件事。代码的不响应性与我无关。leaflet-image.js插件正在后台下载所有图像瓦片,这就是您看到的慢速原因。 - Mark
1
成功地让它运作并进行了一些小的修改:1- svg 图像应该使用其宽度和高度来绘制。2- y = parseInt(t[5]); 中有一个小错误,应该是 y = parseInt(t[5].split(")")[0])。请参见 saving-svg-leaflet-gist 中的完整脚本。 - Miguel Vazq
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接