241 lines
6.7 KiB
JavaScript
241 lines
6.7 KiB
JavaScript
define(["leaflet", "rbush"],
|
|
function (L, rbush) {
|
|
"use strict";
|
|
|
|
var labelLocations = [["left", "middle", 0 / 8],
|
|
["center", "top", 6 / 8],
|
|
["right", "middle", 4 / 8],
|
|
["left", "top", 7 / 8],
|
|
["left", "ideographic", 1 / 8],
|
|
["right", "top", 5 / 8],
|
|
["center", "ideographic", 2 / 8],
|
|
["right", "ideographic", 3 / 8]];
|
|
|
|
var fontFamily = "Roboto";
|
|
var nodeRadius = 4;
|
|
|
|
var ctx = document.createElement("canvas").getContext("2d");
|
|
|
|
function measureText(font, text) {
|
|
ctx.font = font;
|
|
return ctx.measureText(text);
|
|
}
|
|
|
|
function mapRTree(d) {
|
|
var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng];
|
|
|
|
o.label = d;
|
|
|
|
return o;
|
|
}
|
|
|
|
function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) {
|
|
return function (d) {
|
|
var font = fontSize + "px " + fontFamily;
|
|
return {
|
|
position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
|
|
label: d.nodeinfo.hostname,
|
|
offset: offset,
|
|
fillStyle: fillStyle,
|
|
height: fontSize * 1.2,
|
|
font: font,
|
|
stroke: stroke,
|
|
minZoom: minZoom,
|
|
width: measureText(font, d.nodeinfo.hostname).width
|
|
};
|
|
};
|
|
}
|
|
|
|
function calcOffset(offset, loc) {
|
|
return [offset * Math.cos(loc[2] * 2 * Math.PI),
|
|
-offset * Math.sin(loc[2] * 2 * Math.PI)];
|
|
}
|
|
|
|
function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
|
|
var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom));
|
|
|
|
var width = label.width * margin;
|
|
var height = label.height * margin;
|
|
|
|
var dx = {
|
|
left: 0,
|
|
right: -width,
|
|
center: -width / 2
|
|
};
|
|
|
|
var dy = {
|
|
top: 0,
|
|
ideographic: -height,
|
|
middle: -height / 2
|
|
};
|
|
|
|
var x = p.x + offset[0] + dx[anchor[0]];
|
|
var y = p.y + offset[1] + dy[anchor[1]];
|
|
|
|
return [x, y, x + width, y + height];
|
|
}
|
|
|
|
return L.TileLayer.Canvas.extend({
|
|
onAdd: function (map) {
|
|
L.TileLayer.Canvas.prototype.onAdd.call(this, map);
|
|
if (this.data) {
|
|
this.prepareLabels();
|
|
}
|
|
},
|
|
setData: function (d) {
|
|
this.data = d;
|
|
if (this._map) {
|
|
this.prepareLabels();
|
|
}
|
|
},
|
|
prepareLabels: function () {
|
|
var d = this.data;
|
|
|
|
// label:
|
|
// - position (WGS84 coords)
|
|
// - offset (2D vector in pixels)
|
|
// - anchor (tuple, textAlignment, textBaseline)
|
|
// - minZoom (inclusive)
|
|
// - label (string)
|
|
// - color (string)
|
|
|
|
var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13));
|
|
var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16));
|
|
var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0));
|
|
var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0));
|
|
|
|
var labels = []
|
|
.concat(labelsNew)
|
|
.concat(labelsLost)
|
|
.concat(labelsOnline)
|
|
.concat(labelsOffline);
|
|
|
|
var minZoom = this.options.minZoom;
|
|
var maxZoom = this.options.maxZoom;
|
|
|
|
var trees = [];
|
|
|
|
var map = this._map;
|
|
|
|
function nodeToRect(z) {
|
|
return function (d) {
|
|
var p = map.project(d.position, z);
|
|
return [p.x - nodeRadius, p.y - nodeRadius,
|
|
p.x + nodeRadius, p.y + nodeRadius];
|
|
};
|
|
}
|
|
|
|
for (var z = minZoom; z <= maxZoom; z++) {
|
|
trees[z] = rbush(9);
|
|
trees[z].load(labels.map(nodeToRect(z)));
|
|
}
|
|
|
|
labels = labels.map(function (d) {
|
|
var best = labelLocations.map(function (loc) {
|
|
var offset = calcOffset(d.offset, loc);
|
|
var z;
|
|
|
|
for (z = maxZoom; z >= d.minZoom; z--) {
|
|
var p = map.project(d.position, z);
|
|
var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z);
|
|
var candidates = trees[z].search(rect);
|
|
|
|
if (candidates.length > 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return {loc: loc, z: z + 1};
|
|
}).filter(function (d) {
|
|
return d.z <= maxZoom;
|
|
}).sort(function (a, b) {
|
|
return a.z - b.z;
|
|
})[0];
|
|
|
|
if (best !== undefined) {
|
|
d.offset = calcOffset(d.offset, best.loc);
|
|
d.minZoom = best.z;
|
|
d.anchor = best.loc;
|
|
|
|
for (var z = maxZoom; z >= best.z; z--) {
|
|
var p = map.project(d.position, z);
|
|
var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z);
|
|
trees[z].insert(rect);
|
|
}
|
|
|
|
return d;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}).filter(function (d) {
|
|
return d !== undefined;
|
|
});
|
|
|
|
this.margin = 16;
|
|
|
|
if (labels.length > 0) {
|
|
this.margin += labels.map(function (d) {
|
|
return d.width;
|
|
}).sort().reverse()[0];
|
|
}
|
|
|
|
this.labels = rbush(9);
|
|
this.labels.load(labels.map(mapRTree));
|
|
|
|
this.redraw();
|
|
},
|
|
drawTile: function (canvas, tilePoint, zoom) {
|
|
function getTileBBox(s, map, tileSize, margin) {
|
|
var tl = map.unproject([s.x - margin, s.y - margin]);
|
|
var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize]);
|
|
|
|
return [br.lat, tl.lng, tl.lat, br.lng];
|
|
}
|
|
|
|
if (!this.labels) {
|
|
return;
|
|
}
|
|
|
|
var tileSize = this.options.tileSize;
|
|
var s = tilePoint.multiplyBy(tileSize);
|
|
var map = this._map;
|
|
|
|
function projectNodes(d) {
|
|
var p = map.project(d.label.position);
|
|
|
|
p.x -= s.x;
|
|
p.y -= s.y;
|
|
|
|
return {p: p, label: d.label};
|
|
}
|
|
|
|
var bbox = getTileBBox(s, map, tileSize, this.margin);
|
|
|
|
var labels = this.labels.search(bbox).map(projectNodes);
|
|
|
|
var ctx = canvas.getContext("2d");
|
|
|
|
ctx.lineWidth = 5;
|
|
ctx.strokeStyle = "rgba(255, 255, 255, 0.8)";
|
|
ctx.miterLimit = 2;
|
|
|
|
function drawLabel(d) {
|
|
ctx.font = d.label.font;
|
|
ctx.textAlign = d.label.anchor[0];
|
|
ctx.textBaseline = d.label.anchor[1];
|
|
ctx.fillStyle = d.label.fillStyle;
|
|
|
|
if (d.label.stroke) {
|
|
ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
|
|
}
|
|
|
|
ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
|
|
}
|
|
|
|
labels.filter(function (d) {
|
|
return zoom >= d.label.minZoom;
|
|
}).forEach(drawLabel);
|
|
}
|
|
});
|
|
});
|