Solution below is based on using HTML canvas elements and operations upon them to clip heatmap layer with desired polygon. It's more proof of a concept than full fledged solution. But it works.
The following elements are used in the process of creating clipped heatmap:
heatmapLayer
layer is created, with full unclipped heatmap. HTML canvas element containing this layer is then hidden, since this layer is used only as source for clipped layer.
clippedHeatmapLayer
layer is created, without any data. This is target layer when creating clipped heatmap.
- GeoJSON layer is created, with white filled clipping polygon. Layer is assigned to it's own map pane and also hidden, since it is used only for clipping. Layer is rendered on canvas (by setting map option
preferCanvas: true
).
Clipping of heatmap is done by the following steps:
- Temporary working canvas
workCanvas
is created.
- Content of clipping layer canvas
clipCanvas
is drawn on working canvas, taking into account the difference in size between clipping layer canvas and and heatmp canvas.
- Heatmap canvas
heatmapCanvas
is drawn on working canvas so that is clipped by previously drawn clipping canvas.
- Resulting canvas is drawn on target canvas
clippedHeatmapCanvas
of clipped heatmap layer.
Clipping is done first at the time of map initialization and then at each map moveend
event. Since leaflet-heatmap library has no events that would mark end of heatmap drawing, clipping is done with slight delay to ensure that original full heatmap has been drawn.
Working JSFiddle can be checked here: https://jsfiddle.net/TomazicM/0y21uspa/ (code contains also complete leaflet-heatmap.js, since I couldn't find any public source).
This is the code (without full data):
var clipBorder = {"type":"FeatureCollection","features":[{"type":"Feature","id":"USA","properties":{"name":"United States of America"},"geometry":{"type":"MultiPolygon","coordinates": ...
var testData = {
max: 8,
data: [{lat: 24.6408, lng:46.7728, count: 3}, ...
};
var baseLayer = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="http://cloudmade.com">CloudMade</a>',
maxZoom: 18
}
);
var map = new L.Map('map', {
layers: [baseLayer],
center: new L.LatLng(25.6586, -80.3568),
zoom: 4,
preferCanvas: true
});
var heatmapCfg = {
radius: 2,
maxOpacity: 0.8,
scaleRadius: true,
useLocalExtrema: false,
latField: 'lat',
lngField: 'lng',
valueField: 'count'
};
var clippedHeatmapLayer = new HeatmapOverlay(heatmapCfg);
clippedHeatmapLayer.addTo(map);
var heatmapLayer = new HeatmapOverlay(heatmapCfg);
heatmapLayer.addTo(map);
heatmapLayer.setData(testData);
var overlayPane = map.getPanes().overlayPane;
var heatmapCanvas = overlayPane.childNodes[1].childNodes[0];
heatmapCanvas.style.display = 'none';
var clippedHeatmapCanvas = overlayPane.childNodes[0].childNodes[0];
var clipPane = map.createPane('clip-pane');
clipPane.style.zIndex = 350;
clipPane.style.display = 'none';
var clipLayer = L.geoJSON(clipBorder, {
pane: 'clip-pane',
style: {
stroke: false,
fill: true,
fillColor: '#ffffff',
fillOpacity: 1
}
});
clipLayer.addTo(map);
function showClippedCanvas() {
var workCanvas = document.createElement('canvas');
var workCtx = workCanvas.getContext('2d');
workCanvas.width = heatmapCanvas.width;
workCanvas.height = heatmapCanvas.height;
var clipCanvas = clipPane.childNodes[0];
var clipCanvasW = parseInt(clipCanvas.style.width, 10);
var clipCanvasH = parseInt(clipCanvas.style.height, 10);
var xOffset = (clipCanvasW - heatmapCanvas.width) / 2;
var yOffset = (clipCanvasH - heatmapCanvas.height) / 2;
workCtx.drawImage(clipCanvas, -xOffset, -yOffset, clipCanvasW, clipCanvasH);
workCtx.globalCompositeOperation = 'source-in';
workCtx.drawImage(heatmapCanvas, 0, 0);
var targetCtx = clippedHeatmapCanvas.getContext('2d');
targetCtx.drawImage(workCanvas, 0, 0);
}
map.on('moveend', function() {
setTimeout(showClippedCanvas, 1);
});
setTimeout(showClippedCanvas, 5);
Example of clipped heatmap:
