///
// Based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet and
// https://github.com/Leaflet/Leaflet.markercluster because it works very perfectly
var PruneClusterLeafletSpiderfier = ((L).Layer ? (L).Layer : L.Class).extend({
_2PI: Math.PI * 2,
_circleFootSeparation: 25, //related to circumference of circle
_circleStartAngle: Math.PI / 6,
_spiralFootSeparation: 28, //related to size of spiral (experiment!)
_spiralLengthStart: 11,
_spiralLengthFactor: 5,
_spiralCountTrigger: 8,
spiderfyDistanceMultiplier: 1,
initialize: function(cluster: PruneCluster.LeafletAdapter) {
this._cluster = cluster;
this._currentMarkers = [];
this._multiLines = !!L.multiPolyline;
this._lines = this._multiLines ?
L.multiPolyline([], { weight: 1.5, color: '#222' }) :
L.polyline([], { weight: 1.5, color: '#222' });
},
onAdd: function(map: L.Map) {
this._map = map;
this._map.on('overlappingmarkers', this.Spiderfy, this);
this._map.on('click', this.Unspiderfy, this);
this._map.on('zoomend', this.Unspiderfy, this);
},
Spiderfy: function (data) {
// Ignore events from other PruneCluster instances
if (data.cluster !== this._cluster) {
return;
}
this.Unspiderfy();
var markers = data.markers.filter(function(marker) {
return !marker.filtered;
});
this._currentCenter = data.center;
var centerPoint = this._map.latLngToLayerPoint(data.center);
var points: L.Point[];
if (markers.length >= this._spiralCountTrigger) {
points = this._generatePointsSpiral(markers.length, centerPoint);
} else {
if (this._multiLines) { // if multilines, leaflet < 0.8
centerPoint.y += 10; // center fix
}
points = this._generatePointsCircle(markers.length, centerPoint);
}
var polylines: L.LatLng[][] = [];
var leafletMarkers: L.Marker[] = [];
var projectedPoints: L.LatLng[] = [];
for (var i = 0, l = points.length; i < l; ++i) {
var pos = this._map.layerPointToLatLng(points[i]);
var m = this._cluster.BuildLeafletMarker(markers[i], data.center);
m.setZIndexOffset(5000);
m.setOpacity(0);
// polylines.push([data.center, pos]);
this._currentMarkers.push(m);
this._map.addLayer(m);
leafletMarkers.push(m);
projectedPoints.push(pos);
}
window.setTimeout(() => {
for (i = 0, l = points.length; i < l; ++i) {
leafletMarkers[i].setLatLng(projectedPoints[i])
.setOpacity(1);
}
var startTime = +new Date();
var interval = 42, duration = 290;
var anim = window.setInterval(() => {
polylines = [];
var now = +new Date();
var d = now - startTime;
if (d >= duration) {
window.clearInterval(anim);
stepRatio = 1.0;
} else {
var stepRatio = d / duration;
}
var center = data.center;
for (i = 0, l = points.length; i < l; ++i) {
var p = projectedPoints[i],
diffLat = p.lat - center.lat,
diffLng = p.lng - center.lng;
polylines.push([center, new L.LatLng(center.lat + diffLat * stepRatio, center.lng + diffLng * stepRatio)]);
}
this._lines.setLatLngs(polylines);
}, interval);
}, 1);
this._lines.setLatLngs(polylines);
this._map.addLayer(this._lines);
if (data.marker) {
this._clusterMarker = data.marker.setOpacity(0.3);
}
},
_generatePointsCircle: function(count: number, centerPt: L.Point): L.Point[] {
var circumference = this.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
legLength = circumference / this._2PI, //radius from circumference
angleStep = this._2PI / count,
res = [],
i,
angle;
res.length = count;
for (i = count - 1; i >= 0; i--) {
angle = this._circleStartAngle + i * angleStep;
res[i] = new L.Point(
Math.round(centerPt.x + legLength * Math.cos(angle)),
Math.round(centerPt.y + legLength * Math.sin(angle)));
}
return res;
},
_generatePointsSpiral: function(count: number, centerPt: L.Point): L.Point[] {
var legLength = this.spiderfyDistanceMultiplier * this._spiralLengthStart,
separation = this.spiderfyDistanceMultiplier * this._spiralFootSeparation,
lengthFactor = this.spiderfyDistanceMultiplier * this._spiralLengthFactor,
angle = 0,
res = [],
i;
res.length = count;
for (i = count - 1; i >= 0; i--) {
angle += separation / legLength + i * 0.0005;
res[i] = new L.Point(
Math.round(centerPt.x + legLength * Math.cos(angle)),
Math.round(centerPt.y + legLength * Math.sin(angle)));
legLength += this._2PI * lengthFactor / angle;
}
return res;
},
Unspiderfy: function() {
for (var i = 0, l = this._currentMarkers.length; i < l; ++i) {
this._currentMarkers[i].setLatLng(this._currentCenter).setOpacity(0);
}
var markers = this._currentMarkers;
window.setTimeout(() => {
for (i = 0, l = markers.length; i < l; ++i) {
this._map.removeLayer(markers[i]);
}
}, 300);
this._currentMarkers = [];
this._map.removeLayer(this._lines);
if (this._clusterMarker) {
this._clusterMarker.setOpacity(1);
}
},
onRemove: function(map: L.Map) {
this.Unspiderfy();
map.off('overlappingmarkers', this.Spiderfy, this);
map.off('click', this.Unspiderfy, this);
map.off('zoomend', this.Unspiderfy, this);
}
});