我正在尝试使用用户定义的svg图标作为传单上的标记,但是我认为整个任务对于我的浏览器来说太繁重了。
直到现在我一直在使用L.circleMarker
,但是现在我不得不使用诸如星号,箭头,星星等标记,所以我决定将它们作为svg路径,然后将其插入而不是我的circleMarkers。为了使事情更复杂,我获得了超过30万的积分。使用circleMarkers,我可以制作一个可行的图表,虽然速度不快,但可以接受,尤其是当使用相当深的缩放比例来区分各个点时(否则,一切就像一个大斑点,没有用处)。
但是,使用svg标记时,图表的计算量变得很大,以至于浏览器只是挂起。我玩过100、1000和10000分,即使有1000分,差异也很明显。请问对此有什么解决方案,有人使用带有大量数据点的svg标记吗?我认为我的代码中正确使用了canvas,尤其是对于circleMarkers
,我可能会误会。任何帮助,高度赞赏。摘录中的代码,对底部的几行进行注释/取消注释:
return L.circleMarker(p, style(feature));
或
console.log("Starting markers.")
return L.marker(p, {
renderer: myRenderer,
icon: makeIcon('6-pointed-star', style(feature).color),
});
从circleMarkers
切换到svg
标记。
非常感谢!
PS。使用svg标记,代码会因Highlight事件而中断,但是我已经很了解发生了什么错误。.它在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>
答案 0 :(得分:6)
“画布”不会渲染您的SVG 6点标记。看一下DevTools,您会看到它们是img
标签,其中包含base-64编码的svg作为源。如果您有很多标记,这会减慢HTML渲染器的速度。
CircleMarkers呈现在画布上。
通过创建新的L.Path
子类,您可以在画布上绘制所需的任何标记,并让Leaflet发挥其最大作用。在任何其他JS代码之前进行这些传单的修改,否则它将抱怨它不是构造函数。
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);
}
});
您使用的方法与circleMarker相同
return new Marker6Point(p, style(feature));
在代码中,有2个L.canvas
实例和2个变量myRenderer
。我保留了全局变量,但仅在renderChart()
函数中构造了L.map()时才分配它。
对于演示,我使用了较大的标记区域,并使用了10000个标记。我的浏览器没有任何问题。我将属性中的size
增加到500,这样我们得到了一个半径13像素的标记,因此您可以清楚地看到星星。
我使用了最新的传单版本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>
如果速度不够快,您始终可以设置自己的图块服务器,并以不同的缩放级别(在透明的PNG文件中)将标记烘烤到图块中。并将其用作地形图块上方的单独图块层。您没有简单的弹出窗口,但是300K标记的渲染非常快。您可以为要显示的每个图层/组制作一个瓷砖叠加层。所有服务都来自同一个图块服务器。