Compare commits

...

53 Commits

Author SHA1 Message Date
javi
2bc79e183f fixed renderer 2015-12-03 12:40:07 +01:00
javi
75bdbbfb80 removed provider 2015-12-02 14:11:14 +01:00
javi
419403998d removed bi example 2015-12-02 14:10:20 +01:00
javi
029f08b2f9 removed everything from bi 2015-12-02 14:09:42 +01:00
javi
f1a07e7cd9 restored getValues 2015-12-02 12:56:58 +01:00
javi
1eb1344916 moved logic to torque bi 2015-12-02 10:11:48 +01:00
javi
ce6ba46893 improvements in torque rendering, bubbles were not round in some cases 2015-12-02 10:11:11 +01:00
javi
4421294a4e fixed marker-opacity 2015-12-01 20:10:58 +01:00
javi
19e0145fee merged with master 2015-11-25 20:15:51 +01:00
javi
754ab64a9a added formula thing 2015-11-25 14:42:13 +01:00
javi
a393186f9f fixed loading forever on torque visualizations 2015-11-24 14:26:46 +01:00
javi
c660bc8fe7 apply factor based on sampling 2015-11-23 15:19:46 +01:00
javi
7c2d0b353a category search 2015-11-21 09:02:59 +01:00
javi
f82283ff54 fixes and fixes 2015-11-20 11:41:25 +01:00
javi
59e533af10 histograms for category 2015-11-18 11:50:11 +01:00
javi
d95f044ffc histograms with filters 2015-11-18 11:05:04 +01:00
javi
b210ba8b22 histograms 2015-11-17 14:55:43 +01:00
javi
f399acb8cc small refactor 2015-11-17 11:14:58 +01:00
javi
c500a9c025 merged 2015-11-17 11:14:25 +01:00
javi
5299314fb4 fixed sql generation where threre is not filter 2015-11-17 11:12:28 +01:00
javi santana
3e35097496 Merge pull request #240 from CartoDB/bi_provider_bounding_box_histogram
Bi provider bounding box histogram
2015-11-17 11:11:39 +01:00
Stuart Lynn
880bf9870f adding methods to get the histogram for the visible region of the map. One using Pauls Grandfather tile idea and the other using quad tree ranges for each visible tile 2015-11-16 18:59:08 +00:00
Stuart Lynn
c9e87f4365 Adding method to get histogram by tiles 2015-11-16 18:58:16 +00:00
javi
21e28bf66b add filters to info window 2015-11-16 14:05:52 +01:00
javi
ea2fe75fbc removed debugging messages 2015-11-16 12:38:42 +01:00
javi
e93c6169bd added method to know the point count and fixed cartoons setter 2015-11-15 11:16:57 +01:00
javi
33c79dad0c fixed xy position 2015-11-15 10:06:12 +01:00
javi
b46489d8eb utility methods for interactivity 2015-11-14 19:27:53 +01:00
javi
c4fa9b3351 fixed pixel calculation to fetch data for a point 2015-11-14 19:27:36 +01:00
javi
a822c369c7 using sql_api_template 2015-11-13 18:29:20 +01:00
javi
a61976727c merged 2015-11-13 17:21:14 +01:00
javi santana
1058d7ee8f Merge pull request #239 from CartoDB/bi_provider_get_point_data
Methods to get the data for a given torque point from the server
2015-11-13 16:54:14 +01:00
Stuart Lynn
b7f38d5996 Methods to get the data for a given torque tile from the server 2015-11-13 15:12:44 +00:00
javi
55847bda56 syntax 2015-11-13 11:41:53 +01:00
javi
164643d0f1 working with categories per tile 2015-11-12 12:27:08 +01:00
javi
e65367e8a6 fixed pixel rectangle 2015-11-11 18:08:00 +01:00
Stuart Lynn
1fa44a8ae1 Merge pull request #237 from CartoDB/bi_provider_web_workers
Adding web worker support for the BI provider
2015-11-11 15:47:15 +00:00
Stuart Lynn
87b93e6f9e whitespace for readability 2015-11-11 15:46:13 +00:00
javi
dbf48ea091 ose overview from options 2015-11-11 16:21:12 +01:00
Stuart Lynn
6dd0251be8 Allow worker pool to be configured by options 2015-11-11 15:09:24 +00:00
Stuart Lynn
19e35ffe2c Limiting the worker pool to a given size 2015-11-11 15:08:17 +00:00
Stuart Lynn
7a1f206d5e Adding web worker support for the BI provider 2015-11-11 13:30:47 +00:00
javi
1ec2324b83 fixed category count 2015-11-10 15:14:39 +01:00
javi
e0606ee295 fixed number of categories 2015-11-09 10:42:38 +01:00
javi
695cab290a added filters, variable mapping and support for new pixel type 2015-11-08 11:38:59 +01:00
javi
ff4809b08c histogram calculation 2015-11-06 19:47:37 +01:00
javi
4ad0dba547 removed client side filters temporally, integrated with server side queries 2015-11-06 17:35:49 +01:00
javi
37f4367da2 merged 2015-11-06 11:03:55 +01:00
javi
53d5072c4b reverted 2015-11-06 11:03:19 +01:00
Stuart Lynn
71fc89a8d8 Examples of the filters and histogram api working 2015-11-03 18:36:10 -05:00
Stuart Lynn
fd5bc0f732 Adding the ability for leafletLayer to filter data on the client side and to calculate histograms and value arrays for the variables stored in the currently loaded tiles. 2015-11-03 18:35:56 -05:00
Stuart Lynn
130d72c872 adding a filterable json provider. 2015-11-03 18:35:01 -05:00
javi
c27b2471da added filters 2015-10-28 14:56:24 +01:00
4 changed files with 216 additions and 186 deletions

View File

@ -17,6 +17,31 @@ L.Mixin.TileLoader = {
this._removeTiles();
},
visibleTiles: function() {
if (!this._map) { return []; }
var j, i, point, tiles = [];
var bounds = this._map.getPixelBounds(),
zoom = this._map.getZoom(),
tileSize = this.options.tileSize;
var nwTilePoint = new L.Point(
Math.floor(bounds.min.x / tileSize),
Math.floor(bounds.min.y / tileSize)),
seTilePoint = new L.Point(
Math.floor(bounds.max.x / tileSize),
Math.floor(bounds.max.y / tileSize)),
tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
for (j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
for (i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
tiles.push({ x: i, y: j, z: zoom });
}
}
return tiles;
},
_updateTiles: function () {
if (!this._map) { return; }
@ -55,11 +80,12 @@ L.Mixin.TileLoader = {
},
_removeOtherTiles: function (bounds) {
var self = this;
var kArr, x, y, z, key;
var zoom = this._map.getZoom();
for (key in this._tiles) {
if (this._tiles.hasOwnProperty(key)) {
function checkTile(c, key) {
if (c.hasOwnProperty(key)) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
@ -67,22 +93,41 @@ L.Mixin.TileLoader = {
// remove tile if it's out of bounds
if (zoom !== z || x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
this._removeTile(key);
self._removeTile(key);
}
}
}
for (key in this._tiles) {
checkTile(this._tiles, key);
}
for (key in this._tilesLoading) {
checkTile(this._tilesLoading, key);
}
},
_removeTile: function (key) {
this.fire('tileRemoved', this._tiles[key]);
delete this._tiles[key];
delete this._tilesLoading[key];
if (this._tilesLoading[key]) {
--this._tilesToLoad;
delete this._tilesLoading[key];
}
},
_tileKey: function(tilePoint) {
return tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom;
},
_tileFromKey: function(key) {
var kArr = key.split(':');
return {
x: parseInt(kArr[0], 10),
y: parseInt(kArr[1], 10),
z: parseInt(kArr[2], 10)
}
},
_tileShouldBeLoaded: function (tilePoint) {
var k = this._tileKey(tilePoint);
return !(k in this._tiles) && !(k in this._tilesLoading);

View File

@ -72,8 +72,8 @@ L.TorqueLayer = L.CanvasLayer.extend({
if (this.options.tileJSON) this.options.provider = 'tileJSON';
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options);
this.provider = new this.providers[this.options.provider](this.options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options);
options.ready = function() {
self.fire("change:bounds", {
@ -243,6 +243,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
}
}
}
this.renderer.applyFilters();
// prepare caches if the animation is not running
@ -341,6 +342,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
setCartoCSS: function(cartocss) {
if (this.provider.options.named_map) throw new Error("CartoCSS style on named maps is read-only");
if (!this.renderer) throw new Error('renderer is not valid');
this.options.cartocss = cartocss;
var shader = new carto.RendererJS().render(cartocss);
this.renderer.setShader(shader);
@ -398,26 +400,77 @@ L.TorqueLayer = L.CanvasLayer.extend({
var t, tile, pos, value = null, xx, yy;
for(t in this._tiles) {
tile = this._tiles[t];
pos = this.getTilePos(tile.coord);
xx = x - pos.x;
yy = y - pos.y;
if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) {
value = this.renderer.getValueFor(tile, step, xx, yy);
}
if (value !== null) {
return value;
if (tile) {
pos = this.getTilePos(tile.coord);
xx = x - pos.x;
yy = y - pos.y;
if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) {
value = this.renderer.getValueFor(tile, step, xx, yy);
}
if (value !== null) {
return value;
}
}
}
return null;
},
/**
* return tile pos given screen x,y coordinates
*/
getTilePosForPixel: function(x, y) {
var t, tile, pos, xx, yy;
for(t in this._tiles) {
tile = this._tiles[t];
if (tile) {
pos = this.getTilePos(tile.coord);
xx = x - pos.x;
yy = y - pos.y;
if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) {
return {
x: xx,
y: yy,
tile: tile.coord
}
}
}
}
return null;
},
/**
* return a list of the closest values for a given coord.
* returned format is [{ x: .., y: .., value: ...}, .. ]
*/
getClosestValuesFor: function(x, y, dist, step) {
var xf = x + dist,
yf = y + dist,
_x = x;
var values = []
for(_y = y; _y < yf; _y += this.options.resolution){
for(_x = x; _x < xf; _x += this.options.resolution){
var thisValue = this.getValueForPos(_x,_y);
if (thisValue !== null) {
var bb = thisValue.bbox;
var xy = this._map.latLngToContainerPoint([bb[0].lat, bb[0].lon]);
values.push({
x: xy.x,
y: xy.y,
value: thisValue.value
})
}
}
}
return values;
},
getValueForBBox: function(x, y, w, h) {
var xf = x + w, yf = y + h, _x=x;
var sum = 0;
for(_y = y; _y<yf; _y+=this.options.resolution){
for(_x = x; _x<xf; _x+=this.options.resolution){
for(_y = y; _y < yf; _y += this.options.resolution){
for(_x = x; _x < xf; _x += this.options.resolution){
var thisValue = this.getValueForPos(_x,_y);
if (thisValue){
if (thisValue) {
var bb = thisValue.bbox;
var xy = this._map.latLngToContainerPoint([bb[1].lat, bb[1].lon]);
if(xy.x < xf && xy.y < yf){
@ -429,6 +482,20 @@ L.TorqueLayer = L.CanvasLayer.extend({
return sum;
},
/** return the number of points for a step */
pointCount: function(step) {
var t, tile;
step = step === undefined ? this.key: step;
var c = 0;
for(t in this._tiles) {
tile = this._tiles[t];
if (tile) {
c += tile.timeCount[step];
}
}
return c;
},
invalidate: function() {
this.provider.reload();
},

View File

@ -31,13 +31,15 @@ var Filters = require('./torque_filters');
"dst-atop": 'destination-atop',
"xor": 'xor',
"darken": 'darken',
"lighten": 'lighten'
"lighten": 'lighten',
"screen": "screen"
}
function compop2canvas(compop) {
return COMP_OP_TO_CANVAS[compop] || compop;
}
//
// this renderer just render points depending of the value
//
@ -57,7 +59,7 @@ var Filters = require('./torque_filters');
this.TILE_SIZE = 256;
this._style = null;
this._gradients = {};
this._forcePoints = false;
}
@ -110,11 +112,19 @@ var Filters = require('./torque_filters');
// generate sprite based on cartocss style
//
generateSprite: function(shader, value, shaderVars) {
var self = this;
var prof = Profiler.metric('torque.renderer.point.generateSprite').start();
var st = shader.getStyle({
value: value
}, shaderVars);
var ret = this.generateSpriteForStyle(st);
prof.end(true);
return ret;
},
generateSpriteForStyle: function(st) {
var self = this;
if(this._style === null || this._style !== st){
this._style = st;
}
@ -148,9 +158,14 @@ var Filters = require('./torque_filters');
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 canvasSize = (st['marker-line-width'] || 0) + pointSize*2 + 2;
canvasSize = Math.ceil(canvasSize);
// the sprite should be placed in the center of a pixel not in the middle so
// make the canvas size odd
canvasSize += canvasSize % 2 === 0 ? 1 : 0;
var w = ctx.width = canvas.width = ctx.height = canvas.height = canvasSize;
w = Math.floor(w/2) + 1;
ctx.translate(w, w);
var mt = st['marker-type'];
if (mt && mt === 'rectangle') {
@ -159,13 +174,12 @@ var Filters = require('./torque_filters');
cartocss.renderPoint(ctx, st);
}
}
prof.end(true);
if (torque.flags.sprites_to_images) {
var i = this._createImage();
i.src = canvas.toDataURL();
return i;
}
return canvas;
},
@ -193,7 +207,7 @@ var Filters = require('./torque_filters');
}
}
}
prof.end(true);
return callback && callback(null);
@ -237,12 +251,11 @@ 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
//
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
if (!this._canvas) return;
var prof = Profiler.metric('torque.renderer.point.renderTile').start();
var ctx = this._ctx;
var blendMode = compop2canvas(shader.eval('comp-op')) || this.options.blendmode;
@ -259,22 +272,20 @@ var Filters = require('./torque_filters');
if (activePixels) {
var pixelIndex = tile.timeIndex[key];
for(var p = 0; p < activePixels; ++p) {
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
if (c) {
var sp = sprites[c];
if (sp === undefined) {
sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars));
}
if (sp) {
var x = tile.x[posIdx]- (sp.width >> 1) + anchor;
var y = tileMax - tile.y[posIdx] + anchor; // flip mercator
ctx.drawImage(sp, x, y - (sp.height >> 1));
}
}
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
var sp = sprites[c];
if (sp === undefined) {
sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars));
}
if (sp) {
var x = tile.x[posIdx]- (sp.width >> 1) + anchor;
var y = tileMax - tile.y[posIdx] + anchor; // flip mercator
ctx.drawImage(sp, x, y - (sp.height >> 1));
}
}
}
prof.end(true);
},
@ -442,7 +453,7 @@ var Filters = require('./torque_filters');
}
gradient = {};
var colorize = this._style['image-filters'].args;
var increment = 1/colorize.length;
for (var i = 0; i < colorize.length; i++){
var key = increment * i + increment;
@ -459,6 +470,8 @@ var Filters = require('./torque_filters');
}
});
PointRenderer.COMP_OP_TO_CANVAS = COMP_OP_TO_CANVAS;
// exports public api
module.exports = PointRenderer;

View File

@ -1,160 +1,65 @@
var torque = require('../');
var cartocss = require('./cartocss_render');
var Profiler = require('../profiler');
var carto = global.carto || require('carto');
var Filters = require('./torque_filters');
var PointRenderer = require('./point')
var DEFAULT_CARTOCSS = [
'#layer {',
' polygon-fill: #FFFF00;',
' [value > 10] { polygon-fill: #FFFF00; }',
' [value > 100] { polygon-fill: #FFCC00; }',
' [value > 1000] { polygon-fill: #FE9929; }',
' [value > 10000] { polygon-fill: #FF6600; }',
' [value > 100000] { polygon-fill: #FF3300; }',
'}'
].join('\n');
var PixelRenderer = function(canvas, options) {
PointRenderer.call(this, canvas, options);
}
var TAU = Math.PI * 2;
torque.extend(PixelRenderer.prototype, PointRenderer.prototype, {
//
// this renderer just render points depending of the value
//
function RectanbleRenderer(canvas, options) {
this.options = options;
carto.tree.Reference.set(torque['torque-reference']);
this.setCanvas(canvas);
this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS);
}
RectanbleRenderer.prototype = {
//
// sets the cartocss style to render stuff
//
setCartoCSS: function(cartocss) {
this._cartoCssStyle = new carto.RendererJS().render(cartocss);
if(this._cartoCssStyle.getLayers().length > 1) {
throw new Error("only one CartoCSS layer is supported");
}
this._shader = this._cartoCssStyle.getLayers()[0].shader;
},
setCanvas: function(canvas) {
if(!canvas) return;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
},
accumulate: function(tile, keys) {
var prof = Profiler.metric('RectangleRender:accumulate').start();
var x, y, posIdx, p, k, key, activePixels, pixelIndex;
var res = this.options.resolution;
var s = 256/res;
var accum = new Float32Array(s*s);
if(typeof(keys) !== 'object') {
keys = [keys];
generateSprite: function(shader, value, shaderVars) {
var self = this;
var prof = Profiler.metric('torque.renderer.point.generateSprite').start();
var st = shader.getStyle({
value: value
}, shaderVars);
if(this._style === null || this._style !== st){
this._style = st;
}
for(k = 0; k < keys.length; ++k) {
key = keys[k];
activePixels = tile.timeCount[key];
if(activePixels) {
pixelIndex = tile.timeIndex[key];
for(p = 0; p < activePixels; ++p) {
posIdx = tile.renderDataPos[pixelIndex + p];
x = tile.x[posIdx]/res;
y = tile.y[posIdx]/res;
accum[x*s + y] += tile.renderData[pixelIndex + p];
}
}
return {
width: st['marker-width'],
color: st['marker-fill'],
fill_opacity: st['marker-fill-opacity'] === undefined ? 1.0: st['marker-fill-opacity']
}
prof.end();
return accum;
},
renderTileAccum: function(accum, px, py) {
var prof = Profiler.metric('RectangleRender:renderTileAccum').start();
var color, x, y, alpha;
var res = this.options.resolution;
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
if (!this._canvas) return;
var prof = Profiler.metric('torque.renderer.point.renderTile').start();
var ctx = this._ctx;
var s = (256/res) | 0;
var s2 = s*s;
var colors = this._colors;
if(this.options.blendmode) {
ctx.globalCompositeOperation = this.options.blendmode;
if (this.options.cumulative && key > tile.maxDate) {
//TODO: precache because this tile is not going to change
key = tile.maxDate;
}
var polygon_alpha = this._shader['polygon-opacity'] || function() { return 1.0; };
for(var i = 0; i < s2; ++i) {
var xy = i;
var value = accum[i];
if(value) {
x = (xy/s) | 0;
y = xy % s;
// by-pass the style generation for improving performance
color = this._shader['polygon-fill']({ value: value }, { zoom: 0 });
ctx.fillStyle = color;
//TODO: each function should have a default value for each
//property defined in the cartocss
alpha = polygon_alpha({ value: value }, { zoom: 0 });
if(alpha === null) {
alpha = 1.0;
}
ctx.globalAlpha = alpha;
ctx.fillRect(x * res, 256 - res - y * res, res, res);
}
}
prof.end();
},
//
// renders a tile in the canvas for key defined in
// the torque tile
//
renderTile: function(tile, key, callback) {
if(!this._canvas) return;
var res = this.options.resolution;
//var prof = Profiler.get('render').start();
var ctx = this._ctx;
var colors = this._colors;
var activepixels = tile.timeCount[key];
if(activepixels) {
var w = this._canvas.width;
var h = this._canvas.height;
//var imageData = ctx.getImageData(0, 0, w, h);
//var pixels = imageData.data;
var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1)
var activePixels = tile.x.length;
var anchor = this.options.resolution/2;
if (activePixels) {
var pixelIndex = tile.timeIndex[key];
for(var p = 0; p < activePixels; ++p) {
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
if(c) {
var color = colors[Math.min(c, colors.length - 1)];
var x = tile.x[posIdx];// + px;
var y = tile.y[posIdx]; //+ py;
ctx.fillStyle = color;
ctx.fillRect(x, y, res, res);
/*
for(var xx = 0; xx < res; ++xx) {
for(var yy = 0; yy < res; ++yy) {
var idx = 4*((x+xx) + w*(y + yy));
pixels[idx + 0] = color[0];
pixels[idx + 1] = color[1];
pixels[idx + 2] = color[2];
pixels[idx + 3] = color[3];
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
var sp = sprites[c];
if (sp === undefined) {
sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars));
}
if (sp) {
var x = tile.x[posIdx]- (sp.width >> 1) + anchor;
var y = tileMax - tile.y[posIdx] + anchor; // flip mercator
if (sp.fill_opacity > 0) {
ctx.globalAlpha = sp.fill_opacity
ctx.fillStyle = sp.color;
ctx.fillRect(x, y, sp.width, sp.width);
}
}
}
*/
}
}
//ctx.putImageData(imageData, 0, 0);
}
//prof.end();
return callback && callback(null);
}
};
});
// exports public api
module.exports = RectanbleRenderer;
module.exports = PixelRenderer;