Leaflet.js:使用自定义 svg 标记太慢(还有很多要点) [英] leaflet.js: too slow with custom svg markers (and a lot of points)

查看:51
本文介绍了Leaflet.js:使用自定义 svg 标记太慢(还有很多要点)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用一些用户定义的 svg 图标作为传单上的标记,但我认为整个任务对我的浏览器来说太重了.

直到现在我一直在使用 L.circleMarker 但我现在必须使用星号、箭头、星星等标记,所以我决定将它们作为 svg 路径,然后插入它们而不是我的圆圈标记.为了让事情更复杂,我有超过 300K 点.使用 circleMarkers,我能够制作一个可行的图表,虽然速度不是很快,但完全可以接受,尤其是当使用相当深的缩放来区分各个点时(否则一切都像一个大斑点,研究无用).

然而,使用 svg 标记,图表的计算量变得如此之大,以至于浏览器会挂起.我玩过 100、1000 和 10000 点,即使是 1000 点,差异也很明显.请问有什么解决方案吗,有没有人使用过带有大量数据点的svg标记?我认为我的代码中正确使用了画布,特别是对于 circleMarkers,但我可能会误会.任何帮助高度赞赏.代码片段中的代码,注释/取消注释底部的几行:

return L.circleMarker(p, style(feature));

console.log("起始标记.")返回 L.marker(p, {渲染器:myRenderer,图标:makeIcon('6-pointed-star', style(feature).color),});

circleMarkers 切换到 svg 标记.非常感谢!

附注.使用 svg 标记,代码会因突出显示事件而中断,但我已经完全理解出了什么问题..它与 circleMarkers

一起工作正常

<html lang="zh-cn"><头><meta charset="utf-8"><title>图表</title><风格>#工具提示{位置:绝对;背景颜色:#2B292E;白颜色;字体系列:无衬线;字体大小:15px;指针事件:无;/*不要在工具提示上触发事件*/填充:15px 20px 10px 20px;文本对齐:居中;不透明度:0;边框半径:4px;}html,正文{高度:100%;边距:0;}#地图 {宽度:600px;高度:600px;}</风格><!-- 参考 style.css --><!-- <link rel="stylesheet" type="text/css" href="style.css">--><!-- D3 的参考缩小版--><script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script><link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css"/><script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script><身体><div id="地图"></div><脚本>var 数据 = [];var NumOfPoints = 100for (让 i = 0; i 90 ?'#6068F0':y >80 ?'#6B64DC':y >70 ?'#7660C9':y >60 ?'#815CB6':y >50 ?'#8C58A3':y >40 ?'#985490':y >30 ?'#A3507C':y >20 ?'#AE4C69':y >10 ?'#B94856':y >0 ?'#C44443':'#D04030';}//计算半径,使所得圆按面积成比例函数 getRadius(y) {r = Math.sqrt(y/Math.PI)返回 r;}//这是非常重要的!使用画布,否则图表对于浏览器来说太重//点数太高,在这种情况下,我们有大约 300K 点要绘制var myRenderer = L.canvas({填充:0.5});//创建样式,从色带中选取fillColor功能风格(功能){返回 {半径:getRadius(feature.properties.size),fillColor: getColor(feature.properties.year),颜色:#000",权重:0,不透明度:1,填充不透明度:0.9,渲染器:myRenderer};}//创建高亮样式,颜色更深,半径更大功能亮点风格(功能){返回 {半径:getRadius(feature.properties.size) + 1.5,填充颜​​色:#FFCE00",颜色:#FFCE00",重量:1,不透明度:1,填充不透明度:0.9};}//将样式和弹出窗口附加到标记层功能亮点点(e){var 层 = e.target;dotStyleHighlight = highlightStyle(layer.feature);layer.setStyle(dotStyleHighlight);如果 (!L.Browser.ie && !L.Browser.opera) {layer.bringToFront();}}函数 resetDotHighlight(e) {var 层 = e.target;dotStyleDefault = style(layer.feature);layer.setStyle(dotStyleDefault);}函数 onEachDot(特征,层){层上({鼠标悬停:highlightDot,mouseout:resetDotHighlight});var popup = '<table style="width:110px"><tbody><tr><td><div><b>标记:</b></div></td>

'+ 特性.properties.popup +'</div></td></tr><tr类><td><div><b>组:</b></div></td><td><div>'+ 特性.properties.year +'</div></td></tr><tr><td><div><b>X:</b></div></td>;td><div>'+ feature.geometry.coordinates[0] +'</div></td></tr><tr><td><div><b>Y:</b></div></td>;td><div>'+ feature.geometry.coordinates[1] +'</div></td></tr></tbody></table>'layer.bindPopup(弹出);}功能makeIcon(名称,颜色){如果(名称==钻石"){//这是标记的 SVGvar icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +"<path stroke=" + "'" + 颜色 + "'" + " stroke-width='3' fill='none' " +" d='M10,1 5,10 10,19, 15,10Z'/</svg>";}//基于 http://www.smiffysplace.com/stars.htmlif (name == "6-pointed-star") {//这是标记的 SVGvar icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +"<path stroke=" + "'" + 颜色 + "'" + " stroke-width='3' fill='none' " +d ='M13 13m0 5l53.6599999999999966升-0.6700000000000017 -6.159999999999997l5.670000000000002 -2.5l-5.670000000000002 -2.5l0.6700000000000017 -6.159999999999997l -5-3.6599999999999966升-5- -3.6599999999999966l0.67000000000000176.159999999999997升-5.670000000000002 2.5l5.6700000000000022.5升-0.6700000000000017 6.159999999999997z'/></svg>";}//这里是技巧,base64 编码 URLvar svgURL = "data:image/svg+xml;base64," + btoa(icon);//创建图标var svgIcon = L.icon({iconUrl: svgURL,图标大小:[20, 20],阴影大小:[12, 10],iconAnchor: [5, 5],popupAnchor: [5, -5]});返回 svgIcon}函数渲染图表(数据){var myDots = make_dots(data);var minZoom = 0,maxZoom = 15;var map = L.map('地图', {minZoom: minZoom,最大变焦:最大变焦}).setView([0.5, 0.5], 10);L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {连续世界:假,最小缩放:0,noWrap: 真}).addTo(map);var myRenderer = L.canvas({填充:0.5});//定义一个数组来保存 layerGroupsvar dotlayer = [];//创建标记层并将其显示在地图上for (var i = 0; i < myDots.length; i += 1) {dotlayer[i] = L.geoJson(myDots[i], {pointToLayer:函数(特征,纬度){var p = latlng;//返回 L.circleMarker(p, style(feature));console.log("起始标记.")返回 L.marker(p, {渲染器:myRenderer,图标:makeIcon('6-pointed-star', style(feature).color),});},onEachFeature:onEachDot}).addTo(地图);}var cl = L.control.layers(null, {}).addTo(map);for (j = 0; j </html>

解决方案

Canvas 不会呈现您的 svg 6 点标记.看看 DevTools,你会看到它们是 img 标签,以 base-64 编码的 svg 作为源.如果您有大量标记,这会降低 HTML 渲染器的速度.

CircleMarkers 在画布上呈现.

通过创建一个新的 L.Path 子类,您可以在画布上绘制您想要的任何标记,并让传单发挥其最擅长的作用.在任何其他 JS 代码之前进行这些小册子修改,否则它会抱怨它不是构造函数.

L.Canvas.include({_updateMarker6Point:函数(层){if (!this._drawing || layer._empty()) { return;}var p = layer._point,ctx = this._ctx,r = Math.max(Math.round(layer._radius), 1);this._drawnLayers[layer._leaflet_id] = 层;ctx.beginPath();ctx.moveTo(p.x + r , p.y );ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);ctx.lineTo(p.x, p.y + 0.50 * r);ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);ctx.lineTo(p.x - r, p.y );ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);ctx.lineTo(p.x, p.y - 0.50 * r);ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);ctx.closePath();this._fillStroke(ctx, layer);}});var Marker6Point = L.CircleMarker.extend({_updatePath: 函数 () {this._renderer._updateMarker6Point(this);}});

您可以像使用 circleMarker 一样使用它

return new Marker6Point(p, style(feature));

在代码中有 2 个 L.canvas 实例和 2 个变量 myRenderer.我保留了全局变量,但仅在 renderChart() 函数中构造了 L.map() 时才分配它.

在演示中,我使用了带有标记的更大区域并使用了 10000 个标记.我的浏览器没有问题.我将属性中的 size 增加到 500,这样我们就得到了一个半径为 13 像素的标记,这样你就可以清楚地看到星星了.

我使用了最新的传单版本 1.3.3.

<html lang="zh-cn"><头><meta charset="utf-8"><title>图表</title><风格>#工具提示{位置:绝对;背景颜色:#2B292E;白颜色;字体系列:无衬线;字体大小:15px;指针事件:无;/*不要在工具提示上触发事件*/填充:15px 20px 10px 20px;文本对齐:居中;不透明度:0;边框半径:4px;}html,正文{高度:100%;边距:0;}#地图 {宽度:600px;高度:600px;}</风格><!-- 参考 style.css --><!-- <link rel="stylesheet" type="text/css" href="style.css">--><!-- D3 的参考缩小版--><script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script><script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script><link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css"/><script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script><身体><div id="地图"></div><脚本>L.Canvas.include({_updateMarker6Point:函数(层){if (!this._drawing || layer._empty()) { return;}var p = layer._point,ctx = this._ctx,r = Math.max(Math.round(layer._radius), 1);this._drawnLayers[layer._leaflet_id] = 层;ctx.beginPath();ctx.moveTo(p.x + r , p.y );ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);ctx.lineTo(p.x, p.y + 0.50 * r);ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);ctx.lineTo(p.x - r, p.y );ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);ctx.lineTo(p.x, p.y - 0.50 * r);ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);ctx.closePath();this._fillStroke(ctx, layer);}});var Marker6Point = L.CircleMarker.extend({_updatePath: 函数 () {this._renderer._updateMarker6Point(this);}});var 数据 = [];var NumOfPoints = 10000;for (让 i = 0; i 90 ?'#6068F0':y >80 ?'#6B64DC':y >70 ?'#7660C9':y >60 ?'#815CB6':y >50 ?'#8C58A3':y >40 ?'#985490':y >30 ?'#A3507C':y >20 ?'#AE4C69':y >10 ?'#B94856':y >0 ?'#C44443':'#D04030';}//计算半径,使所得圆按面积成比例函数 getRadius(y) {r = Math.sqrt(y/Math.PI)返回 r;}//这是非常重要的!使用画布,否则图表对于浏览器来说太重//点数太高,在这种情况下,我们有大约 300K 点要绘制var myRenderer;//= L.canvas({//填充:0.5//});//创建样式,从色带中选取fillColor功能风格(功能){返回 {半径:getRadius(feature.properties.size),fillColor: getColor(feature.properties.year),颜色:#000",权重:0,不透明度:1,填充不透明度:0.9,渲染器:myRenderer};}//创建高亮样式,颜色更深,半径更大功能亮点风格(功能){返回 {半径:getRadius(feature.properties.size) + 1.5,填充颜​​色:#FFCE00",颜色:#FFCE00",重量:1,不透明度:1,填充不透明度:0.9};}//将样式和弹出窗口附加到标记层功能亮点点(e){var 层 = e.target;dotStyleHighlight = highlightStyle(layer.feature);layer.setStyle(dotStyleHighlight);如果 (!L.Browser.ie && !L.Browser.opera) {layer.bringToFront();}}函数 resetDotHighlight(e) {var 层 = e.target;dotStyleDefault = style(layer.feature);layer.setStyle(dotStyleDefault);}函数 onEachDot(特征,层){层上({鼠标悬停:highlightDot,mouseout:resetDotHighlight});var popup = '<table style="width:110px"><tbody><tr><td><div><b>标记:</b></div></td>

'+ 特性.properties.popup +'</div></td></tr><tr类><td><div><b>组:</b></div></td><td><div>'+ 特性.properties.year +'</div></td></tr><tr><td><div><b>X:</b></div></td>;td><div>'+ feature.geometry.coordinates[0] +'</div></td></tr><tr><td><div><b>Y:</b></div></td>;td><div>'+ feature.geometry.coordinates[1] +'</div></td></tr></tbody></table>'layer.bindPopup(弹出);}功能makeIcon(名称,颜色){如果(名称==钻石"){//这是标记的 SVGvar icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +"<path stroke=" + "'" + 颜色 + "'" + " stroke-width='3' fill='none' " +" d='M10,1 5,10 10,19, 15,10Z'/</svg>";}//基于 http://www.smiffysplace.com/stars.htmlif (name == "6-pointed-star") {//这是标记的 SVGvar icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +"<path stroke=" + "'" + 颜色 + "'" + " stroke-width='3' fill='none' " +" d='m13 13m0 5l5 3.66l-0.67 -6.16l5.67 -2.5l-5.67 -2.5l0.67 -6.16l-5 3.66l-5 -3.66l0.67 6.16l-5.65.672l.67-0.67 6.16z'/></svg>";}//这里是技巧,base64 编码 URLvar svgURL = "data:image/svg+xml;base64," + btoa(icon);//创建图标var svgIcon = L.icon({图标网址:svgURL,图标大小:[20, 20],阴影大小:[12, 10],iconAnchor: [5, 5],popupAnchor: [5, -5]});返回 svgIcon}函数渲染图表(数据){var myDots = make_dots(data);var minZoom = 0,maxZoom = 15;var map = L.map('地图', {minZoom: minZoom,最大变焦:最大变焦}).setView([0.5, 0.5], 5);L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {连续世界:假,最小缩放:0,noWrap: 真}).addTo(地图);myRenderer = L.canvas({ padding: 0.5 });//定义一个数组来保存 layerGroupsvar dotlayer = [];//创建标记层并将其显示在地图上for (var i = 0; i < myDots.length; i += 1) {dotlayer[i] = L.geoJson(myDots[i], {pointToLayer:函数(特征,纬度){var p = latlng;//返回 L.circleMarker(p, style(feature));//console.log("起始标记.")//返回 L.marker(p, {//渲染器: myRenderer,//icon: makeIcon('6-pointed-star', style(feature).color),//});return new Marker6Point(p, style(feature));},onEachFeature:onEachDot}).addTo(地图);}var cl = L.control.layers(null, {}).addTo(map);for (j = 0; j </html>

如果这还不够快,您可以随时设置自己的图块服务器并将标记烘焙到不同缩放级别的图块中(在透明的 PNG 文件中).并将其用作地形图块顶部的单独图块层.您没有简单的弹出窗口,但 300K 标记的渲染速度非常快.您可以为要显示的每个图层/组制作图块叠加.所有服务都来自同一个磁贴服务器.

I am trying to use some user defined svg icons for markers on leaflet but I think the whole task gets too heavy for my browser.

Until now I was using L.circleMarker but I now have to use markers like asterisks, arrows, stars etc instead so I decided to do them as svg path and then plug them in instead of my circleMarkers. To make things more complicated I have more than 300K points. With the circleMarkers I was able to make a workable chart, not lightning fast but quite acceptable especially when a fairly deep zoom was used to be able to distinguish individual points (otherwise everything was like a big blob and useless to study).

With the svg markers however the chart becomes so computationally heavy that the browser just hangs. I have played around with 100, 1000 and 10000 points and even with 1000 points the difference becomes apparent. Is there any solution to this please, has anyone used svg markers with lots of data points? I think canvas is properly employed in my code, especially for the circleMarkers but I might be mistaken. Any help highly appreciated. Code in the snippet, comment/uncomment the few lines towards the bottom:

return L.circleMarker(p, style(feature));

or

console.log("Starting markers.")
return L.marker(p, {
    renderer: myRenderer,
    icon: makeIcon('6-pointed-star', style(feature).color),
    });

to switch from the circleMarkers to svg markers. Many thanks!

PS. With svg markers the code breaks with the highlight event but I have quite understood whats wrong..it works fine with circleMarkers

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">

<title>Chart</title>
    <style>
        #tooltip {
          position:absolute;
          background-color: #2B292E;
          color: white;
          font-family: sans-serif;
          font-size: 15px;
          pointer-events: none; /*dont trigger events on the tooltip*/
          padding: 15px 20px 10px 20px;
          text-align: center;
          opacity: 0;
          border-radius: 4px;
        }

		html, body {
			height: 100%;
			margin: 0;
		}
		#map {
			width: 600px;
			height: 600px;
		}
    </style>

<!-- Reference style.css -->
<!--    <link rel="stylesheet" type="text/css" href="style.css">-->

<!-- Reference minified version of D3 -->
    <script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script>
</head>

<body>



<div id="map"></div>


<script>
    var data = [];
    var NumOfPoints = 100
    for (let i = 0; i < NumOfPoints; i++) {
        data.push({
            num: i,
            x: Math.random(),
            y: Math.random(),
            year: Math.floor(100*Math.random())
        })
    }
    
    renderChart(data);


    function make_dots(data) {
        var arr = [];
        var nest = d3.nest()
            .key(function (d) {
                return Math.floor(d.year / 10);;
            })
            .entries(data);

        for (var k = 0; k < nest.length; ++k) {
            arr[k] = helper(nest[k].values);
        }
        return arr;
    }

    function helper(data) {
        dots = {
            type: "FeatureCollection",
            features: []
        };
        for (var i = 0; i < data.length; ++i) {
            x = data[i].x;
            y = data[i].y;
            var g = {
                "type": "Point",
                "coordinates": [x, y]
            };

            //create feature properties
            var p = {
                "id": i,
                "popup": "Dot_" + i,
                "year": parseInt(data[i].year),
                "size": 30 // Fixed size
            };

            //create features with proper geojson structure
            dots.features.push({
                "geometry": g,
                "type": "Feature",
                "properties": p
            });
        }
        return dots;
    }


    //////////////////////////////////////////////////////////////////////////////////////////////
    //styling and displaying the data as circle markers//
    //////////////////////////////////////////////////////////////////////////////////////////////

    //create color ramp
    function getColor(y) {
        return y > 90 ? '#6068F0' :
            y > 80 ? '#6B64DC' :
            y > 70 ? '#7660C9' :
            y > 60 ? '#815CB6' :
            y > 50 ? '#8C58A3' :
            y > 40 ? '#985490' :
            y > 30 ? '#A3507C' :
            y > 20 ? '#AE4C69' :
            y > 10 ? '#B94856' :
            y > 0 ? '#C44443' :
            '#D04030';
    }

    //calculate radius so that resulting circles will be proportional by area
    function getRadius(y) {
        r = Math.sqrt(y / Math.PI)
        return r;
    }

    // This is very important! Use a canvas otherwise the chart is too heavy for the browser when
    // the number of points is too high, as in this case where we have around 300K points to plot
    var myRenderer = L.canvas({
        padding: 0.5
    });

    //create style, with fillColor picked from color ramp
    function style(feature) {
        return {
            radius: getRadius(feature.properties.size),
            fillColor: getColor(feature.properties.year),
            color: "#000",
            weight: 0,
            opacity: 1,
            fillOpacity: 0.9,
            renderer: myRenderer
        };
    }

    //create highlight style, with darker color and larger radius
    function highlightStyle(feature) {
        return {
            radius: getRadius(feature.properties.size) + 1.5,
            fillColor: "#FFCE00",
            color: "#FFCE00",
            weight: 1,
            opacity: 1,
            fillOpacity: 0.9
        };
    }

    //attach styles and popups to the marker layer
    function highlightDot(e) {
        var layer = e.target;
        dotStyleHighlight = highlightStyle(layer.feature);
        layer.setStyle(dotStyleHighlight);
        if (!L.Browser.ie && !L.Browser.opera) {
            layer.bringToFront();
        }
    }

    function resetDotHighlight(e) {
        var layer = e.target;
        dotStyleDefault = style(layer.feature);
        layer.setStyle(dotStyleDefault);
    }

    function onEachDot(feature, layer) {
        layer.on({
            mouseover: highlightDot,
            mouseout: resetDotHighlight
        });
        var popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker:</b></div></td><td><div>' + feature.properties.popup +
            '</div></td></tr><tr class><td><div><b>Group:</b></div></td><td><div>' + feature.properties.year +
            '</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' + feature.geometry.coordinates[0] +
            '</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>' + feature.geometry.coordinates[1] +
            '</div></td></tr></tbody></table>'

        layer.bindPopup(popup);
    }
    
    function makeIcon(name, color) {

    if (name == "diamond") {
        // here's the SVG for the marker
        var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +
            "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
            " d='M10,1 5,10 10,19, 15,10Z'/></svg>";
    }


    // Based on http://www.smiffysplace.com/stars.html
    if (name == "6-pointed-star") {
        // here's the SVG for the marker
        var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +
            "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
            " d='m13 13m0 5l5 3.6599999999999966l-0.6700000000000017 -6.159999999999997l5.670000000000002 -2.5l-5.670000000000002 -2.5l0.6700000000000017 -6.159999999999997l-5 3.6599999999999966l-5 -3.6599999999999966l0.6700000000000017 6.159999999999997l-5.670000000000002 2.5l5.670000000000002 2.5l-0.6700000000000017 6.159999999999997z'/></svg>";

    }


    // here's the trick, base64 encode the URL
    var svgURL = "data:image/svg+xml;base64," + btoa(icon);

    // create icon
    var svgIcon = L.icon({
        iconUrl: svgURL,
        iconSize: [20, 20],
        shadowSize: [12, 10],
        iconAnchor: [5, 5],
        popupAnchor: [5, -5]
    });

    return svgIcon
}
    

    function renderChart(data) {
        var myDots = make_dots(data);

        var minZoom = 0,
            maxZoom = 15;

        var map = L.map('map', {
            minZoom: minZoom,
            maxZoom: maxZoom
        }).setView([0.5, 0.5], 10);

        L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
            continuousWorld: false,
            minZoom: 0,
            noWrap: true
        }).addTo(map);

        var myRenderer = L.canvas({
            padding: 0.5
        });

        // Define an array to keep layerGroups
        var dotlayer = [];

        //create marker layer and display it on the map
        for (var i = 0; i < myDots.length; i += 1) {
            dotlayer[i] = L.geoJson(myDots[i], {
                pointToLayer: function (feature, latlng) {
                    var p = latlng;
//                    return L.circleMarker(p, style(feature));
                    console.log("Starting markers.")
                    return L.marker(p, {
                        renderer: myRenderer,
                        icon: makeIcon('6-pointed-star', style(feature).color),
                    });
                },
                onEachFeature: onEachDot
            }).addTo(map);
        }


        var cl = L.control.layers(null, {}).addTo(map);
        for (j = 0; j < dotlayer.length; j += 1) {
            var name = "Group " + j + "0-" + j + "9";
            cl.addOverlay(dotlayer[j], name);
        }

    }


</script>
</body>

</html>

解决方案

Your svg 6-point markers are not rendered by the Canvas. Have a look with the DevTools and you see they are img tags with a base-64 encoded svg as source. If you have a large number of markers this will slow down the HTML renderer.

The CircleMarkers are rendered on the canvas.

By creating a new L.Path subclass you can draw any marker you want on the canvas and let leaflet do what it does best. Make these leaflet modifications before any other JS code otherwise it will complain that it is not a constructor.

L.Canvas.include({
    _updateMarker6Point: function (layer) {
        if (!this._drawing || layer._empty()) { return; }

        var p = layer._point,
            ctx = this._ctx,
            r = Math.max(Math.round(layer._radius), 1);

        this._drawnLayers[layer._leaflet_id] = layer;

        ctx.beginPath();
        ctx.moveTo(p.x + r     , p.y );
        ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);
        ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);
        ctx.lineTo(p.x         , p.y + 0.50 * r);
        ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);
        ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);
        ctx.lineTo(p.x -      r, p.y );
        ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);
        ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);
        ctx.lineTo(p.x         , p.y - 0.50 * r);
        ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);
        ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);
        ctx.closePath();
        this._fillStroke(ctx, layer);
    }
});

var Marker6Point = L.CircleMarker.extend({
    _updatePath: function () {
        this._renderer._updateMarker6Point(this);
    }
});

You use it the same as for the circleMarker

return new Marker6Point(p, style(feature));

In the code there are 2 L.canvas instances and there are 2 variables myRenderer. I have kept the global variable but assign it only when there is an L.map() constructed in the renderChart() function.

For the demo I have used a larger area with markers and used 10000 markers. My browser has no problems with that. I have increased the size in the properties to 500 so we get a marker with radius 13 pixels, so you can see the star clearly.

I have used the latest leaflet version 1.3.3.

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">

<title>Chart</title>
<style>
#tooltip {
    position:absolute;
    background-color: #2B292E;
    color: white;
    font-family: sans-serif;
    font-size: 15px;
    pointer-events: none; /*dont trigger events on the tooltip*/
    padding: 15px 20px 10px 20px;
    text-align: center;
    opacity: 0;
    border-radius: 4px;
}

html, body {
    height: 100%;
    margin: 0;
}
#map {
    width: 600px;
    height: 600px;
}
</style>

<!-- Reference style.css -->
<!--    <link rel="stylesheet" type="text/css" href="style.css">-->

<!-- Reference minified version of D3 -->
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.3/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.3.3/dist/leaflet.js"></script>
</head>

<body>
<div id="map"></div>
<script>
L.Canvas.include({
    _updateMarker6Point: function (layer) {
        if (!this._drawing || layer._empty()) { return; }

        var p = layer._point,
            ctx = this._ctx,
            r = Math.max(Math.round(layer._radius), 1);

        this._drawnLayers[layer._leaflet_id] = layer;

        ctx.beginPath();
        ctx.moveTo(p.x + r     , p.y );
        ctx.lineTo(p.x + 0.43*r, p.y + 0.25 * r);
        ctx.lineTo(p.x + 0.50*r, p.y + 0.87 * r);
        ctx.lineTo(p.x         , p.y + 0.50 * r);
        ctx.lineTo(p.x - 0.50*r, p.y + 0.87 * r);
        ctx.lineTo(p.x - 0.43*r, p.y + 0.25 * r);
        ctx.lineTo(p.x -      r, p.y );
        ctx.lineTo(p.x - 0.43*r, p.y - 0.25 * r);
        ctx.lineTo(p.x - 0.50*r, p.y - 0.87 * r);
        ctx.lineTo(p.x         , p.y - 0.50 * r);
        ctx.lineTo(p.x + 0.50*r, p.y - 0.87 * r);
        ctx.lineTo(p.x + 0.43*r, p.y - 0.25 * r);
        ctx.closePath();
        this._fillStroke(ctx, layer);
    }
});

var Marker6Point = L.CircleMarker.extend({
    _updatePath: function () {
        this._renderer._updateMarker6Point(this);
    }
});

var data = [];
var NumOfPoints = 10000;
for (let i = 0; i < NumOfPoints; i++) {
    data.push({
        num: i,
        x: Math.random()*60,
        y: Math.random()*60,
        year: Math.floor(100*Math.random())
    })
}

renderChart(data);

function make_dots(data) {
    var arr = [];
    var nest = d3.nest()
        .key(function (d) {
            return Math.floor(d.year / 10);
        })
        .entries(data);

    for (var k = 0; k < nest.length; ++k) {
        arr[k] = helper(nest[k].values);
    }
    return arr;
}

function helper(data) {
    dots = {
        type: "FeatureCollection",
        features: []
    };
    for (var i = 0; i < data.length; ++i) {
        x = data[i].x;
        y = data[i].y;
        var g = {
            "type": "Point",
            "coordinates": [x, y]
        };

        //create feature properties
        var p = {
            "id": i,
            "popup": "Dot_" + i,
            "year": parseInt(data[i].year),
            "size": 500 // Fixed size circle radius=~13
        };

        //create features with proper geojson structure
        dots.features.push({
            "geometry": g,
            "type": "Feature",
            "properties": p
        });
    }
    return dots;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//styling and displaying the data as circle markers//
//////////////////////////////////////////////////////////////////////////////////////////////

//create color ramp
function getColor(y) {
    return y > 90 ? '#6068F0' :
        y > 80 ? '#6B64DC' :
        y > 70 ? '#7660C9' :
        y > 60 ? '#815CB6' :
        y > 50 ? '#8C58A3' :
        y > 40 ? '#985490' :
        y > 30 ? '#A3507C' :
        y > 20 ? '#AE4C69' :
        y > 10 ? '#B94856' :
        y > 0 ? '#C44443' :
        '#D04030';
}

//calculate radius so that resulting circles will be proportional by area
function getRadius(y) {
    r = Math.sqrt(y / Math.PI)
    return r;
}

// This is very important! Use a canvas otherwise the chart is too heavy for the browser when
// the number of points is too high, as in this case where we have around 300K points to plot
var myRenderer;
//  = L.canvas({
//     padding: 0.5
// });

//create style, with fillColor picked from color ramp
function style(feature) {
    return {
        radius: getRadius(feature.properties.size),
        fillColor: getColor(feature.properties.year),
        color: "#000",
        weight: 0,
        opacity: 1,
        fillOpacity: 0.9,
        renderer: myRenderer
    };
}

//create highlight style, with darker color and larger radius
function highlightStyle(feature) {
    return {
        radius: getRadius(feature.properties.size) + 1.5,
        fillColor: "#FFCE00",
        color: "#FFCE00",
        weight: 1,
        opacity: 1,
        fillOpacity: 0.9
    };
}

//attach styles and popups to the marker layer
function highlightDot(e) {
    var layer = e.target;
    dotStyleHighlight = highlightStyle(layer.feature);
    layer.setStyle(dotStyleHighlight);
    if (!L.Browser.ie && !L.Browser.opera) {
        layer.bringToFront();
    }
}

function resetDotHighlight(e) {
    var layer = e.target;
    dotStyleDefault = style(layer.feature);
    layer.setStyle(dotStyleDefault);
}

function onEachDot(feature, layer) {
    layer.on({
        mouseover: highlightDot,
        mouseout: resetDotHighlight
    });
    var popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker:</b></div></td><td><div>' + feature.properties.popup +
        '</div></td></tr><tr class><td><div><b>Group:</b></div></td><td><div>' + feature.properties.year +
        '</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' + feature.geometry.coordinates[0] +
        '</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>' + feature.geometry.coordinates[1] +
        '</div></td></tr></tbody></table>'

    layer.bindPopup(popup);
}

function makeIcon(name, color) {

    if (name == "diamond") {
        // here's the SVG for the marker
        var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='20' height='20'> " +
            "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
            " d='M10,1 5,10 10,19, 15,10Z'/></svg>";
    }

    // Based on http://www.smiffysplace.com/stars.html
    if (name == "6-pointed-star") {
        // here's the SVG for the marker
        var icon = "<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='28' height='28'> " +
            "<path stroke=" + "'" + color + "'" + " stroke-width='3' fill='none' " +
            " d='m13 13m0 5l5 3.66l-0.67 -6.16l5.67 -2.5l-5.67 -2.5l0.67 -6.16l-5 3.66l-5 -3.66l0.67 6.16l-5.67 2.5l5.67 2.5l-0.67 6.16z'/></svg>";
    }

    // here's the trick, base64 encode the URL
    var svgURL = "data:image/svg+xml;base64," + btoa(icon);

    // create icon
    var svgIcon = L.icon({
        iconUrl: svgURL,
        iconSize: [20, 20],
        shadowSize: [12, 10],
        iconAnchor: [5, 5],
        popupAnchor: [5, -5]
    });

    return svgIcon
}

function renderChart(data) {
    var myDots = make_dots(data);

    var minZoom = 0,
        maxZoom = 15;

    var map = L.map('map', {
        minZoom: minZoom,
        maxZoom: maxZoom
    }).setView([0.5, 0.5], 5);

    L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
        continuousWorld: false,
        minZoom: 0,
        noWrap: true
    }).addTo(map);

    myRenderer = L.canvas({ padding: 0.5 });

    // Define an array to keep layerGroups
    var dotlayer = [];

    //create marker layer and display it on the map
    for (var i = 0; i < myDots.length; i += 1) {
        dotlayer[i] = L.geoJson(myDots[i], {
            pointToLayer: function (feature, latlng) {
                var p = latlng;
                // return L.circleMarker(p, style(feature));

                // console.log("Starting markers.")
                // return L.marker(p, {
                //     renderer: myRenderer,
                //     icon: makeIcon('6-pointed-star', style(feature).color),
                // });

                return new Marker6Point(p, style(feature));
            },
            onEachFeature: onEachDot
        }).addTo(map);
    }
    var cl = L.control.layers(null, {}).addTo(map);
    for (j = 0; j < dotlayer.length; j += 1) {
        var name = "Group " + j + "0-" + j + "9";
        cl.addOverlay(dotlayer[j], name);
    }
}
</script>
</body>

</html>

If this is not fast enough you can always set up your own tile server and bake the markers into the tiles at the different zoom levels (in transparent PNG files). And use this as a separate tile layer on top of the terrain tiles. You don't have easy popups but the rendering of 300K markers is pretty fast. You can make a tile overlay for each layer/group you want to show. All serviced from the same tile server.

这篇关于Leaflet.js:使用自定义 svg 标记太慢(还有很多要点)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆