Allow to render several keys/steps with a given range

- At low level point renderer now allows to receive several keys
 in renderTile.
- leaflet and gmaps layers expose renderRange(start, end) to be able
 to set and render a range of steps.

This closes #246
This commit is contained in:
Raul Ochoa 2015-12-14 10:56:07 +01:00
parent 5413f4cb82
commit 2d5542a28c
6 changed files with 137 additions and 30 deletions

View File

@ -9,7 +9,7 @@ function GMapsTorqueLayer(options) {
if (!torque.isBrowserSupported()) { if (!torque.isBrowserSupported()) {
throw new Error("browser is not supported by torque"); throw new Error("browser is not supported by torque");
} }
this.key = 0; this.keys = [0];
this.shader = null; this.shader = null;
this.ready = false; this.ready = false;
this.options = torque.extend({}, options); this.options = torque.extend({}, options);
@ -31,7 +31,7 @@ function GMapsTorqueLayer(options) {
this.animator = new torque.Animator(function(time) { this.animator = new torque.Animator(function(time) {
var k = time | 0; var k = time | 0;
if(self.key !== k) { if(self.getKey() !== k) {
self.setKey(k); self.setKey(k);
} }
}, torque.extend(torque.clone(this.options), { }, torque.extend(torque.clone(this.options), {
@ -102,7 +102,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
self.fire('change:steps', { self.fire('change:steps', {
steps: self.provider.getSteps() steps: self.provider.getSteps()
}); });
self.setKey(self.key); self.setKey(self.getKey());
}; };
this.provider = new this.providers[this.options.provider](this.options); this.provider = new this.providers[this.options.provider](this.options);
@ -211,7 +211,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
if (tile) { if (tile) {
pos = this.getTilePos(tile.coord); pos = this.getTilePos(tile.coord);
ctx.setTransform(1, 0, 0, 1, pos.x, pos.y); ctx.setTransform(1, 0, 0, 1, pos.x, pos.y);
this.renderer.renderTile(tile, this.key); this.renderer.renderTile(tile, this.keys);
} }
} }
this.renderer.applyFilters(); this.renderer.applyFilters();
@ -233,10 +233,18 @@ GMapsTorqueLayer.prototype = torque.extend({},
* accumulated * accumulated
*/ */
setKey: function(key) { setKey: function(key) {
this.key = key; this.setKeys([key]);
this.animator.step(key); },
setKeys: function(keys) {
this.keys = keys;
this.animator.step(this.getKey());
this.redraw(); this.redraw();
this.fire('change:time', { time: this.getTime(), step: this.key }); this.fire('change:time', { time: this.getTime(), step: this.getKey() });
},
getKey: function() {
return this.keys[0];
}, },
/** /**
@ -250,6 +258,20 @@ GMapsTorqueLayer.prototype = torque.extend({},
this.setKey(time); this.setKey(time);
}, },
renderRange: function(start, end) {
this.pause();
var keys = [];
for (var i = start; i <= end; i++) {
keys.push(i);
}
this.setKeys(keys);
},
resetRenderRange: function() {
this.stop();
this.play();
},
/** /**
* transform from animation step to Date object * transform from animation step to Date object
* that contains the animation time * that contains the animation time
@ -272,7 +294,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
}, },
getStep: function() { getStep: function() {
return this.key; return this.getKey();
}, },
/** /**
@ -280,7 +302,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
* in the defined column. Date object * in the defined column. Date object
*/ */
getTime: function() { getTime: function() {
return this.stepToTime(this.key); return this.stepToTime(this.getKey());
}, },
/** /**
@ -328,7 +350,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
*/ */
getValues: function(step) { getValues: function(step) {
var values = []; var values = [];
step = step === undefined ? this.key: step; step = step === undefined ? this.getKey(): step;
var t, tile; var t, tile;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];
@ -338,7 +360,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
}, },
getValueForPos: function(x, y, step) { getValueForPos: function(x, y, step) {
step = step === undefined ? this.key: step; step = step === undefined ? this.getKey(): step;
var t, tile, pos, value = null, xx, yy; var t, tile, pos, value = null, xx, yy;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];
@ -402,7 +424,7 @@ GMapsTiledTorqueLayer.prototype = torque.extend({}, CanvasTileLayer.prototype, {
initialize: function(options) { initialize: function(options) {
var self = this; var self = this;
this.key = 0; this.keys = [0];
this.options.renderer = this.options.renderer || 'pixel'; this.options.renderer = this.options.renderer || 'pixel';
this.options.provider = this.options.provider || 'sql_api'; this.options.provider = this.options.provider || 'sql_api';
@ -438,12 +460,12 @@ GMapsTiledTorqueLayer.prototype = torque.extend({}, CanvasTileLayer.prototype, {
this.renderer.setCanvas(canvas); this.renderer.setCanvas(canvas);
var accum = this.renderer.accumulate(tile.data, this.key); var accum = this.renderer.accumulate(tile.data, this.getKey());
this.renderer.renderTileAccum(accum, 0, 0); this.renderer.renderTileAccum(accum, 0, 0);
}, },
setKey: function(key) { setKey: function(key) {
this.key = key; this.keys = [key];
this.redraw(); this.redraw();
}, },

View File

@ -26,7 +26,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
throw new Error("browser is not supported by torque"); throw new Error("browser is not supported by torque");
} }
options.tileLoader = true; options.tileLoader = true;
this.key = 0; this.keys = [0];
this.prevRenderedKey = 0; this.prevRenderedKey = 0;
if (options.cartocss) { if (options.cartocss) {
torque.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss)); torque.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss));
@ -39,7 +39,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
this.animator = new torque.Animator(function(time) { this.animator = new torque.Animator(function(time) {
var k = time | 0; var k = time | 0;
if(self.key !== k) { if(self.getKey() !== k) {
self.setKey(k, { direct: true }); self.setKey(k, { direct: true });
} }
}, torque.extend(torque.clone(options), { }, torque.extend(torque.clone(options), {
@ -84,7 +84,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
self.fire('change:steps', { self.fire('change:steps', {
steps: self.provider.getSteps() steps: self.provider.getSteps()
}); });
self.setKey(self.key); self.setKey(self.getKey());
}; };
this.renderer.on("allIconsLoaded", this.render.bind(this)); this.renderer.on("allIconsLoaded", this.render.bind(this));
@ -239,7 +239,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
// all the points // all the points
this.renderer._ctx.drawImage(tile._tileCache, 0, 0); this.renderer._ctx.drawImage(tile._tileCache, 0, 0);
} else { } else {
this.renderer.renderTile(tile, this.key); this.renderer.renderTile(tile, this.keys);
} }
} }
} }
@ -248,7 +248,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
// prepare caches if the animation is not running // prepare caches if the animation is not running
// don't cache if the key has just changed, this avoids to cache // don't cache if the key has just changed, this avoids to cache
// when the user is dragging, it only cache when the map is still // when the user is dragging, it only cache when the map is still
if (!this.animator.isRunning() && this.key === this.prevRenderedKey) { if (!this.animator.isRunning() && this.getKey() === this.prevRenderedKey) {
var tile_size = this.renderer.TILE_SIZE; var tile_size = this.renderer.TILE_SIZE;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];
@ -268,7 +268,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
} }
} }
this.prevRenderedKey = this.key; this.prevRenderedKey = this.getKey();
}, },
@ -278,11 +278,28 @@ L.TorqueLayer = L.CanvasLayer.extend({
* accumulated * accumulated
*/ */
setKey: function(key, options) { setKey: function(key, options) {
this.key = key; this.setKeys([key], options);
this.animator.step(key); },
setKeys: function(keys, options) {
this.keys = keys;
this.animator.step(this.getKey());
this._clearTileCaches(); this._clearTileCaches();
this.redraw(options && options.direct); this.redraw(options && options.direct);
this.fire('change:time', { time: this.getTime(), step: this.key }); this.fire('change:time', {
time: this.getTime(),
step: this.getKey(),
start: this.getKey(),
end: this.getLastKey()
});
},
getKey: function() {
return this.keys[0];
},
getLastKey: function() {
return this.keys[this.keys.length - 1];
}, },
/** /**
@ -296,6 +313,20 @@ L.TorqueLayer = L.CanvasLayer.extend({
this.setKey(time); this.setKey(time);
}, },
renderRange: function(start, end) {
this.pause();
var keys = [];
for (var i = start; i <= end; i++) {
keys.push(i);
}
this.setKeys(keys);
},
resetRenderRange: function() {
this.stop();
this.play();
},
/** /**
* transform from animation step to Date object * transform from animation step to Date object
* that contains the animation time * that contains the animation time
@ -317,7 +348,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
}, },
getStep: function() { getStep: function() {
return this.key; return this.getKey();
}, },
/** /**
@ -325,7 +356,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
* in the defined column. Date object * in the defined column. Date object
*/ */
getTime: function() { getTime: function() {
return this.stepToTime(this.key); return this.stepToTime(this.getKey());
}, },
/** /**
@ -381,7 +412,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
*/ */
getValues: function(step) { getValues: function(step) {
var values = []; var values = [];
step = step === undefined ? this.key: step; step = step === undefined ? this.getKey(): step;
var t, tile; var t, tile;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];
@ -394,7 +425,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
* return the value for position relative to map coordinates. null for no value * return the value for position relative to map coordinates. null for no value
*/ */
getValueForPos: function(x, y, step) { getValueForPos: function(x, y, step) {
step = step === undefined ? this.key: step; step = step === undefined ? this.getKey(): step;
var t, tile, pos, value = null, xx, yy; var t, tile, pos, value = null, xx, yy;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];

View File

@ -172,13 +172,19 @@ var Filters = require('./torque_filters');
// //
// renders all the layers (and frames for each layer) from cartocss // renders all the layers (and frames for each layer) from cartocss
// //
renderTile: function(tile, key, callback) { renderTile: function(tile, keys, callback) {
if (this._iconsToLoad > 0) { if (this._iconsToLoad > 0) {
this.on('allIconsLoaded', function() { this.on('allIconsLoaded', function() {
this.renderTile.apply(this, [tile, key, callback]); this.renderTile.apply(this, [tile, keys, callback]);
}); });
return false; return false;
} }
// convert scalar key to keys array
if (typeof keys.length === 'undefined') {
keys = [keys];
}
var prof = Profiler.metric('torque.renderer.point.renderLayers').start(); var prof = Profiler.metric('torque.renderer.point.renderLayers').start();
var layers = this._shader.getLayers(); var layers = this._shader.getLayers();
for(var i = 0, n = layers.length; i < n; ++i ) { for(var i = 0, n = layers.length; i < n; ++i ) {
@ -189,7 +195,9 @@ var Filters = require('./torque_filters');
for(var fr = 0; fr < layer.frames().length; ++fr) { for(var fr = 0; fr < layer.frames().length; ++fr) {
var frame = layer.frames()[fr]; var frame = layer.frames()[fr];
var fr_sprites = sprites[frame] || (sprites[frame] = []); var fr_sprites = sprites[frame] || (sprites[frame] = []);
this._renderTile(tile, key - frame, frame, fr_sprites, layer); for (var k = 0, len = keys.length; k < len; k++) {
this._renderTile(tile, keys[k] - frame, frame, fr_sprites, layer);
}
} }
} }
} }

View File

@ -5,6 +5,11 @@ QUnit.module('renderer/point');
var IMAGE_DIFF_TOLERANCE = 4 / 100; var IMAGE_DIFF_TOLERANCE = 4 / 100;
// HOW TO debug image output
// -------------------------
// Once you have a valid canvas and no errors, it's possible to write to disk the canvas buffer as a png image with:
// require('fs').writeFileSync('/tmp/torque-acceptance-test-tile.png', canvas.toBuffer(), {encoding: null});
asyncTest('navy example', function(assert) { asyncTest('navy example', function(assert) {
var cartocss = [ var cartocss = [
'Map {', 'Map {',
@ -64,3 +69,43 @@ asyncTest('basic heatmap', function(assert) {
QUnit.start(); QUnit.start();
}); });
}); });
asyncTest('render multiple steps', function(assert) {
var CARTOCSS = [
'Map {',
' -torque-frame-count: 360;',
' -torque-animation-duration: 30;',
' -torque-time-attribute: "cartodb_id";',
' -torque-aggregation-function: "count(cartodb_id)";',
' -torque-resolution: 1;',
' -torque-data-aggregation: linear;',
'}',
'#generate_series {',
' comp-op: lighter;',
' marker-fill-opacity: 0.9;',
' marker-line-color: #FFF;',
' marker-line-width: 0;',
' marker-line-opacity: 1;',
' marker-type: rectable;',
' marker-width: 6;',
' marker-fill: #0F3B82;',
'}'
].join('\n');
var steps = [];
for (var i = 20; i <= 50; i++) {
steps.push(i);
}
// Dataset can be regenerated with:
// SELECT
// s + 181 as cartodb_id,
// st_transform(ST_SetSRID (st_makepoint(s, 20 + 10*sin(s)), 4326), 3857) as the_geom_webmercator
// FROM generate_series(-180, 180, 1) as s
pointRenderer.getTile('generate_series_sin-2-0-1.torque.json', CARTOCSS, 2, 0, 1, steps, function(err, canvas) {
assert.ok(!err, 'no error while getting tile');
var imageDiff = image.compare(canvas.toBuffer(), 'generate_series_sin-2-0-1.png');
assert.ok(imageDiff < IMAGE_DIFF_TOLERANCE, 'image not matching, probably not rendering several steps');
QUnit.start();
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

File diff suppressed because one or more lines are too long