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:
parent
5413f4cb82
commit
2d5542a28c
@ -9,7 +9,7 @@ function GMapsTorqueLayer(options) {
|
||||
if (!torque.isBrowserSupported()) {
|
||||
throw new Error("browser is not supported by torque");
|
||||
}
|
||||
this.key = 0;
|
||||
this.keys = [0];
|
||||
this.shader = null;
|
||||
this.ready = false;
|
||||
this.options = torque.extend({}, options);
|
||||
@ -31,7 +31,7 @@ function GMapsTorqueLayer(options) {
|
||||
|
||||
this.animator = new torque.Animator(function(time) {
|
||||
var k = time | 0;
|
||||
if(self.key !== k) {
|
||||
if(self.getKey() !== k) {
|
||||
self.setKey(k);
|
||||
}
|
||||
}, torque.extend(torque.clone(this.options), {
|
||||
@ -102,7 +102,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
self.fire('change:steps', {
|
||||
steps: self.provider.getSteps()
|
||||
});
|
||||
self.setKey(self.key);
|
||||
self.setKey(self.getKey());
|
||||
};
|
||||
|
||||
this.provider = new this.providers[this.options.provider](this.options);
|
||||
@ -211,7 +211,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
if (tile) {
|
||||
pos = this.getTilePos(tile.coord);
|
||||
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();
|
||||
@ -233,10 +233,18 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
* accumulated
|
||||
*/
|
||||
setKey: function(key) {
|
||||
this.key = key;
|
||||
this.animator.step(key);
|
||||
this.setKeys([key]);
|
||||
},
|
||||
|
||||
setKeys: function(keys) {
|
||||
this.keys = keys;
|
||||
this.animator.step(this.getKey());
|
||||
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);
|
||||
},
|
||||
|
||||
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
|
||||
* that contains the animation time
|
||||
@ -272,7 +294,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
},
|
||||
|
||||
getStep: function() {
|
||||
return this.key;
|
||||
return this.getKey();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -280,7 +302,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
* in the defined column. Date object
|
||||
*/
|
||||
getTime: function() {
|
||||
return this.stepToTime(this.key);
|
||||
return this.stepToTime(this.getKey());
|
||||
},
|
||||
|
||||
/**
|
||||
@ -328,7 +350,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
*/
|
||||
getValues: function(step) {
|
||||
var values = [];
|
||||
step = step === undefined ? this.key: step;
|
||||
step = step === undefined ? this.getKey(): step;
|
||||
var t, tile;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
@ -338,7 +360,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
},
|
||||
|
||||
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;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
@ -402,7 +424,7 @@ GMapsTiledTorqueLayer.prototype = torque.extend({}, CanvasTileLayer.prototype, {
|
||||
|
||||
initialize: function(options) {
|
||||
var self = this;
|
||||
this.key = 0;
|
||||
this.keys = [0];
|
||||
|
||||
this.options.renderer = this.options.renderer || 'pixel';
|
||||
this.options.provider = this.options.provider || 'sql_api';
|
||||
@ -438,12 +460,12 @@ GMapsTiledTorqueLayer.prototype = torque.extend({}, CanvasTileLayer.prototype, {
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
setKey: function(key) {
|
||||
this.key = key;
|
||||
this.keys = [key];
|
||||
this.redraw();
|
||||
},
|
||||
|
||||
|
@ -26,7 +26,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
throw new Error("browser is not supported by torque");
|
||||
}
|
||||
options.tileLoader = true;
|
||||
this.key = 0;
|
||||
this.keys = [0];
|
||||
this.prevRenderedKey = 0;
|
||||
if (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) {
|
||||
var k = time | 0;
|
||||
if(self.key !== k) {
|
||||
if(self.getKey() !== k) {
|
||||
self.setKey(k, { direct: true });
|
||||
}
|
||||
}, torque.extend(torque.clone(options), {
|
||||
@ -84,7 +84,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
self.fire('change:steps', {
|
||||
steps: self.provider.getSteps()
|
||||
});
|
||||
self.setKey(self.key);
|
||||
self.setKey(self.getKey());
|
||||
};
|
||||
|
||||
this.renderer.on("allIconsLoaded", this.render.bind(this));
|
||||
@ -239,7 +239,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
// all the points
|
||||
this.renderer._ctx.drawImage(tile._tileCache, 0, 0);
|
||||
} 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
|
||||
// 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
|
||||
if (!this.animator.isRunning() && this.key === this.prevRenderedKey) {
|
||||
if (!this.animator.isRunning() && this.getKey() === this.prevRenderedKey) {
|
||||
var tile_size = this.renderer.TILE_SIZE;
|
||||
for(t in this._tiles) {
|
||||
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
|
||||
*/
|
||||
setKey: function(key, options) {
|
||||
this.key = key;
|
||||
this.animator.step(key);
|
||||
this.setKeys([key], options);
|
||||
},
|
||||
|
||||
setKeys: function(keys, options) {
|
||||
this.keys = keys;
|
||||
this.animator.step(this.getKey());
|
||||
this._clearTileCaches();
|
||||
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);
|
||||
},
|
||||
|
||||
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
|
||||
* that contains the animation time
|
||||
@ -317,7 +348,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
},
|
||||
|
||||
getStep: function() {
|
||||
return this.key;
|
||||
return this.getKey();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -325,7 +356,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
* in the defined column. Date object
|
||||
*/
|
||||
getTime: function() {
|
||||
return this.stepToTime(this.key);
|
||||
return this.stepToTime(this.getKey());
|
||||
},
|
||||
|
||||
/**
|
||||
@ -381,7 +412,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
*/
|
||||
getValues: function(step) {
|
||||
var values = [];
|
||||
step = step === undefined ? this.key: step;
|
||||
step = step === undefined ? this.getKey(): step;
|
||||
var t, tile;
|
||||
for(t in this._tiles) {
|
||||
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
|
||||
*/
|
||||
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;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
|
@ -172,13 +172,19 @@ var Filters = require('./torque_filters');
|
||||
//
|
||||
// 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) {
|
||||
this.on('allIconsLoaded', function() {
|
||||
this.renderTile.apply(this, [tile, key, callback]);
|
||||
this.renderTile.apply(this, [tile, keys, callback]);
|
||||
});
|
||||
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 layers = this._shader.getLayers();
|
||||
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) {
|
||||
var frame = layer.frames()[fr];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,11 @@ QUnit.module('renderer/point');
|
||||
|
||||
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) {
|
||||
var cartocss = [
|
||||
'Map {',
|
||||
@ -63,4 +68,44 @@ asyncTest('basic heatmap', function(assert) {
|
||||
assert.ok(imageDiff < IMAGE_DIFF_TOLERANCE, 'heatmap tile is ok');
|
||||
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();
|
||||
});
|
||||
});
|
BIN
test/fixtures/image/generate_series_sin-2-0-1.png
vendored
Normal file
BIN
test/fixtures/image/generate_series_sin-2-0-1.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
1
test/fixtures/json/generate_series_sin-2-0-1.torque.json
vendored
Normal file
1
test/fixtures/json/generate_series_sin-2-0-1.torque.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user