Skip to content

Commit

Permalink
Merge pull request #557 from OpenGeoscience/canvas_heatmap
Browse files Browse the repository at this point in the history
Canvas heatmap
  • Loading branch information
aashish24 committed Apr 18, 2016
2 parents 1ff57c7 + e249600 commit 1b56c2d
Show file tree
Hide file tree
Showing 11 changed files with 560 additions and 2 deletions.
9 changes: 9 additions & 0 deletions examples/heatmap/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"path": "heatmap",
"title": "Heatmap Feature",
"exampleCss": ["main.css"],
"exampleJs": ["main.js"],
"about": {
"text": "This example shows how to add a heatmap to a map."
}
}
1 change: 1 addition & 0 deletions examples/heatmap/index.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extends ../common/templates/index.jade
Empty file added examples/heatmap/main.css
Empty file.
49 changes: 49 additions & 0 deletions examples/heatmap/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Run after the DOM loads
$(function () {
'use strict';

var map = geo.map({
node: '#map',
center: {
x: -98,
y: 39
},
zoom: 3
});

$.ajax('https://s3.amazonaws.com/uploads.hipchat.com/446632/3114847/4dZfl0YfZpTfYzq/AdderallCities2015.csv', {
success: function (resp) {
var rows = resp.split(/\r\n|\n|\r/);
rows = rows.map( function (r) {
var fields = r.split(',');
return [fields[12], fields[24], fields[25]].map(parseFloat);
});
rows.splice(0, 1);

var layer = map.createLayer('feature', {renderer: 'canvas'});
var heatmap = layer.createFeature('heatmap')
.data(rows)
.intensity(function (d) {
return d[0];
})
.position(function (d) {
return {
x: d[2],
y: d[1]
};
})
.style('radius', 10)
.style('blurRadius', 30)
.style('opacity', 1.0)
.style('color',
{0: {r: 0, g: 0, b: 0, a: 0.0},
0.25: {r: 0, g: 1, b: 0, a: 0.5},
0.5: {r: 1, g: 1, b: 0, a: 0.8},
1: {r: 1, g: 0, b: 0, a: 1.0}});
map.draw();
}
});

var base = map.createLayer('osm');
map.draw();
});
Binary file added examples/heatmap/thumb.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions sources.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"renderer.js",
"osmLayer.js",
"domRenderer.js",
"choroplethFeature.js"
"choroplethFeature.js",
"heatmapFeature.js"
]
},
"geo.util": {
Expand Down Expand Up @@ -77,7 +78,8 @@
"init.js",
"quadFeature.js",
"canvasRenderer.js",
"tileLayer.js"
"tileLayer.js",
"heatmapFeature.js"
]
},
"geo.d3": {
Expand Down
207 changes: 207 additions & 0 deletions src/canvas/heatmapFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
var inherit = require('../inherit');
var registerFeature = require('../registry').registerFeature;
var heatmapFeature = require('../heatmapFeature');

//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of class heatmapFeature
* Inspired from
* https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js
*
* @class geo.canvas.heatmapFeature
* @param {Object} arg Options object
* @extends geo.heatmapFeature
* @returns {canvas_heatmapFeature}
*/
//////////////////////////////////////////////////////////////////////////////
var canvas_heatmapFeature = function (arg) {
'use strict';

if (!(this instanceof canvas_heatmapFeature)) {
return new canvas_heatmapFeature(arg);
}
heatmapFeature.call(this, arg);

////////////////////////////////////////////////////////////////////////////
/**
* @private
*/
////////////////////////////////////////////////////////////////////////////
var m_this = this,
s_exit = this._exit,
s_init = this._init,
s_update = this._update;

////////////////////////////////////////////////////////////////////////////
/**
* Meta functions for converting from geojs styles to canvas.
* @private
*/
////////////////////////////////////////////////////////////////////////////
this._convertColor = function (c) {
var color;
if (c.hasOwnProperty('r') &&
c.hasOwnProperty('g') &&
c.hasOwnProperty('b') &&
c.hasOwnProperty('a')) {
color = 'rgba(' + 255 * c.r + ',' + 255 * c.g + ','
+ 255 * c.b + ',' + c.a + ')';
}
return color;
};

////////////////////////////////////////////////////////////////////////////
/**
* Compute gradient (color lookup table)
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._computeGradient = function () {
var canvas, stop, context2d, gradient, colors;

if (!m_this._grad) {
canvas = document.createElement('canvas');
context2d = canvas.getContext('2d');
gradient = context2d.createLinearGradient(0, 0, 0, 256);
colors = m_this.style('color');

canvas.width = 1;
canvas.height = 256;

for (stop in colors) {
gradient.addColorStop(stop, m_this._convertColor(colors[stop]));
}

context2d.fillStyle = gradient;
context2d.fillRect(0, 0, 1, 256);
m_this._grad = context2d.getImageData(0, 0, 1, 256).data;
}

return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Create circle for each data point
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._createCircle = function () {
var circle, ctx, r, r2, blur;
if (!m_this._circle) {
circle = m_this._circle = document.createElement('canvas');
ctx = circle.getContext('2d');
r = m_this.style('radius');
blur = m_this.style('blurRadius');

r2 = blur + r;

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();
m_this._circle = circle;
}
return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Compute color for each pixel on the screen
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._colorize = function (pixels, gradient) {
var i, j;
for (i = 0; i < pixels.length; i += 4) {
// Get opacity from the temporary canvas image,
// then multiply by 4 to get the color index on linear gradient
j = pixels[i + 3] * 4;
if (j) {
pixels[i] = gradient[j];
pixels[i + 1] = gradient[j + 1];
pixels[i + 2] = gradient[j + 2];
pixels[i + 3] = m_this.style('opacity') * gradient[j + 3];
}
}
};

////////////////////////////////////////////////////////////////////////////
/**
* Render each data point on canvas
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._renderOnCanvas = function (context2d, map) {
var data = m_this.data() || [],
radius = m_this.style('radius') + m_this.style('blurRadius'),
pos, intensity, canvas, pixelArray;
m_this._createCircle();
m_this._computeGradient();
data.forEach(function (d) {
pos = m_this.layer().map().gcsToDisplay(m_this.position()(d));
intensity = (m_this.intensity()(d) - m_this.minIntensity()) /
(m_this.maxIntensity() - m_this.minIntensity());
// Small values are not visible because globalAlpha < .01
// cannot be read from imageData
context2d.globalAlpha = intensity < 0.01 ? 0.01 : intensity;
context2d.drawImage(m_this._circle, pos.x - radius, pos.y - radius);
});
canvas = m_this.layer().canvas()[0];
pixelArray = context2d.getImageData(0, 0, canvas.width, canvas.height);
m_this._colorize(pixelArray.data, m_this._grad);
context2d.putImageData(pixelArray, 0, 0);
return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Initialize
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._init = function () {
s_init.call(m_this, arg);
return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Update
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._update = function () {
s_update.call(m_this);
if (m_this.buildTime().getMTime() <= m_this.dataTime().getMTime() ||
m_this.updateTime().getMTime() < m_this.getMTime()) {
m_this._build();
}
m_this.updateTime().modified();
return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Destroy
* @protected
*/
////////////////////////////////////////////////////////////////////////////
this._exit = function () {
s_exit.call(m_this);
};

m_this._init(arg);
return this;
};

inherit(canvas_heatmapFeature, heatmapFeature);

// Now register it
registerFeature('canvas', 'heatmap', canvas_heatmapFeature);
module.exports = canvas_heatmapFeature;
1 change: 1 addition & 0 deletions src/canvas/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
module.exports = {
canvasRenderer: require('./canvasRenderer'),
quadFeature: require('./quadFeature'),
heatmapFeature: require('./heatmapFeature'),
tileLayer: require('./tileLayer')
};
Loading

0 comments on commit 1b56c2d

Please sign in to comment.