Compare commits

...

51 Commits

Author SHA1 Message Date
Francisco Dans
1895d0551d codes more of the worker for colorize 2015-05-04 18:14:09 +02:00
Francisco Dans
50fa5c356e adds some workers work 2015-05-04 18:13:29 +02:00
Francisco Dans
5d143d4270 various optimisations for contour 2015-05-04 18:13:09 +02:00
Francisco Dans
5e2bf59c4e commas 2015-05-04 10:45:19 +02:00
Francisco Dans
e6e6664e28 improves example a bit 2015-04-30 10:15:30 +02:00
Francisco Dans
b7f1253c95 adds webworkify dependency 2015-04-30 10:15:04 +02:00
Francisco Dans
4e6744abe9 starts webworker support for colorizing 2015-04-30 10:13:15 +02:00
Francisco Dans
6d16120336 adds colorize worker 2015-04-30 10:12:25 +02:00
Francisco Dans
ee332d0762 doesn't colorize is alpha is 0 or less 2015-04-29 16:24:30 +02:00
Francisco Dans
bd479fc507 adds heatmap functionality to renderer 2015-04-29 16:20:34 +02:00
Francisco Dans
f3e9e2e3f2 puts ball precaching in different method 2015-04-29 16:19:53 +02:00
Francisco Dans
ac5b2aaf7b adjusts radius 2015-04-29 16:19:22 +02:00
Francisco Dans
52dbe5fd33 changes a couple of things in examples 2015-04-28 12:41:06 +02:00
Francisco Dans
edd49ec100 uses clamped array for values 2015-04-28 12:30:15 +02:00
Francisco Dans
4b6c680ae9 adds clamped type 2015-04-28 12:28:23 +02:00
Francisco Dans
f283167c41 adds available workers property 2015-04-28 12:27:05 +02:00
Francisco Dans
32d2334c18 adds web worker for ball renderer 2015-04-28 12:26:33 +02:00
Francisco Dans
944b5c500d adds color to isopleth 2015-04-27 12:57:54 +02:00
Francisco Dans
285260b8b0 adds colour to contours 2015-04-27 12:52:51 +02:00
Francisco Dans
62b9de79aa adds contour degradation 2015-04-27 12:52:26 +02:00
Francisco Dans
884144a442 Merge branch 'master' into aequus 2015-04-27 12:25:50 +02:00
Francisco Dans
c524df2f01 comments out cumulative in example 2015-02-27 10:32:08 +01:00
Francisco Dans
6ded5dafc9 makes isopleth work again in spito of lack of movement 2015-02-27 10:31:23 +01:00
Francisco Dans
aa79eeda20 adds getballindices 2015-02-26 18:19:48 +01:00
Francisco Dans
dc7516e9e6 modifies navy example to try renderer 2015-02-26 16:07:39 +01:00
Francisco Dans
3f9a8f4a18 full invalidation only on animation end 2015-02-26 16:06:39 +01:00
Francisco Dans
deacb1ae00 adds repeat checks in isopleth 2015-02-26 16:05:58 +01:00
Francisco Dans
83b6b3d058 invalidates layers a bit smarter 2015-02-26 16:05:25 +01:00
Francisco Dans
a8a8d79d86 caches balls 2015-02-26 16:03:20 +01:00
Francisco Dans
204df7829d makes sure that animation is paused when steps is one 2015-02-26 16:02:06 +01:00
Francisco Dans
98a37771ca gets rid of unnecessary stuff in isopleth 2015-02-25 18:10:31 +01:00
Francisco Dans
6a0fe4beb7 removes map_range 2015-02-25 18:08:33 +01:00
Francisco Dans
68ff448f78 invalidates pointarray when not cumulative 2015-02-25 18:02:41 +01:00
Francisco Dans
171a5b6e0c optimizes horizontalLine 2015-02-25 18:01:38 +01:00
Francisco Dans
7f72ae01af conveys draw calls in flush method 2015-02-25 12:47:55 +01:00
Francisco Dans
8e8ae30dc8 adds isInvalid to check for layers with wrong values 2015-02-25 12:47:12 +01:00
Francisco Dans
3fe1c60d61 simplifies invalidate 2015-02-25 12:46:45 +01:00
Francisco Dans
51fc104793 changes name to pointLayer 2015-02-25 12:46:26 +01:00
Francisco Dans
d048652ba9 optimizes drawnTemp a bit more 2015-02-25 12:45:45 +01:00
Francisco Dans
9479b214fa initialises layers JIT, instead of in the constructor 2015-02-25 12:42:21 +01:00
Francisco Dans
43e6cb7a37 invalidates the canvas properly 2015-02-25 12:40:43 +01:00
Francisco Dans
4f41136d24 calls draw at the end of each tile render instead of each canvas 2015-02-24 12:12:04 +01:00
Francisco Dans
a36fd0936a comments out comp ops 2015-02-24 12:11:37 +01:00
Francisco Dans
bc37a754a8 constructs the ballRenderer JIT 2015-02-24 12:11:04 +01:00
Francisco Dans
7ad4f21cc7 removes normalisation to 255 since native already does that 2015-02-24 12:10:21 +01:00
Francisco Dans
ea7f4efb23 checks for repeated lines using a simple array, optimizing the hell of horizontalLine 2015-02-24 12:09:46 +01:00
Francisco Dans
169de36bcf uses only canvas object, renames it to avoid future problems 2015-02-24 12:08:11 +01:00
Francisco Dans
4b240a4485 passes position on renderTile 2015-02-24 12:07:11 +01:00
Francisco Dans
d7d7396ba8 properly invalidates ballrenderer with each tile 2015-02-24 12:06:47 +01:00
Francisco Dans
cd396b19c3 prepares point renderer for ball renderer 2015-02-23 15:16:57 +01:00
Francisco Dans
82c5ef2b96 adds ball renderer 2015-02-23 15:15:43 +01:00
10 changed files with 499 additions and 191 deletions

View File

@ -9,20 +9,10 @@ JS_CLIENT_FILES= lib/torque/*.js \
lib/torque/leaflet/canvas_layer.js \ lib/torque/leaflet/canvas_layer.js \
lib/torque/leaflet/torque.js lib/torque/leaflet/torque.js
all: dist/torque.js dist/torque.full.js
dist/torque.full.uncompressed.js: dist_folder dist/torque.uncompressed.js dist/torque.full.uncompressed.js: dist_folder dist/torque.uncompressed.js
$(BROWSERIFY) lib/torque/index.js --standalone torque > dist/torque.full.uncompressed.js $(BROWSERIFY) lib/torque/index.js --standalone torque > dist/torque.full.uncompressed.js
dist/torque.full.js: dist_folder dist/torque.full.uncompressed.js
$(UGLIFYJS) dist/torque.full.uncompressed.js > dist/torque.full.js
dist/torque.uncompressed.js: dist_folder $(JS_CLIENT_FILES)
$(BROWSERIFY) lib/torque/index.js --no-bundle-external --standalone torque > dist/torque.uncompressed.js
dist/torque.js: dist_folder dist/torque.uncompressed.js
$(UGLIFYJS) dist/torque.uncompressed.js > dist/torque.js
dist_folder: dist_folder:
mkdir -p dist mkdir -p dist

View File

@ -16,11 +16,12 @@
// define the torque layer style using cartocss // define the torque layer style using cartocss
var CARTOCSS = [ var CARTOCSS = [
'Map {', 'Map {',
'-torque-time-attribute: "date";', '-torque-time-attribute: "postedtime";',
'-torque-aggregation-function: "count(cartodb_id)";', '-torque-aggregation-function: "count(cartodb_id)";',
'-torque-frame-count: 760;', '-torque-frame-count: 1024;',
'-torque-animation-duration: 15;', '-torque-animation-duration: 30;',
'-torque-resolution: 2', '-torque-resolution: 2',
//'-torque-data-aggregation:cumulative;',
'}', '}',
'#layer {', '#layer {',
' marker-width: 3;', ' marker-width: 3;',
@ -33,16 +34,14 @@
' [value > 5] { marker-fill: #CC4C02; }', ' [value > 5] { marker-fill: #CC4C02; }',
' [value > 6] { marker-fill: #993404; }', ' [value > 6] { marker-fill: #993404; }',
' [value > 7] { marker-fill: #662506; }', ' [value > 7] { marker-fill: #662506; }',
' [frame-offset = 1] { marker-width: 10; marker-fill-opacity: 0.05;}',
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}',
'}' '}'
].join('\n'); ].join('\n');
var map = new L.Map('map', { var map = new L.Map('map', {
zoomControl: true, zoomControl: true,
center: [40, 0], center: [54.1109429427243, -2.724609375],
zoom: 3 zoom: 6
}); });
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', { L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
@ -50,8 +49,8 @@
}).addTo(map); }).addTo(map);
var torqueLayer = new L.TorqueLayer({ var torqueLayer = new L.TorqueLayer({
user : 'viz2', user : 'fdansv',
table : 'ow', table : 'twitter_jeremyclarkson_clarksonsacked_',
cartocss: CARTOCSS cartocss: CARTOCSS
}); });
torqueLayer.error(function(err){ torqueLayer.error(function(err){
@ -60,7 +59,7 @@
} }
}); });
torqueLayer.addTo(map); torqueLayer.addTo(map);
torqueLayer.play() torqueLayer.play();
</script> </script>
</body> </body>
</html> </html>

View File

@ -61,6 +61,7 @@
// types // types
var types = { var types = {
Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array, Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array,
Uint8ClampedArray: typeof(global['Uint8ClampedArray']) !== 'undefined' ? global.Uint8ClampedArray: Array,
Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array, Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array,
Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array, Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array,
Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array

View File

@ -60,6 +60,8 @@ L.TorqueLayer = L.CanvasLayer.extend({
this.setDuration = this.animator.duration.bind(this.animator); this.setDuration = this.animator.duration.bind(this.animator);
this.isRunning = this.animator.isRunning.bind(this.animator); this.isRunning = this.animator.isRunning.bind(this.animator);
this.lastStep = 0;
L.CanvasLayer.prototype.initialize.call(this, options); L.CanvasLayer.prototype.initialize.call(this, options);
@ -212,7 +214,9 @@ L.TorqueLayer = L.CanvasLayer.extend({
var canvas = this.getCanvas(); var canvas = this.getCanvas();
this.renderer.clearCanvas(); this.renderer.clearCanvas();
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
var brrr = false;
for(t in this._tiles) { for(t in this._tiles) {
tile = this._tiles[t]; tile = this._tiles[t];
if (tile) { if (tile) {
@ -228,12 +232,17 @@ L.TorqueLayer = L.CanvasLayer.extend({
// when the tile has a cached image just render it and avoid to render // when the tile has a cached image just render it and avoid to render
// all the points // all the points
this.renderer._ctx.drawImage(tile._tileCache, 0, 0); this.renderer._ctx.drawImage(tile._tileCache, 0, 0);
brrr = true;
} else { } else {
this.renderer.renderTile(tile, this.key); this.renderer.renderTile(tile, this.key, pos);
brrr = false;
} }
} }
} }
this.renderer.applyFilters(); var full = this.getStep() < this.lastStep;
this.lastStep = this.getStep();
if(!brrr) this.renderer.flush(full);
//this.renderer.applyFilters();
// 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

View File

@ -4,6 +4,7 @@
var Uint8Array = torque.types.Uint8Array; var Uint8Array = torque.types.Uint8Array;
var Int32Array = torque.types.Int32Array; var Int32Array = torque.types.Int32Array;
var Uint32Array = torque.types.Uint32Array; var Uint32Array = torque.types.Uint32Array;
var Uint8ClampedArray = torque.types.Uint8ClampedArray;
// format('hello, {0}', 'rambo') -> "hello, rambo" // format('hello, {0}', 'rambo') -> "hello, rambo"
function format(str) { function format(str) {
@ -82,7 +83,7 @@
dates = (1 + maxDateSlots) * rows.length; dates = (1 + maxDateSlots) * rows.length;
} }
var type = this.options.cumulative ? Uint32Array: Uint8Array; var type = this.options.cumulative ? Uint32Array: Uint8ClampedArray;
// reserve memory for all the dates // reserve memory for all the dates
var timeIndex = new Int32Array(maxDateSlots + 1); //index-size var timeIndex = new Int32Array(maxDateSlots + 1); //index-size

321
lib/torque/renderer/ball.js Normal file
View File

@ -0,0 +1,321 @@
////////////////////////////////
// Torque BallRenderer
// CartoDB, 2015
// developed by Francisco Dans
////////////////////////////////
var work = require('webworkify');
function BallRenderer(thecanvas){
this.canvas = thecanvas;
this.ctx = this.canvas.getContext("2d");
this.width = this.canvas.width;
this.height = this.canvas.height;
this.size = this.width * this.height;
this.pointLayer = new Uint8ClampedArray(this.size * 4);
this.radius = 15;
this.drawnTemp = {};
this.prof = 0;
this.gradient = {};
this.ballsSoFar = 0;
this.balls = 0;
this.cumulative = true;
this.RW4 = this.radius * this.width *4;
this.availableWorkers = 10;
}
BallRenderer.prototype = {
getBallIndices: function(x, y){
var indices = new Int8Array(2*r+1);
return indices;
},
addBall: function(x0, y0){
if(this.cachedBall){
var startingPoint = this.getRIndexPos(x0, y0) - this.RW4 - this.radius*4;
var i = 0,
pointer = startingPoint,
ballWidth = (this.radius*2)*4
linemax = startingPoint + 2 * this.radius*4,
endPoint = this.getRIndexPos(x0, y0) + this.RW4 + this.radius;
while (pointer <= endPoint){
while (pointer <= linemax){
this.pointLayer[pointer+3] += this.cachedBall[i+3];
i+=4;
pointer+=4;
}
linemax += this.width * 4;
pointer += this.width * 4 - ballWidth -4;
}
}
else{
this.precacheBall(x0,y0);
}
},
precacheBall: function(x0, y0){
this.cachedBall = new Uint8ClampedArray(Math.pow(2 * this.radius + 1, 2)*4);
x0 = this.radius;
y0 = this.radius;
var orad = this.radius;
var x = this.radius;
var y = 0;
var radiusError = 1 - x;
while (x >= y){
// Try not to touch the following, it's a pain in the ass to write
this.horizontalLine(-x + x0, x + x0, y + y0, x0, y0);
this.horizontalLine(-y + x0, y + x0, -x + y0, x0, y0);
this.horizontalLine(-x + x0, x + x0, -y + y0, x0, y0);
this.horizontalLine(-y + x0, y + x0, x + y0, x0, y0);
++y;
if (radiusError<0){
radiusError += 2 * y + 1;
}
else{
--x;
radiusError += 2 * (y - x) + 1;
}
}
this.drawnTemp = {};
},
horizontalLine: function(xi, xf, yi, x0, y0){
// Assumes xi is on the left and xf is on the right
if(typeof this.drawnTemp[yi] === "undefined"){
while (xi <= xf){
this.addPoint(xi, yi, 20 - ((20 * this.lineDistance(xi, yi,x0,y0)) / this.radius));
++xi;
}
this.drawnTemp[yi]=true;
}
},
addPoint: function(x, y, alpha){
var indexPos = (y*(this.radius*2+1)+x)*4;
this.cachedBall[indexPos + 3] += alpha;
},
map_range: function(value, low1, high1, low2, high2) {
return low2 + (high2 - low2) * (value - low1) / (high1 - low1);
},
lineDistance: function(x,y,x0,y0){
var xs = Math.pow(x - x0, 2);
var ys = Math.pow(y - y0, 2);
return Math.sqrt( xs + ys );
},
draw: function(dataArray){
if (!dataArray){
if (this.isoplethLayer) dataArray = this.isoplethLayer;
else if (this.contourLayer) dataArray = this.contourLayer;
else if (this.heatmapLayer) dataArray = this.heatmapLayer;
else dataArray = this.pointLayer;
}
if(!this.imageData) this.imageData = this.ctx.createImageData(this.width, this.height);
this.imageData.data.set(dataArray);
this.ctx.putImageData(this.imageData, 0, 0);
},
mergeLayers: function(from, to){
if (from.length !== to.length) throw("layers aren't of the same size"); return;
for (var i = 0; i<to.length; i++){
if(from[i+3]>0){
// There's a better way of doing this but I was lazy.
to[i] = from[i];
to[i+1] = from[i+1];
to[i+2] = from[i+2];
to[i+3] = from[i+3];
}
}
},
reduceArray: function(){
},
expandArray: function(){
},
invalidate: function(full){
// if(!transit){
for (var i = 0, len = this.pointLayer.length; i< len; i+=4){
this.pointLayer[i + 3] -= 10;
}
// }
// else{
// this.pointLayer = new Uint8ClampedArray(this.size * 4);
// }
if(this.heatmapLayer){
this.heatmapLayer = new Uint8ClampedArray(this.size * 4);
}
else if(this.contourLayer){
this.contourLayer = new Uint8ClampedArray(this.size * 4);
if(this.isoplethLayer){
this.isoplethLayer = new Uint8ClampedArray(this.size * 4);
}
}
},
getRIndexPos: function(x,y){
var rIndexPos = (y*this.width+x)*4;
return rIndexPos;
},
getXYFromRIndex: function(index){
var x = (index % (this.width*4))/4;
var y = (index - 4 * x) / (4 * this.width);
return [x,y];
},
// Clockwise. Again, there definitely is a better way.
getNeighbors: function(index){
var tw = this.width*4;
var n = index - tw;
var s = index + tw;
return [n, n + 4, index + 4, s + 4, s, s - 4, index - 4, n -4];
},
isEmpty: function(layer) {
for (var i = 0; i<layer.length; i+=4){
if(layer[i+3] > 0) return false;
}
return true;
},
isInvalid: function(layer) {
for (var i = 0; i<layer.length; i+=4){
if(layer[i+3]!==0 && !layer[i+3]) return true;
}
return false;
},
createArray: function(){
return new Uint8ClampedArray(this.size * 4);
},
contour: function(granularity){
if(!this.contourGradient){
var step = 255/granularity;
var i = 0, a = new Uint8ClampedArray(granularity+1), c=0;
while (i<255){
a[c] = i;
i += step;
c++;
}
a[a.length-1] = 255;
var l = -step/2;
this.contourGradient = new Uint8ClampedArray(1024);
for(var i = 0; i<a.length; i++){
var y = Math.round(i*step);
var thisAlpha = a[i];
while(y<step*(i+1)){
this.contourGradient[y*4+3] = thisAlpha;
y++;
}
}
}
var gradient = this.contourGradient;
if(!this.contourLayer) this.contourLayer = new Uint8ClampedArray(this.size * 4);
for (var i = this.pointLayer.length-4, alpha; i>=0; i-=4){
if(this.pointLayer[i+3] > 0){
var currentAlpha = this.pointLayer[i+3];
this.contourLayer[i+0] = 255;
this.contourLayer[i+1] = 105;
this.contourLayer[i+2] = 180;
this.contourLayer[i+3] = gradient[currentAlpha*4+3];
}
}
},
isopleth: function(){
var iso = this.createArray();
var contour = this.contourLayer;
var eq = false;
for (var i = 0, len = contour.length; i<len; i+=4){
if(!eq){
var alpha = contour[i + 3];
if (alpha > 0 && alpha < 255){
var neighbors = this.getNeighbors(i);
var refCol = contour[neighbors[0]+3];
for (var n = 1, ln = neighbors.length; n<ln; ++n){
if (contour[neighbors[n]+3] !== refCol) break;
if (n === ln-1) eq = true;
}
if(!eq){
for (var n = 0, ln = neighbors.length; n<ln; ++n){
var index = neighbors[n];
if(index>0 && (contour[index+3] === 0 || contour[index+3] !== contour[i+3])){
iso[index + 3] = contour[i+3];
iso[index] = 153;
iso[index + 1] = 60;
iso[index + 2] = 243;
}
}
}
}
}
else{
eq = false;
}
}
this.isoplethLayer = iso;
},
heatmap: function (gradient){
if(!gradient) gradient = {
0.4: 'blue',
0.6: 'cyan',
0.7: 'lightgreen',
0.9: 'yellow',
1.0: 'red'
};
if(JSON.stringify(this.gradient) !== JSON.stringify(gradient)){
this.gradient = gradient;
// create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
gradientColours = ctx.createLinearGradient(0, 0, 0, 256);
canvas.width = 1;
canvas.height = 256;
for (var i in gradient) {
gradientColours.addColorStop(+i, gradient[i]);
}
ctx.fillStyle = gradientColours;
ctx.fillRect(0, 0, 1, 256);
this.gradientData = ctx.getImageData(0, 0, 1, 256).data;
}
this.colorize();
},
colorize: function () {
if (!this.colorWorkers){
this.initColorWorkers();
}
for(k in this.colorWorkers){
this.colorWorkers[k].postMessage([this.pointLayer,k, 3]);
}
var grad = this.gradientData;
this.heatmapLayer = new Uint8ClampedArray(this.size * 4);
// while(Object.keys(this.loadedColorizedChunks).length < this.colorWorkers.length){
// console.log(Object.keys(this.loadedColorizedChunks).length)
// }
// var i = 0;
// while(i<=this.colorWorkers.length){
// this.heatmapLayer += this.loadedColorizedChunks[i];
// }
// for (var i = this.pointLayer.length-4, alpha; i>=0; i-=4){
// alpha = this.pointLayer[i+3] * 4; // get gradient color from opacity value
// if (alpha>0) {
// this.heatmapLayer[i] = grad[alpha]; // R
// this.heatmapLayer[i + 1] = grad[alpha + 1]; // G
// this.heatmapLayer[i + 2] = grad[alpha + 2]; // B
// this.heatmapLayer[i + 3] = alpha; // A
// }
// }
},
initColorWorkers: function(){
var workerCount = 3
this.colorWorkers = [];
if(!this.loadedColorizedChunks) this.loadedColorizedChunks = {};
for (var i = 0; i < workerCount; i++) {
var worker = work(require('./colorWorker.js'));
worker.grad = this.gradientData;
worker.onmessage = function(data){
this.loadedColorizedChunks[data.data[0]] = data.data[1];
}
this.colorWorkers.push(worker);
}
}
}
module.exports = BallRenderer;

View File

View File

@ -0,0 +1,19 @@
onmessage = function(e){
var index = e.data[1];
var pointList = e.data[0];
var n = e.data[2]
var cs = Math.round(pointList.length/4/n);
var si = cs * index * 4;
var ei = (si + cs) * 4;
var subset = pointList.subarray(si,ei);
for (var i = subset.length-4, alpha; i>=0; i-=4){
alpha = subset[i+3] * 4; // get gradient color from opacity value
if (alpha>0) {
subset[i] = this.gradientData[alpha]; // R
subset[i + 1] = this.gradientData[alpha + 1]; // G
subset[i + 2] = this.gradientData[alpha + 2]; // B
subset[i + 3] = alpha; // A
}
}
postMessage([index, subset]);
}

View File

@ -2,7 +2,8 @@ var torque = require('../');
var cartocss = require('./cartocss_render'); var cartocss = require('./cartocss_render');
var Profiler = require('../profiler'); var Profiler = require('../profiler');
var carto = global.carto || require('carto'); var carto = global.carto || require('carto');
var Filters = require('./torque_filters'); var filters = require('./torque_filters');
var ballRenderer = require('./ball.js');
var TAU = Math.PI * 2; var TAU = Math.PI * 2;
var DEFAULT_CARTOCSS = [ var DEFAULT_CARTOCSS = [
@ -51,8 +52,7 @@ var Filters = require('./torque_filters');
this._sprites = []; // sprites per layer this._sprites = []; // sprites per layer
this._shader = null; this._shader = null;
this._icons = {}; this._icons = {};
this._iconsToLoad = 0; this._filters = filters(this._canvas);
this._filters = new Filters(this._canvas, {canvasClass: options.canvasClass});
this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS); this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS);
this.TILE_SIZE = 256; this.TILE_SIZE = 256;
this._style = null; this._style = null;
@ -129,29 +129,20 @@ var Filters = require('./torque_filters');
} }
var canvas = this._createCanvas(); var canvas = this._createCanvas();
// take into account the exterior ring to calculate the size
var canvasSize = (st['marker-line-width'] || 0) + pointSize*2;
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize);
ctx.translate(w/2, w/2);
var markerFile = st["marker-file"] || st["point-file"]; var img_name = this._qualifyURL(st["marker-file"] || st["point-file"]);
var qualifiedUrl = markerFile && this._qualifyURL(markerFile); if (img_name && this._icons.itemsToLoad <= 0) {
var img = this._icons[img_name];
if (qualifiedUrl && this._iconsToLoad <= 0 && this._icons[qualifiedUrl]) { img.w = st['marker-width'] || img.width;
var img = this._icons[qualifiedUrl]; img.h = st['marker-width'] || st['marker-height'];
cartocss.renderSprite(ctx, img, st);
var dWidth = st['marker-width'] * 2 || img.width; }
var dHeight = (st['marker-height'] || dWidth) * (img.width / img.height); else {
canvas.width = ctx.width = dWidth;
canvas.height = ctx.height = dHeight;
ctx.scale(dWidth/img.width, dHeight/img.height);
cartocss.renderSprite(ctx, img, st);
} else {
// take into account the exterior ring to calculate the size
var canvasSize = (st['marker-line-width'] || 0) + pointSize*2;
var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize);
ctx.translate(w/2, w/2);
var mt = st['marker-type']; var mt = st['marker-type'];
if (mt && mt === 'rectangle') { if (mt && mt === 'rectangle') {
cartocss.renderRectangle(ctx, st); cartocss.renderRectangle(ctx, st);
@ -172,31 +163,27 @@ 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, key,pos) {
if (this._iconsToLoad > 0) { if(typeof this.ballRenderer === "undefined"){
this.on('allIconsLoaded', function() { this.ballRenderer = new ballRenderer(this._canvas);
this.renderTile.apply(this, [tile, key, callback]);
});
return false;
}
var prof = Profiler.metric('torque.renderer.point.renderLayers').start();
var layers = this._shader.getLayers();
for(var i = 0, n = layers.length; i < n; ++i ) {
var layer = layers[i];
if (layer.name() !== "Map") {
var sprites = this._sprites[i] || (this._sprites[i] = {});
// frames for each layer
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);
}
}
} }
this._renderTile(tile, key, 0, null, null, null, pos);
// var prof = Profiler.metric('torque.renderer.point.renderLayers').start();
// var layers = this._shader.getLayers();
// for(var i = 0, n = layers.length; i < n; ++i ) {
// var layer = layers[i];
// if (layer.name() !== "Map") {
// var sprites = this._sprites[i] || (this._sprites[i] = {});
// // frames for each layer
// 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);
// }
// }
// }
prof.end(true); // prof.end(true);
return callback && callback(null);
}, },
_createCanvas: function() { _createCanvas: function() {
@ -210,21 +197,6 @@ var Filters = require('./torque_filters');
? new this.options.imageClass() ? new this.options.imageClass()
: new Image(); : new Image();
}, },
_setImageSrc: function(img, url, callback) {
if (this.options.setImageSrc) {
this.options.setImageSrc(img, url, callback);
} else {
img.onload = function(){
callback(null);
};
img.onerror = function(){
callback(new Error('Could not load image'));
};
img.src = url;
}
},
_qualifyURL: function(url) { _qualifyURL: function(url) {
if (typeof this.options.qualifyURL !== "undefined"){ if (typeof this.options.qualifyURL !== "undefined"){
return this.options.qualifyURL(url); return this.options.qualifyURL(url);
@ -240,42 +212,43 @@ var Filters = require('./torque_filters');
// renders a tile in the canvas for key defined in // renders a tile in the canvas for key defined in
// the torque tile // the torque tile
// //
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) { _renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars, pos) {
if (!this._canvas) return; if (!this._canvas) return;
var prof = Profiler.metric('torque.renderer.point.renderTile').start(); var prof = Profiler.metric('torque.renderer.point.renderTile').start();
var ctx = this._ctx; var ctx = this._ctx;
var blendMode = compop2canvas(shader.eval('comp-op')) || this.options.blendmode; // var blendMode = compop2canvas(shader.eval('comp-op')) || this.options.blendmode;
if (blendMode) { // if (blendMode) {
ctx.globalCompositeOperation = blendMode; // ctx.globalCompositeOperation = blendMode;
} // }
if (this.options.cumulative && key > tile.maxDate) { if (this.options.cumulative && key > tile.maxDate) {
//TODO: precache because this tile is not going to change //TODO: precache because this tile is not going to change
key = tile.maxDate; key = tile.maxDate;
} }
var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1) var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1)
var activePixels = tile.timeCount[key]; var activePixels = tile.timeCount[key];
var anchor = this.options.resolution/2;
if (activePixels) { if (activePixels) {
var pixelIndex = tile.timeIndex[key]; var pixelIndex = tile.timeIndex[key];
for(var p = 0; p < activePixels; ++p) { for(var p = 0; p < activePixels; ++p) {
var posIdx = tile.renderDataPos[pixelIndex + p]; var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p]; var c = tile.renderData[pixelIndex + p];
if (c) { if (c) {
var sp = sprites[c]; var x = tile.x[posIdx];
if (sp === undefined) { var y = tileMax - tile.y[posIdx]; // flip mercator
sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars)); this.ballRenderer.addBall(pos.x + x, pos.y + y);
} // var sp = sprites[c];
if (sp) { // if (sp === undefined) {
var x = tile.x[posIdx]- (sp.width >> 1) + anchor; // sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars));
var y = tileMax - tile.y[posIdx] + anchor; // flip mercator // }
ctx.drawImage(sp, x, y - (sp.height >> 1)); // if (sp) {
} // var x = tile.x[posIdx]- (sp.width >> 1);
// var y = tileMax - tile.y[posIdx]; // flip mercator
// ctx.drawImage(sp, x, y - (sp.height >> 1));
// }
} }
} }
} }
prof.end(true); prof.end(true);
}, },
@ -347,61 +320,44 @@ var Filters = require('./torque_filters');
} }
return null; return null;
}, },
_preloadIcons: function(img_names){
_preloadIcons: function(img_names) {
var self = this; var self = this;
this._icons = {};
if (img_names.length > 0 && !this._forcePoints) { if (img_names.length > 0 && !this._forcePoints){
for (var i = 0; i<img_names.length; i++){
var qualifiedImageUrlSet = Object.keys(img_names.reduce(function(imgNamesMap, imgName) { var new_img = this._createImage();
var qualifiedUrl = self._qualifyURL(imgName); this._icons[this._qualifyURL(img_names[i])] = null;
if (!self._icons[qualifiedUrl]) { if (typeof self._icons.itemsToLoad === 'undefined'){
imgNamesMap[qualifiedUrl] = true; this._icons.itemsToLoad = img_names.length;
}
return imgNamesMap;
}, {}));
var filtered = self._shader.getLayers().some(function(layer) {
return typeof layer.shader["image-filters"] !== "undefined";
});
this._iconsToLoad += qualifiedImageUrlSet.length;
qualifiedImageUrlSet.forEach(function(qualifiedImageUrl) {
self._icons[qualifiedImageUrl] = null;
var img = self._createImage();
if (filtered) {
img.crossOrigin = 'Anonymous';
} }
var filtered = self._shader.getLayers().some(function(layer){return typeof layer.shader["image-filters"] !== "undefined"});
self._setImageSrc(img, qualifiedImageUrl, function(err) { if (filtered){
if (err) { new_img.crossOrigin = 'Anonymous';
self._forcePoints = true; }
self.clearSpriteCache(); new_img.onload = function(e){
self._iconsToLoad = 0; self._icons[this.src] = this;
self.fire("allIconsLoaded"); if (Object.keys(self._icons).length === img_names.length + 1){
if(filtered) { self._icons.itemsToLoad--;
console.info("Only CORS-enabled, or same domain image-files can be used in combination with image-filters"); if (self._icons.itemsToLoad <= 0){
}
console.error("Couldn't get marker-file " + qualifiedImageUrl);
} else {
self._icons[qualifiedImageUrl] = img;
self._iconsToLoad--;
if (self._iconsToLoad <= 0){
self.clearSpriteCache(); self.clearSpriteCache();
self.fire("allIconsLoaded"); self.fire("allIconsLoaded");
} }
} }
}); };
}); new_img.onerror = function(){
} else { self._forcePoints = true;
this.fire("allIconsLoaded"); self.clearSpriteCache();
if(filtered){
console.info("Only CORS-enabled, or same domain image-files can be used in combination with image-filters");
}
console.error("Couldn't get marker-file " + this.src);
};
this.itemsToLoad++;
new_img.src = img_names[i];
}
} }
},
},
applyFilters: function(){ applyFilters: function(){
if(this._style){ if(this._style){
if(this._style['image-filters']){ if(this._style['image-filters']){
@ -439,6 +395,15 @@ var Filters = require('./torque_filters');
this._filters.draw(); this._filters.draw();
} }
} }
},
flush: function(full){
this._ctx.setTransform(1, 0, 0, 1, 0, 0);
if(!this.ballRenderer) return;
// this.ballRenderer.contour(7);
// this.ballRenderer.isopleth();
this.ballRenderer.heatmap();
this.ballRenderer.draw();
this.ballRenderer.invalidate();
} }
}); });

View File

@ -1,46 +1,49 @@
{ {
"name": "torque.js", "name": "torque.js",
"version": "2.11.1", "version": "2.11.1",
"description": "Torque javascript library", "description": "Torque javascript library",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/CartoDB/torque.git" "url": "git://github.com/CartoDB/torque.git"
}, },
"author": { "author": {
"name": "CartoDB", "name": "CartoDB",
"url": "http://cartodb.com/", "url": "http://cartodb.com/",
"email": "wadus@cartodb.com" "email": "wadus@cartodb.com"
}, },
"contributors": [ "contributors": [
"Andrew Hill <andrew@vizzuality.com>", "Andrew Hill <andrew@vizzuality.com>",
"Simon Tokumine <tokumine@google.com>", "Simon Tokumine <tokumine@google.com>",
"Javier Alvarez <jmedina@vizzuality.com>", "Javier Alvarez <jmedina@vizzuality.com>",
"Javier Arce <javierarce@vizzuality.com>", "Javier Arce <javierarce@vizzuality.com>",
"Javier Santana <jsantana@vizzuality.com>", "Javier Santana <jsantana@vizzuality.com>",
"Raúl Ochoa <rochoa@cartodb.com>", "Raúl Ochoa <rochoa@cartodb.com>",
"Nicklas Gummesson <nicklas@cartodb.com>", "Nicklas Gummesson <nicklas@cartodb.com>",
"Francisco Dans <francisco@cartodb.com>" "Francisco Dans <francisco@cartodb.com>"
], ],
"licenses": [{ "licenses": [
{
"type": "BSD" "type": "BSD"
}], }
"dependencies": { ],
"carto": "https://github.com/CartoDB/carto/archive/master.tar.gz" "dependencies": {
}, "carto": "https://github.com/CartoDB/carto/archive/master.tar.gz",
"devDependencies": { "webworkify": "^1.0.2"
"leaflet": "0.7.3", },
"underscore": "^1.6.0", "devDependencies": {
"node-qunit-phantomjs": "^1.0.0", "leaflet": "0.7.3",
"browserify": "^7.0.0", "underscore": "^1.6.0",
"mapnik": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1", "node-qunit-phantomjs": "^1.0.0",
"canvas": "~1.2.1", "browserify": "^7.0.0",
"request": "^2.53.0", "mapnik": "https://github.com/CartoDB/node-mapnik/tarball/1.4.15-cdb1",
"qunit": "~0.7.5", "canvas": "~1.2.1",
"qunitjs": "1.x", "request": "^2.53.0",
"uglify-js": "1.3.3" "qunit": "~0.7.5",
}, "qunitjs": "1.x",
"scripts": { "uglify-js": "1.3.3"
"test": "make test-all" },
}, "scripts": {
"main": "./lib/torque/index.js" "test": "make test-all"
},
"main": "./lib/torque/index.js"
} }