【问题标题】:Export leaflet map WITH SVG layer to image将带有 SVG 图层的传单地图导出到图像
【发布时间】:2016-12-18 14:45:39
【问题描述】:

我正在使用带有 Leaflet-D3's hexbin 的 Leaflet。我想将带有 hexbin 层的地图导出到图像。对于将地图导出到图像,到目前为止我一直在使用Leaflet-image。但是,由于 hexbin 叠加层是 SVG 图层,所以当我使用同一个库导出 map+hexbin 时,图像上只会出现地图。

如何将该 hexbin 层导出到 Leaflet-image 生成的同一图像?例如,我已经看到了将 SVG 导出为图像的工具,但这在我的情况下并不完全有效,因为它只能处理 hexbin 而不是地图。

【问题讨论】:

    标签: javascript d3.js svg leaflet


    【解决方案1】:

    这实际上很酷。使用here 描述的代码,您实际上可以将生成的传单图像与从 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>

    【讨论】:

    • 爱你的想法!但是,它并没有完全正常工作......除了不是很响应(看起来它只在两次点击中捕获)之外,最终图像似乎与捕获的地图 + hexbin 不同,因为你可以see in this screenshot (focus on Baltimore)。还有另一个问题,你可以see in this other screenshot。这与您的特定示例有关吗?
    • 好的,我知道这个解决方案有什么问题。整个 SVG 层被捕获,并放在画布上。这很好用,如果您不移动地图。如果您将地图放到两侧,整个 SVG 将被“粘贴”在“未居中”地图的顶部,从而产生一些图像,例如我向您展示的图像。所以我们需要像平移地图一样平移 SVG 层。如何?不知道...
    • @pavlag,它应该是可以解决的。最近几天我一直在旅行。这周我回来时会看看。
    • @pavlag,还有一件事。代码的无响应性我什么也没做。 leaflet-image.js 插件正在下载背景中的所有图像图块,这就是您看到的速度。
    • 做了一些小的修改让它工作: 1 - svg-image 应该用它的宽度和高度来绘制。 2- y = parseInt(t[5]); 中的小错误应该是 y = parseInt(t[5].split(")")[0]。查看saving-svg-leaflet-gist中的完整脚本
    猜你喜欢
    • 2018-10-10
    • 1970-01-01
    • 2014-04-02
    • 2014-04-16
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-27
    相关资源
    最近更新 更多