hopglass/lib/map/labelslayer.js
2015-07-07 21:28:44 +02:00

224 lines
6.6 KiB
JavaScript

define(["leaflet", "rbush"],
function (L, rbush) {
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) {
var dx = { left: 0,
right: -label.width,
center: -label.width / 2
}
var dy = { top: 0,
ideographic: -label.height,
middle: -label.height / 2
}
var x = p.x + offset[0] + dx[anchor[0]]
var y = p.y + offset[1] + dy[anchor[1]]
return [x, y, x + label.width, y + label.height]
}
var c = 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)
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)
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)
}
})
return c
})