3

Here is a heatmap in Leaflet (using heatmap.js and leaflet-heatmap.js) with hot spots that extend beyond the basemap boundaries:

enter image description here

How would someone clip this? In QGIS or ArcMap you would follow a certain workflow, but this is not available in Leaflet.

We can set a GeoJSON layer of just the Georgia state, but how would one tell the heatmap not to cross the boundaries of this GeoJSON layer?

Slightly similar question (not using Leaflet), unanswered: Limiting OpenLayers heatmap to vector feature boundaries

  • Leaflet has no native heatmap functionality. Which plugin you are referring to? – TomazicM Mar 7 at 21:35
  • Updated my question, @TomazicM – briennakh Mar 9 at 3:04
4
+50

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:

  1. 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.
  2. clippedHeatmapLayer layer is created, without any data. This is target layer when creating clipped heatmap.
  3. 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:

  1. Temporary working canvas workCanvas is created.
  2. 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.
  3. Heatmap canvas heatmapCanvas is drawn on working canvas so that is clipped by previously drawn clipping canvas.
  4. 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 &copy; <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: enter image description here

| improve this answer | |
  • Thank you!! I was able to extend this answer to fit a single state. – briennakh Mar 11 at 0:27
2

Considering that heatmap.js does not output the heatmap as geojson but just json and clip it to a bounding box rectangle, it is difficult to clip it in the way you want it.

  • It is better to create a heatmap raster using a QGIS's Heatmap tool that you can find under Processing menu > Toolbox Toolbox > Interpolation. enter image description here
  • If you want to automate it, you can use pyQGIS processsing library.
  • Then you can clip the heatmap boundary using Gdal or QGIS.
  • Finally, you can use Geoserver to render the clipped heatmap with style and semi-transparency.
| improve this answer | |
  • Yeah I've actually recreated this heat map in QGIS. I am looking for a way to let the web user zoom onto a state on a choropleth map of the United States, then the zoomed-in view shows a heat map for the state alone. It looks like Geoserver might actually allow this... Looking into this. – briennakh Mar 10 at 3:44
  • In that case, you might need clip the heatmap using each feature (states), to have state specific heatmaps. This might also be possible but seemingly only on Geoserver user interface. gis.stackexchange.com/a/211523/37262 – wondim Mar 10 at 7:10
  • This answer was really helpful in finding out about a potential workaround for higher precision in "clipping", thank you! I went down this avenue and also found out about qgis2web which is another possible way to integrate my maps. – briennakh Mar 11 at 0:37
  • Glad it helped! – wondim Mar 11 at 2:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.