/*
|
(c) 2016, Manuel Bär (www.geonet.ch)
|
Leaflet.idw, a tiny and fast inverse distance weighting plugin for Leaflet.
|
Largely based on the source code of Leaflet.heat by Vladimir Agafonkin (c) 2014
|
https://github.com/Leaflet/Leaflet.heat
|
version: 0.0.2
|
*/
|
!function(){
|
"use strict";
|
|
function simpleidw(canvas) {
|
if (!(this instanceof simpleidw)) return new simpleidw(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 = [];
|
}
|
|
simpleidw.prototype = {
|
|
defaultCellSize: 25,
|
|
defaultGradient: {
|
0.0: '#000066',
|
0.1: '#00e400',
|
0.2: '#ffff00',
|
0.3: '#ff7e00',
|
0.4: '#ff0000',
|
0.5: '#99004c',
|
0.6: '#7e0023',
|
0.7: 'Maroon',
|
0.8: '#660066',
|
0.9: '#990099',
|
1.0: '#ff66ff'
|
},
|
|
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;
|
},
|
|
cellSize: function (r) {
|
// create a grayscale blurred cell image that we'll use for drawing points
|
var cell = this._cell = document.createElement("canvas"),
|
ctx = cell.getContext('2d');
|
this._r = r;
|
|
cell.width = cell.height = r;
|
|
ctx.beginPath();
|
ctx.rect(0, 0, r, r);
|
ctx.fill();
|
ctx.closePath();
|
|
return this;
|
},
|
|
resize: function () {
|
this._width = this._canvas.width;
|
this._height = this._canvas.height;
|
},
|
|
gradient: function (grad) {
|
// create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one
|
var canvas = document.createElement("canvas"),
|
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 (opacity) {
|
if (!this._cell) this.cellSize(this.defaultCellSize);
|
if (!this._grad) this.gradient(this.defaultGradient);
|
|
var ctx = this._ctx;
|
|
ctx.clearRect(0, 0, this._width, this._height);
|
|
// draw a grayscale idwmap by putting a cell at each data point
|
for (var i = 0, len = this._data.length, p; i < len; i++) {
|
p = this._data[i];
|
ctx.globalAlpha = p[2] / this._max;
|
ctx.drawImage(this._cell, p[0] - this._r, p[1] - this._r);
|
}
|
|
// colorize the heatmap, using opacity value of each pixel to get the right color from our gradient
|
var colored = ctx.getImageData(0, 0, this._width, this._height);
|
this._colorize(colored.data, this._grad, opacity);
|
|
ctx.putImageData(colored, 0, 0);
|
|
return this;
|
},
|
|
_colorize: function (pixels, gradient, opacity) {
|
for (var i = 0, len = pixels.length, j; i < len; i += 4) {
|
j = pixels[i + 3] * 4;
|
|
pixels[i] = gradient[j];
|
pixels[i + 1] = gradient[j + 1];
|
pixels[i + 2] = gradient[j + 2];
|
pixels[i + 3] = opacity*256;
|
}
|
}
|
},
|
window.simpleidw = simpleidw
|
}(),
|
|
L.IdwLayer = (L.Layer ? L.Layer : L.Class).extend({
|
/*
|
options: {
|
opacity: 0.5,
|
maxZoom: 18,
|
cellSize: 1,
|
exp: 2,
|
max: 100
|
},
|
*/
|
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._idw) {
|
this._updateOptions();
|
}
|
return this.redraw();
|
},
|
|
redraw: function () {
|
if (this._idw && !this._frame && !this._map._animating) {
|
this._frame = L.Util.requestAnimFrame(this._redraw, this);
|
}
|
return this;
|
},
|
|
onAdd: function (map) {
|
this._map = map;
|
|
if (!this._canvas) {
|
this._initCanvas();
|
}
|
|
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) {
|
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-idwmap-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._idw = simpleidw(canvas);
|
this._updateOptions();
|
},
|
|
_updateOptions: function () {
|
this._idw.cellSize(this.options.cellSize || this._idw.defaultCellSize);
|
|
if (this.options.gradient) {
|
this._idw.gradient(this.options.gradient);
|
}
|
if (this.options.max) {
|
this._idw.max(this.options.max);
|
}
|
},
|
|
_reset: function () {
|
var topLeft = this._map.containerPointToLayerPoint([0, 0]);
|
L.DomUtil.setPosition(this._canvas, topLeft);
|
|
var size = this._map.getSize();
|
|
if (this._idw._width !== size.x) {
|
this._canvas.width = this._idw._width = size.x;
|
}
|
if (this._idw._height !== size.y) {
|
this._canvas.height = this._idw._height = size.y;
|
}
|
|
this._redraw();
|
},
|
|
_redraw: function () {
|
if (!this._map) {
|
return;
|
}
|
var data = [],
|
r = this._idw._r,
|
size = this._map.getSize(),
|
bounds = new L.Bounds(
|
L.point([-r, -r]),
|
size.add([r, r])),
|
|
exp = this.options.exp === undefined ? 1 : this.options.exp,
|
max = this.options.max === undefined ? 1 : this.options.max,
|
maxZoom = this.options.maxZoom === undefined ? this._map.getMaxZoom() : this.options.maxZoom,
|
v = 1,
|
cellCen = r / 2,
|
grid = [],
|
nCellX = Math.ceil((bounds.max.x-bounds.min.x)/r)+1,
|
nCellY = Math.ceil((bounds.max.y-bounds.min.y)/r)+1,
|
panePos = this._map._getMapPanePos(),
|
|
offsetX = 0,
|
offsetY = 0,
|
i, len, p, cell, x, y, j, len2, k;
|
|
console.log(nCellX);
|
console.log(nCellY);
|
|
console.time('process');
|
|
for (i = 0, len = nCellY; i < len; i++) {
|
for (j = 0, len2 = nCellX; j < len2; j++) {
|
|
var x=i*r,y=j*r;
|
var numerator=0,denominator=0;
|
|
for (k = 0, len3 = this._latlngs.length; k < len3; k++) {
|
|
var p = this._map.latLngToContainerPoint(this._latlngs[k]);
|
var cp = L.point((y-cellCen), (x-cellCen));
|
var dist = cp.distanceTo(p);
|
|
var val =
|
this._latlngs[k].alt !== undefined ? this._latlngs[k].alt :
|
this._latlngs[k][2] !== undefined ? +this._latlngs[k][2] : 1;
|
|
if(dist===0){
|
numerator = val;
|
denominator = 1;
|
break;
|
}
|
|
var dist2 = Math.pow(dist, exp);
|
|
numerator += (val/dist2);
|
denominator += (1/dist2);
|
|
}
|
|
interpolVal = numerator/denominator;
|
|
cell = [j*r, i*r, interpolVal];
|
|
if (cell) {
|
data.push([
|
Math.round(cell[0]),
|
Math.round(cell[1]),
|
Math.min(cell[2], max)
|
]);
|
}
|
}
|
}
|
console.timeEnd('process');
|
console.time('draw ' + data.length);
|
this._idw.data(data).draw(this.options.opacity);
|
console.timeEnd('draw ' + data.length);
|
|
this._frame = null;
|
},
|
|
_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.idwLayer = function (latlngs, options) {
|
return new L.IdwLayer(latlngs, options);
|
};
|