labelslayer.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. define(["leaflet", "rbush"],
  2. function (L, rbush) {
  3. var labelLocations = [["left", "middle", 0 / 8],
  4. ["center", "top", 6 / 8],
  5. ["right", "middle", 4 / 8],
  6. ["left", "top", 7 / 8],
  7. ["left", "ideographic", 1 / 8],
  8. ["right", "top", 5 / 8],
  9. ["center", "ideographic", 2 / 8],
  10. ["right", "ideographic", 3 / 8]]
  11. var fontFamily = "Roboto"
  12. var nodeRadius = 4
  13. var ctx = document.createElement("canvas").getContext("2d")
  14. function measureText(font, text) {
  15. ctx.font = font
  16. return ctx.measureText(text)
  17. }
  18. function mapRTree(d) {
  19. var o = [d.position.lat, d.position.lng, d.position.lat, d.position.lng]
  20. o.label = d
  21. return o
  22. }
  23. function prepareLabel(fillStyle, fontSize, offset, stroke, minZoom) {
  24. return function (d) {
  25. var font = fontSize + "px " + fontFamily
  26. return { position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
  27. label: d.nodeinfo.hostname,
  28. offset: offset,
  29. fillStyle: fillStyle,
  30. height: fontSize * 1.2,
  31. font: font,
  32. stroke: stroke,
  33. minZoom: minZoom,
  34. width: measureText(font, d.nodeinfo.hostname).width
  35. }
  36. }
  37. }
  38. function calcOffset(offset, loc) {
  39. return [ offset * Math.cos(loc[2] * 2 * Math.PI),
  40. -offset * Math.sin(loc[2] * 2 * Math.PI)]
  41. }
  42. function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
  43. var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom))
  44. var width = label.width * margin
  45. var height = label.height * margin
  46. var dx = { left: 0,
  47. right: -width,
  48. center: -width / 2
  49. }
  50. var dy = { top: 0,
  51. ideographic: -height,
  52. middle: -height / 2
  53. }
  54. var x = p.x + offset[0] + dx[anchor[0]]
  55. var y = p.y + offset[1] + dy[anchor[1]]
  56. return [x, y, x + width, y + height]
  57. }
  58. var c = L.TileLayer.Canvas.extend({
  59. onAdd: function (map) {
  60. L.TileLayer.Canvas.prototype.onAdd.call(this, map)
  61. if (this.data)
  62. this.prepareLabels()
  63. },
  64. setData: function (d) {
  65. this.data = d
  66. if (this._map)
  67. this.prepareLabels()
  68. },
  69. prepareLabels: function () {
  70. var d = this.data
  71. // label:
  72. // - position (WGS84 coords)
  73. // - offset (2D vector in pixels)
  74. // - anchor (tuple, textAlignment, textBaseline)
  75. // - minZoom (inclusive)
  76. // - label (string)
  77. // - color (string)
  78. var labelsOnline = d.online.map(prepareLabel("rgba(0, 0, 0, 0.9)", 10, 8, true, 13))
  79. var labelsOffline = d.offline.map(prepareLabel("rgba(212, 62, 42, 0.9)", 9, 5, false, 16))
  80. var labelsNew = d.new.map(prepareLabel("rgba(48, 99, 20, 0.9)", 11, 8, true, 0))
  81. var labelsLost = d.lost.map(prepareLabel("rgba(212, 62, 42, 0.9)", 11, 8, true, 0))
  82. var labels = []
  83. .concat(labelsNew)
  84. .concat(labelsLost)
  85. .concat(labelsOnline)
  86. .concat(labelsOffline)
  87. var minZoom = this.options.minZoom
  88. var maxZoom = this.options.maxZoom
  89. var trees = []
  90. var map = this._map
  91. function nodeToRect(z) {
  92. return function (d) {
  93. var p = map.project(d.position, z)
  94. return [p.x - nodeRadius, p.y - nodeRadius,
  95. p.x + nodeRadius, p.y + nodeRadius]
  96. }
  97. }
  98. for (var z = minZoom; z <= maxZoom; z++) {
  99. trees[z] = rbush(9)
  100. trees[z].load(labels.map(nodeToRect(z)))
  101. }
  102. labels = labels.map(function (d) {
  103. var best = labelLocations.map(function (loc) {
  104. var offset = calcOffset(d.offset, loc)
  105. var z
  106. for (z = maxZoom; z >= d.minZoom; z--) {
  107. var p = map.project(d.position, z)
  108. var rect = labelRect(p, offset, loc, d, minZoom, maxZoom, z)
  109. var candidates = trees[z].search(rect)
  110. if (candidates.length > 0)
  111. break
  112. }
  113. return {loc: loc, z: z + 1}
  114. }).filter(function (d) {
  115. return d.z <= maxZoom
  116. }).sort(function (a, b) {
  117. return a.z - b.z
  118. })[0]
  119. if (best !== undefined) {
  120. d.offset = calcOffset(d.offset, best.loc)
  121. d.minZoom = best.z
  122. d.anchor = best.loc
  123. for (var z = maxZoom; z >= best.z; z--) {
  124. var p = map.project(d.position, z)
  125. var rect = labelRect(p, d.offset, best.loc, d, minZoom, maxZoom, z)
  126. trees[z].insert(rect)
  127. }
  128. return d
  129. } else
  130. return undefined
  131. }).filter(function (d) { return d !== undefined })
  132. this.margin = 16
  133. if (labels.length > 0)
  134. this.margin += labels.map(function (d) {
  135. return d.width
  136. }).sort().reverse()[0]
  137. this.labels = rbush(9)
  138. this.labels.load(labels.map(mapRTree))
  139. this.redraw()
  140. },
  141. drawTile: function (canvas, tilePoint, zoom) {
  142. function getTileBBox(s, map, tileSize, margin) {
  143. var tl = map.unproject([s.x - margin, s.y - margin])
  144. var br = map.unproject([s.x + margin + tileSize, s.y + margin + tileSize])
  145. return [br.lat, tl.lng, tl.lat, br.lng]
  146. }
  147. if (!this.labels)
  148. return
  149. var tileSize = this.options.tileSize
  150. var s = tilePoint.multiplyBy(tileSize)
  151. var map = this._map
  152. function projectNodes(d) {
  153. var p = map.project(d.label.position)
  154. p.x -= s.x
  155. p.y -= s.y
  156. return {p: p, label: d.label}
  157. }
  158. var bbox = getTileBBox(s, map, tileSize, this.margin)
  159. var labels = this.labels.search(bbox).map(projectNodes)
  160. var ctx = canvas.getContext("2d")
  161. ctx.lineWidth = 5
  162. ctx.strokeStyle = "rgba(255, 255, 255, 0.8)"
  163. ctx.miterLimit = 2
  164. function drawLabel(d) {
  165. ctx.font = d.label.font
  166. ctx.textAlign = d.label.anchor[0]
  167. ctx.textBaseline = d.label.anchor[1]
  168. ctx.fillStyle = d.label.fillStyle
  169. if (d.label.stroke)
  170. ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1])
  171. ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1])
  172. }
  173. labels.filter(function (d) {
  174. return zoom >= d.label.minZoom
  175. }).forEach(drawLabel)
  176. }
  177. })
  178. return c
  179. })