Skip to content

Instantly share code, notes, and snippets.

@kuzvac
Created May 6, 2016 11:22
Show Gist options
  • Save kuzvac/84145d079b7c67a7094c973007d2e6a4 to your computer and use it in GitHub Desktop.
Save kuzvac/84145d079b7c67a7094c973007d2e6a4 to your computer and use it in GitHub Desktop.
Leaflet.heat fixed radius
import simpleheat from 'simpleheat';
/*
(c) 2014, Vladimir Agafonkin
Leaflet.heat, a tiny and fast heatmap plugin for Leaflet.
https://github.com/Leaflet/Leaflet.heat
*/
L.HeatLayer = (L.Layer ? L.Layer : L.Class).extend({
// options: {
// minOpacity: 0.05,
// maxOpacity: 1,
// maxZoom: 18,
// radius: 25,
// blur: 15,
// max: 1.0,
// fixedZoomLevel: 15
// },
initialize: function (latlngs, options) {
/* Create geometric progression with multiplier 2
It's need for scale heat points based on map zoom level */
var geometryMultiplier = 2;
var geomProgr = [geometryMultiplier];
for (var i = 0; i < 17; i++) {
geometryMultiplier = geometryMultiplier * 2;
geomProgr.push(geometryMultiplier);
}
options['fixedMultiplier'] = geomProgr;
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._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-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();
/* Calculate fixed zoom level on map load */
var fixedZoomLevel = this._map._zoom - this.options.fixedZoomLevel;
/* Calculate base fixed radius for heat map. */
var basedRadius = this._metersToPixels(this.options.radius, this._map.getCenter().lat, this.options.fixedZoomLevel);
/* If current map zoom greather or equal fixedZoomLevel, change heat point radius */
if(fixedZoomLevel >= 0) {
this._heat.radius(this._metersToPixels(this.options.radius, this._map.getCenter().lat, this._map.getZoom()), this.options.blur)
} else {
/* Otherwise apply calculated basedRadius */
this._heat.radius(basedRadius, this.options.blur)
}
/* If current map zoom greather or equal maxZoom || _layersMaxZoom, change opacity for heatpoints */
if(this._map._zoom >= (this.options.maxZoom || this._map._layersMaxZoom)) {
L.DomUtil.setOpacity(this._canvas, this.options.maxOpacity || 1)
} else {
/* Otherwise remove opacity */
this._canvas.style.opacity = '';
}
},
_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.max) {
this._heat.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._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 () {
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;
// console.time('process');
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); // x
cell[1] = (cell[1] * cell[2] + p.y * k) / (cell[2] + k); // y
cell[2] += k; // cumulated intensity value
}
}
}
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)
]);
}
}
}
}
// console.timeEnd('process');
// console.time('draw ' + data.length);
this._heat.data(data).draw(this.options.minOpacity);
// console.timeEnd('draw ' + data.length);
this._frame = null;
},
_animateZoom: function (e) {
var fixedZoomLevel = e.zoom - this.options.fixedZoomLevel;
var fixedZoomRadius;
/* Change heat point radius when zoom is changed */
if(fixedZoomLevel >= 0) {
this._heat.radius(this._metersToPixels(this.options.radius, e.center.lat, e.zoom), this.options.blur)
}
/* Change opacity for heat points when zoom is changed */
if(this.options.maxZoom && e.zoom >= this.options.maxZoom) {
L.DomUtil.setOpacity(this._canvas, this.options.maxOpacity || 1)
} else if(!this.options.maxZoom && e.zoom >= (e.target._layersMaxZoom - 1)) {
L.DomUtil.setOpacity(this._canvas, this.options.maxOpacity || 1)
} else {
this._canvas.style.opacity = '';
}
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 + ')';
}
},
/* Convert meters from options to map pixel for radius */
_metersToPixels: function(meters, center, zoom) {
var metersPerPixel = 40075016.686 * Math.abs(Math.cos(center * 180/Math.PI)) / Math.pow(2, zoom+8);
//var metersToMiles = metersPerPixel * 0.00062137;
return meters / metersPerPixel;
},
});
L.heatLayer = function (latlngs, options) {
return new L.HeatLayer(latlngs, options);
};
var heatmapOption = {
minOpacity: 0.3,
maxOpacity: 0.5,
radius: 200, //in meters
blur: 15,
//maxZoom: 16,
fixedZoomLevel: 13
}
var heat = L.heatLayer(heatmapPoints, heatmapOption).addTo(map);
@jackhagart
Copy link

jackhagart commented Apr 20, 2017

I need to plot heat maps using leaflet.heat with constant radius in meters. I dont want that the zoom changes the area covered by an color/intensity. It looks that your code do that, but, I could not figure out how to use it with map quest. This code is working? Do you have some running example?

@muyueh
Copy link

muyueh commented Aug 7, 2017

maybe it should be 40075016.686 * Math.abs(Math.cos(center * Math.PI / 180 )) instead of 40075016.686 * Math.abs(Math.cos(center * 180/Math.PI)) ?

@klaasa
Copy link

klaasa commented Jun 6, 2018

L.heatLayer is not a function. Am I doing something wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment