I'm facing a performance problem with Leaflet (version 0.7.3). I'm working with an OSM map that I use to display a bunch of CircleMarkers linked by decorated Polylines (with arrow pattern every 25px). Loading take a little time but the main problem is that when I zoom the map I start facing severe lag (from the zoom level 16) and, beyond a certain limit (say 18 most of the time), browser just freeze and eventually crash (tested with chrome and firefox). I tried with a bunch of 1,000 linked markers, then I dropped to a set of around 100, but still the same concern... Of course, with 10 markers or less I don't have any problem.
Did you already face a similar trouble? How can I optimize Leaflet performances so that I can use an accurate zoom (beyond level 16) with more than 100 linked CircleMarkers ? I also wonder why performances are dropping so badly when zooming, while marker amount stay the same...
Thank you in advance for your answers,
Lenalys.
Cannot get the PolylineDecorator plugin to work on jsfiddle.
But here is the code that generate markers :
Map initialization :
var map;
function initializeMap(){
"use strict";
var layer;
var layer2;
function layerUrl(key, layer) {
return "http://wxs.ign.fr/" + key
+ "/geoportail/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&"
+ "LAYER=" + layer + "&STYLE=normal&TILEMATRIXSET=PM&"
+ "TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2Fjpeg";
}
layer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png',
{
attribution: '© OpenStreetMap contributors',
maxZoom: 18
});
layer2 = L.tileLayer(
layerUrl(IGN_AMBIENTE_KEY, "GEOGRAPHICALGRIDSYSTEMS.MAPS"),
{attribution: '© IGN'}
);
var baseMaps = {
"Terrestre": layer,
"Bathymetrique": layer2
};
map = L.map('map', {
layers: [layer],
zoom: 8,
center: [42.152796, 9.139150],
zoomControl: false
});
L.control.layers(baseMaps).addTo(map);
//add zoom control with your options
L.control.zoom({
position:'topright' //topleft
}).addTo(map);
L.control.scale({
position:'bottomleft',
imperial : false
}).addTo(map);
}
Data Sample :
var jsonData ={"12":[{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.42362","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 15:37:12"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.42365","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 16:42:16"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657381666667","longitude":"9.4236933333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 17:47:21"},
{"id_stm_device":"7","individual_name":"cerf3","latitude":"42.657283333333","longitude":"9.4237383333333","temperature":null,"pulse":null,"battery":"20","date_time":"2015-03-17 19:57:23"}],
"13":[{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.61683","longitude":"9.4804633333333","temperature":"17.45","pulse":null,"battery":"80","date_time":"2015-04-08 07:45:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.538858333333","longitude":"9.48169","temperature":"14.37","pulse":null,"battery":"80","date_time":"2015-04-08 08:00:29"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.458748333333","longitude":"9.500225","temperature":"14.46","pulse":null,"battery":"80","date_time":"2015-04-08 08:15:49"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.3302","longitude":"9.5374583333333","temperature":"15.19","pulse":null,"battery":"80","date_time":"2015-04-08 08:31:05"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.170133333333","longitude":"9.5272116666667","temperature":"15.48","pulse":null,"battery":"80","date_time":"2015-04-08 08:46:20"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07959","longitude":"9.47688","temperature":"15.97","pulse":null,"battery":"80","date_time":"2015-04-08 09:01:31"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.076163333333","longitude":"9.4828633333333","temperature":"20.42","pulse":null,"battery":"80","date_time":"2015-04-08 09:16:59"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07194","longitude":"9.4908866666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:32:17"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072583333333","longitude":"9.4901516666667","temperature":"17.36","pulse":null,"battery":"80","date_time":"2015-04-08 09:47:32"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.07238","longitude":"9.4904266666667","temperature":"19.38","pulse":null,"battery":"80","date_time":"2015-04-08 10:02:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.072298333333","longitude":"9.4904983333333","temperature":"17.46","pulse":null,"battery":"80","date_time":"2015-04-08 10:17:55"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.095093333333","longitude":"9.5148383333333","temperature":"17.47","pulse":null,"battery":"80","date_time":"2015-04-08 10:33:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112881666667","longitude":"9.5133133333333","temperature":"19.3","pulse":null,"battery":"80","date_time":"2015-04-08 10:48:23"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.112875","longitude":"9.513285","temperature":"22.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:03:57"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.141096666667","longitude":"9.5078216666667","temperature":"23.73","pulse":null,"battery":"80","date_time":"2015-04-08 11:19:12"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.282186666667","longitude":"9.5505183333333","temperature":"18.97","pulse":null,"battery":"80","date_time":"2015-04-08 11:34:28"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.405126666667","longitude":"9.531145","temperature":"20.71","pulse":null,"battery":"80","date_time":"2015-04-08 11:49:42"},
{"id_stm_device":"8","individual_name":"cerf5","latitude":"42.482063333333","longitude":"9.480665","temperature":"21.7","pulse":null,"battery":"80","date_time":"2015-04-08 12:05:07"}]}
var oJSON = JSON.parse(jsonData);
var colors = [
"#400080",
"#008000",
"#EC7600",
"#E40341",
"#0D5E5E",
"#919191",
"#FF3C9D",
"#A70A0E",
"#00BFBF",
"#7171FF"
];
var classes = [
"color1",
"color2",
"color3",
"color4",
"color5",
"color6",
"color7",
"color8",
"color9",
"color10"
];
var lastMarkers = [];
var layers = new Array();
var polyline;
var decorator;
window.graphicsDevices = [];
var offsetLatitude = 0.003333;
var offsetLongitude = 0.011666;
Marker instanciation :
function printGPSOnMap(oJSON){
var nbKeys = 0;
for (var key in oJSON) {
nbKeys++;
var classe = classes[(key-1)%classes.length];
var color = colors[(key-1)%colors.length];
var positionInfo = [];
if (oJSON.hasOwnProperty(key)) {
var aInfo = oJSON[key];
var marker;
var latlngs = Array();
var startMarker = lastMarkers[key];
if(startMarker !== undefined && startMarker != null) {
var myIcon = L.divIcon({className: "myCircle "+classe, iconSize : [ 20, 20 ] });
startMarker.setIcon(myIcon);
latlngs.push(startMarker.getLatLng());
}
for(var i = 0; i < aInfo.length; i++) {
var oInfos = aInfo[i];
var sIdIndividual = oInfos["id_individual"];
var sLongitude = oInfos["longitude"];
var sLatitude = oInfos["latitude"];
var sTemperature = oInfos["temperature"];
var sPulse = oInfos["pulse"];
var sBattery = oInfos["battery"];
var sDatetime = oInfos["date_time"];
var sIndividualName = oInfos["individual_name"];
var id_device = oInfos["id_stm_device"];
var popupMsg = "...";
latlngs.push(L.marker([sLatitude,sLongitude]).getLatLng());
marker = new MyCustomMarker([sLatitude,sLongitude], {
icon : L.divIcon({
className : "myCircle "+classe + ((i == aInfo.length-1) ? ' myCircleEnd' : ''),
iconSize : [ 20, 20 ]
})
});
marker.bindPopup(popupMsg, {
showOnMouseOver: true
});
marker.bindLabel(key, {
noHide: true,
direction: 'middle',
offset: [offset[0], offset[1]]
});
positionInfo.push(marker);
}
lastMarkers[key] = marker;
}
if(latlngs.length > 1)
{
polyline = L.polyline(latlngs, {className: classe, weight: 2,opacity: 0.4}).addTo(map);
decorator = L.polylineDecorator(polyline, {
patterns: [
// define a pattern of 10px-wide arrows, repeated every 20px on the line
{offset: 0, repeat: '25px', symbol: new L.Symbol.arrowHead({pixelSize: 10, pathOptions: {fillOpacity:
0.76, color: color, weight: 1}})}
]}).addTo(map);
}
if(!window.graphicsDevices.hasOwnProperty(key))
window.graphicsDevices[key] = [];
for(var i = 0; i < positionInfo.length; i++) {
window.graphicsDevices[key].push(positionInfo[i]);
positionInfo[i].addTo(map);
if(latlngs.length > 1){
window.graphicsDevices[key].push(polyline);
polyline.addTo(map);
window.graphicsDevices[key].push(decorator);
decorator.addTo(map);
}
}
}//foreach key
}
Code for the custom marker :
var MyCustomMarker = L.Marker.extend({
bindPopup: function(htmlContent, options) {
if (options && options.showOnMouseOver) {
// call the super method
L.Marker.prototype.bindPopup.apply(this, [htmlContent, options]);
// unbind the click event
this.off("click", this.openPopup, this);
// bind to mouse over
this.on("mouseover", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.fromElement || e.originalEvent.relatedTarget;
var parent = this._getParent(target, "leaflet-popup");
// check to see if the element is a popup, and if it is this marker's popup
if (parent == this._popup._container)
return true;
// show the popup
this.openPopup();
}, this);
// and mouse out
this.on("mouseout", function(e) {
// get the element that the mouse hovered onto
var target = e.originalEvent.toElement || e.originalEvent.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup")) {
L.DomEvent.on(this._popup._container, "mouseout", this._popupMouseOut, this);
return true;
}
// hide the popup
this.closePopup();
}, this);
}
},
_popupMouseOut: function(e) {
// detach the event
L.DomEvent.off(this._popup, "mouseout", this._popupMouseOut, this);
// get the element that the mouse hovered onto
var target = e.toElement || e.relatedTarget;
// check to see if the element is a popup
if (this._getParent(target, "leaflet-popup"))
return true;
// check to see if the marker was hovered back onto
if (target == this._icon)
return true;
// hide the popup
this.closePopup();
},
_getParent: function(element, className) {
var parent = null;
if(element != null) parent = element.parentNode;
while (parent != null) {
if (parent.className && L.DomUtil.hasClass(parent, className))
return parent;
parent = parent.parentNode;
}
return false;
}
});
Have you gauged performance in canvas mode?
Use L_PREFER_CANVAS = true before initializing your leaflet map container.Might be able to help you possibly.