/* * Leaflet.heat — a tiny, fast Leaflet heatmap plugin * https://github.com/Leaflet/Leaflet.heat * (c) 2014, Vladimir Agafonkin * MIT License * * Bundled local copy for INTERCEPT — avoids CDN dependency. * Includes simpleheat (https://github.com/mourner/simpleheat), MIT License. */ // ---- simpleheat ---- (function (global, factory) { typeof define === 'function' && define.amd ? define(factory) : typeof exports !== 'undefined' ? module.exports = factory() : global.simpleheat = factory(); }(this, function () { 'use strict'; function simpleheat(canvas) { if (!(this instanceof simpleheat)) return new simpleheat(canvas); this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; this._ctx = canvas.getContext('2d'); this._width = canvas.width; this._height = canvas.height; this._max = 1; this._data = []; } simpleheat.prototype = { defaultRadius: 25, defaultGradient: { 0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red' }, data: function (data) { this._data = data; return this; }, max: function (max) { this._max = max; return this; }, add: function (point) { this._data.push(point); return this; }, clear: function () { this._data = []; return this; }, radius: function (r, blur) { blur = blur === undefined ? 15 : blur; var circle = this._circle = this._createCanvas(), ctx = circle.getContext('2d'), r2 = this._r = r + blur; circle.width = circle.height = r2 * 2; ctx.shadowOffsetX = ctx.shadowOffsetY = r2 * 2; ctx.shadowBlur = blur; ctx.shadowColor = 'black'; ctx.beginPath(); ctx.arc(-r2, -r2, r, 0, Math.PI * 2, true); ctx.closePath(); ctx.fill(); return this; }, resize: function () { this._width = this._canvas.width; this._height = this._canvas.height; }, gradient: function (grad) { var canvas = this._createCanvas(), ctx = canvas.getContext('2d'), gradient = ctx.createLinearGradient(0, 0, 0, 256); canvas.width = 1; canvas.height = 256; for (var i in grad) { gradient.addColorStop(+i, grad[i]); } ctx.fillStyle = gradient; ctx.fillRect(0, 0, 1, 256); this._grad = ctx.getImageData(0, 0, 1, 256).data; return this; }, draw: function (minOpacity) { if (!this._circle) this.radius(this.defaultRadius); if (!this._grad) this.gradient(this.defaultGradient); var ctx = this._ctx; ctx.clearRect(0, 0, this._width, this._height); for (var i = 0, len = this._data.length, p; i < len; i++) { p = this._data[i]; ctx.globalAlpha = Math.min(Math.max(p[2] / this._max, minOpacity === undefined ? 0.05 : minOpacity), 1); ctx.drawImage(this._circle, p[0] - this._r, p[1] - this._r); } var colored = ctx.getImageData(0, 0, this._width, this._height); this._colorize(colored.data, this._grad); ctx.putImageData(colored, 0, 0); return this; }, _colorize: function (pixels, gradient) { for (var i = 3, len = pixels.length, j; i < len; i += 4) { j = pixels[i] * 4; if (j) { pixels[i - 3] = gradient[j]; pixels[i - 2] = gradient[j + 1]; pixels[i - 1] = gradient[j + 2]; } } }, _createCanvas: function () { if (typeof document !== 'undefined') { return document.createElement('canvas'); } return { getContext: function () {} }; } }; return simpleheat; })); // ---- Leaflet.heat plugin ---- (function () { if (typeof L === 'undefined') return; L.HeatLayer = (L.Layer ? L.Layer : L.Class).extend({ initialize: function (latlngs, options) { this._latlngs = latlngs; L.setOptions(this, options); }, setLatLngs: function (latlngs) { this._latlngs = latlngs; return this.redraw(); }, addLatLng: function (latlng) { this._latlngs.push(latlng); return this.redraw(); }, setOptions: function (options) { L.setOptions(this, options); if (this._heat) this._updateOptions(); return this.redraw(); }, redraw: function () { if (this._heat && !this._frame && this._map && !this._map._animating) { this._frame = L.Util.requestAnimFrame(this._redraw, this); } return this; }, onAdd: function (map) { this._map = map; if (!this._canvas) this._initCanvas(); if (this.options.pane) this.getPane().appendChild(this._canvas); else map._panes.overlayPane.appendChild(this._canvas); map.on('moveend', this._reset, this); if (map.options.zoomAnimation && L.Browser.any3d) { map.on('zoomanim', this._animateZoom, this); } this._reset(); }, onRemove: function (map) { if (this.options.pane) this.getPane().removeChild(this._canvas); else map.getPanes().overlayPane.removeChild(this._canvas); map.off('moveend', this._reset, this); if (map.options.zoomAnimation) { map.off('zoomanim', this._animateZoom, this); } }, addTo: function (map) { map.addLayer(this); return this; }, _initCanvas: function () { var canvas = this._canvas = L.DomUtil.create('canvas', 'leaflet-heatmap-layer leaflet-layer'); var originProp = L.DomUtil.testProp(['transformOrigin', 'WebkitTransformOrigin', 'msTransformOrigin']); canvas.style[originProp] = '50% 50%'; var size = this._map.getSize(); canvas.width = size.x; canvas.height = size.y; var animated = this._map.options.zoomAnimation && L.Browser.any3d; L.DomUtil.addClass(canvas, 'leaflet-zoom-' + (animated ? 'animated' : 'hide')); this._heat = simpleheat(canvas); this._updateOptions(); }, _updateOptions: function () { this._heat.radius(this.options.radius || this._heat.defaultRadius, this.options.blur); if (this.options.gradient) this._heat.gradient(this.options.gradient); if (this.options.minOpacity) this._heat.minOpacity = this.options.minOpacity; }, _reset: function () { var topLeft = this._map.containerPointToLayerPoint([0, 0]); L.DomUtil.setPosition(this._canvas, topLeft); var size = this._map.getSize(); if (this._heat._width !== size.x) { this._canvas.width = this._heat._width = size.x; } if (this._heat._height !== size.y) { this._canvas.height = this._heat._height = size.y; } this._redraw(); }, _redraw: function () { this._frame = null; if (!this._map) return; var data = [], r = this._heat._r, size = this._map.getSize(), bounds = new L.Bounds(L.point([-r, -r]), size.add([r, r])), max = this.options.max === undefined ? 1 : this.options.max, maxZoom = this.options.maxZoom === undefined ? this._map.getMaxZoom() : this.options.maxZoom, v = 1 / Math.pow(2, Math.max(0, Math.min(maxZoom - this._map.getZoom(), 12))), cellSize = r / 2, grid = [], panePos = this._map._getMapPanePos(), offsetX = panePos.x % cellSize, offsetY = panePos.y % cellSize, i, len, p, cell, x, y, j, len2, k; for (i = 0, len = this._latlngs.length; i < len; i++) { p = this._map.latLngToContainerPoint(this._latlngs[i]); if (bounds.contains(p)) { x = Math.floor((p.x - offsetX) / cellSize) + 2; y = Math.floor((p.y - offsetY) / cellSize) + 2; var alt = this._latlngs[i].alt !== undefined ? this._latlngs[i].alt : this._latlngs[i][2] !== undefined ? +this._latlngs[i][2] : 1; k = alt * v; grid[y] = grid[y] || []; cell = grid[y][x]; if (!cell) { grid[y][x] = [p.x, p.y, k]; } else { cell[0] = (cell[0] * cell[2] + p.x * k) / (cell[2] + k); cell[1] = (cell[1] * cell[2] + p.y * k) / (cell[2] + k); cell[2] += k; } } } for (i = 0, len = grid.length; i < len; i++) { if (grid[i]) { for (j = 0, len2 = grid[i].length; j < len2; j++) { cell = grid[i][j]; if (cell) { data.push([ Math.round(cell[0]), Math.round(cell[1]), Math.min(cell[2], max) ]); } } } } this._heat.data(data).draw(this.options.minOpacity); }, _animateZoom: function (e) { var scale = this._map.getZoomScale(e.zoom), offset = this._map._getCenterOffset(e.center)._multiplyBy(-scale).subtract(this._map._getMapPanePos()); if (L.DomUtil.setTransform) { L.DomUtil.setTransform(this._canvas, offset, scale); } else { this._canvas.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ')'; } } }); L.heatLayer = function (latlngs, options) { return new L.HeatLayer(latlngs, options); }; }());