Skip to content

Commit

Permalink
Fixed and updated intensity normalization and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
aashish24 committed Apr 14, 2016
1 parent 525088d commit 8023c46
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 91 deletions.
59 changes: 25 additions & 34 deletions src/canvas/heatmapFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ var heatmapFeature = require('../heatmapFeature');
//////////////////////////////////////////////////////////////////////////////
/**
* Create a new instance of class heatmapFeature
* The rendering borrows from
* Inspired from
* https://github.com/mourner/simpleheat/blob/gh-pages/simpleheat.js
*
* @class
Expand All @@ -14,7 +14,7 @@ var heatmapFeature = require('../heatmapFeature');
* @returns {canvas_heatmapFeature}
*/
//////////////////////////////////////////////////////////////////////////////
canvas_heatmapFeature = function (arg) {
var canvas_heatmapFeature = function (arg) {
'use strict';

if (!(this instanceof canvas_heatmapFeature)) {
Expand All @@ -32,17 +32,6 @@ canvas_heatmapFeature = function (arg) {
s_init = this._init,
s_update = this._update;

////////////////////////////////////////////////////////////////////////////
/**
* Build
* @override
*/
////////////////////////////////////////////////////////////////////////////
this._build = function () {
s_update.call(m_this);
return m_this;
};

////////////////////////////////////////////////////////////////////////////
/**
* Meta functions for converting from geojs styles to canvas.
Expand All @@ -52,10 +41,11 @@ canvas_heatmapFeature = function (arg) {
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+')';
c.hasOwnProperty('g') &&
c.hasOwnProperty('b') &&
c.hasOwnProperty('a')) {
color = 'rgba(' + 255 * c.r + ',' + 255 * c.g + ','
+ 255 * c.b + ',' + c.a + ')';
}
return color;
};
Expand All @@ -70,10 +60,10 @@ canvas_heatmapFeature = function (arg) {
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 = 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;
Expand All @@ -97,11 +87,11 @@ canvas_heatmapFeature = function (arg) {
*/
////////////////////////////////////////////////////////////////////////////
this._createCircle = function () {
var circle, ctx, r, r2;
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'),
circle = m_this._circle = document.createElement('canvas');
ctx = circle.getContext('2d');
r = m_this.style('radius');
blur = m_this.style('blurRadius');

r2 = blur + r;
Expand All @@ -128,14 +118,15 @@ canvas_heatmapFeature = function (arg) {
////////////////////////////////////////////////////////////////////////////
this._colorize = function (pixels, gradient) {
var i, j;
for (i = 0; i < pixels.length; i+=4) {
j = pixels[i + 3] * 4; // get opacity from the temporary canvas image,
// then multiply by 4 to get the color index on linear gradient
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];
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];
}
}
};
Expand Down Expand Up @@ -164,7 +155,7 @@ canvas_heatmapFeature = function (arg) {
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)
context2d.putImageData(pixelArray, 0, 0);
return m_this;
};

Expand Down Expand Up @@ -213,4 +204,4 @@ inherit(canvas_heatmapFeature, heatmapFeature);

// Now register it
registerFeature('canvas', 'heatmap', canvas_heatmapFeature);
module.exports = canvas_heatmapFeature;
module.exports = canvas_heatmapFeature;
88 changes: 61 additions & 27 deletions src/heatmapFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,34 @@ var feature = require('./feature');
* @extends geo.feature
* @param {Object|string|Function} [color] Color transfer function that.
* will be used to evaluate color of each pixel using normalized intensity
* as the look up value.
* @param {number|Function} [opacity=1] Opacity for each pixel.
* as the look up value.
* @param {number|Function} [opacity=1] Homogeneous opacity for each pixel.
* @param {Object|Function} [radius=10] Radius of a point in terms of number
* of pixels.
* @param {Object|Function} [blurRadius=10] Gaussian blur radius.
* of pixels.
* @param {Object|Function} [blurRadius=10] Gaussian blur radius for each
* point in terms of number of pixels.
* @param {Object|Function} [position] Position of the data. Default is
* (data). The position is an Object which specifies the location of the
* (data). The position is an Object which specifies the location of the
* data in geo-spatial context.
* @param {boolean} [intensity] Scalar value that of each data point. Scalar
* value will be used to compute the weight for each data point for the final
* computation of its opacity.
* @param {boolean} [maxIntensity=1] Maximum intensity of the data. Maximum
* intensity will be used to normalize all intensities with a dataset.
* @param {boolean} [intensity] Scalar value of each data point. Scalar
* value must be a positive real number and will be used to compute
* the weight for each data point.
* @param {boolean} [maxIntensity=null] Maximum intensity of the data. Maximum
* intensity must be a positive real number and will be used to normalize all
* intensities with a dataset. If no value is given, then a it will
* be computed.
* @param {boolean} [minIntensity=null] Minimum intensity of the data. Minimum
* intensity must be a positive real number will be used to normalize all
* intensities with a dataset. If no value is given, then a it will
* be computed.
* @returns {geo.heatmapFetures}
*/
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
var heatmapFeature = function (arg) {
'use strict';
if (!(this instanceof heatmapFeature)){
if (!(this instanceof heatmapFeature)) {
return new heatmapFeature(arg);
}
arg = arg || {};
Expand All @@ -47,13 +54,12 @@ var heatmapFeature = function (arg) {
m_intensity,
m_maxIntensity,
m_minIntensity,
s_init = this._init,
s_data = this.data;
s_init = this._init;

m_position = arg.position || function (d) { return d; };
m_intensity = arg.intensity || function (d) { return 1; };
m_maxIntensity = arg.maxIntensity || 1;
m_minIntensity = arg.minIntensity ? arg.minIntensity : 0;
m_maxIntensity = arg.maxIntensity || null;
m_minIntensity = arg.minIntensity ? arg.minIntensity : null;

////////////////////////////////////////////////////////////////////////////
/**
Expand Down Expand Up @@ -137,16 +143,16 @@ var heatmapFeature = function (arg) {

var defaultStyle = $.extend(
{},
{
opacity: 0.1,
radius: 10,
blurRadius: 10,
color: {0: {r: 0, g: 0, b: 0.0, a: 0.0},
.25: {r: 0, g: 0, b: 1, a: 0.5},
.5: {r: 0, g: 1, b: 1, a: 0.6},
.75: {r: 1, g: 1, b: 0, a: 0.7},
1: {r: 1, g: 0, b: 0, a: 0.8}}
},
{
opacity: 0.1,
radius: 10,
blurRadius: 10,
color: {0: {r: 0, g: 0, b: 0.0, a: 0.0},
0.25: {r: 0, g: 0, b: 1, a: 0.5},
0.5: {r: 0, g: 1, b: 1, a: 0.6},
0.75: {r: 1, g: 1, b: 0, a: 0.7},
1: {r: 1, g: 0, b: 0, a: 0.8}}
},
arg.style === undefined ? {} : arg.style
);

Expand All @@ -157,10 +163,38 @@ var heatmapFeature = function (arg) {
}
};

////////////////////////////////////////////////////////////////////////////
/**
* Build
* @override
*/
////////////////////////////////////////////////////////////////////////////
this._build = function () {
var data = m_this.data(),
intensity = null;

if (!m_maxIntensity || !m_minIntensity) {
data.forEach(function (d) {
intensity = m_this.intensity()(d);
if (!m_maxIntensity && !m_minIntensity) {
m_maxIntensity = m_minIntensity = intensity;
} else {
if (intensity > m_maxIntensity) {
m_maxIntensity = intensity;
}
if (intensity < m_minIntensity) {
m_minIntensity = intensity;
}
}
});
}

return m_this;
};

this._init(arg);
return this;

};

inherit(heatmapFeature, feature)
inherit(heatmapFeature, feature);
module.exports = heatmapFeature;
53 changes: 23 additions & 30 deletions tests/cases/heatmap.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,10 @@ describe('canvas heatmap feature', function () {
var stepAnimationFrame = require('../test-utils').stepAnimationFrame;
var unmockAnimationFrame = require('../test-utils').unmockAnimationFrame;

var map, width = 800, height = 600, layer, feature1, feature2,
testData = "Strength, Lat,Lon\
0.6,42.8584,-70.9301\
0.233,42.2776,-83.7409\
0.2,42.2776,-83.7409,";
testData = testData.split(/\r\n|\n|\r/);
testData = testData.map( function (r) {
var fields = r.split(',');
return [fields[12], fields[24], fields[25]].map(parseFloat);
});
testData.splice(0, 1);
var map, width = 800, height = 600, layer, feature1,
testData = [[0.6, 42.8584, -70.9301],
[0.233, 42.2776, -83.7409],
[0.2, 42.2776, -83.7409]];

it('Setup map', function () {
map = geo.map({node: '#map-canvas-heatmap-feature', center: [0, 0], zoom: 3});
Expand All @@ -38,24 +31,24 @@ describe('canvas heatmap feature', function () {

it('Add features to a layer', function () {
feature1 = layer.createFeature('heatmap')
.data(testData)
.intensity(function (d) {
return d[0];
})
.position(function (d) {
return {
x: d[2],
y: d[1]
};
})
.style('radius', 5)
.style('blurRadius', 15)
.style('opacity', 1.0);
.data(testData)
.intensity(function (d) {
return d[0];
})
.position(function (d) {
return {
x: d[2],
y: d[1]
};
})
.style('radius', 5)
.style('blurRadius', 15)
.style('opacity', 1.0);

mockAnimationFrame();
map.draw();
stepAnimationFrame(new Date().getTime());
expect(layer.children().length).toBe(1)
expect(layer.children().length).toBe(1);
unmockAnimationFrame();
});

Expand All @@ -69,11 +62,11 @@ describe('canvas heatmap feature', function () {
});

it('Validate maximum intensity', function () {
expect(feature1.maxIntensity()).toBe(1);
expect(feature1.maxIntensity()).toBe(0.6);
});

it('Validate minimum intensity', function () {
expect(feature1.minIntensity()).toBe(0);
expect(feature1.minIntensity()).toBe(0.2);
});

it('Remove a feature from a layer', function () {
Expand All @@ -82,13 +75,13 @@ describe('canvas heatmap feature', function () {
});

it('Compute gradient', function () {
feature1.style("color", {0: {r: 0, g: 0, b: 0.0, a: 0.0},
feature1.style('color', {0: {r: 0, g: 0, b: 0.0, a: 0.0},
0.25: {r: 0, g: 0, b: 1, a: 0.5},
0.5: {r: 0, g: 1, b: 1, a: 0.6},
0.75: {r: 1, g: 1, b: 0, a: 0.7},
1: {r: 1, g: 0, b: 0, a: 0.1}});
feature1._computeGradient();
expect(layer.node()[0].children[0].getContext('2d').
getImageData(1, 0, 1, 1).data.length).toBe(4)
expect(layer.node()[0].children[0].getContext('2d')
.getImageData(1, 0, 1, 1).data.length).toBe(4);
});
});

0 comments on commit 8023c46

Please sign in to comment.