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.

This commit is contained in:
Stuart Lynn 2015-11-03 18:35:56 -05:00
parent 130d72c872
commit fd5bc0f732
2 changed files with 181 additions and 23 deletions

View File

@ -10,6 +10,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
providers: {
'sql_api': torque.providers.json,
'filterable_sql_api': torque.providers.filterableJson,
'url_template': torque.providers.JsonArray,
'windshaft': torque.providers.windshaft,
'tileJSON': torque.providers.tileJSON
@ -28,6 +29,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
options.tileLoader = true;
this.key = 0;
this.prevRenderedKey = 0;
this._filters = {}
if (options.cartocss) {
torque.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss));
}
@ -98,11 +100,42 @@ L.TorqueLayer = L.CanvasLayer.extend({
self.redraw();
}
self.fire('tileLoaded');
self.fire('dataUpdate')
});
}, this);
},
setFilters:function(){
this.provider.setFilters(this._filters);
this._reloadTiles();
return this;
},
filterByRange:function(variableName,start,end){
this._filters[variableName] = {type: 'range', range: {start:start,end:end}}
this._filtersChanged()
this.fire('dataUpdate')
return this
},
filterByCat:function(variableName,categories){
this._filters[variableName] = {type: 'cat', categories: categories}
this._filtersChanged()
return this
},
clearFilter: function(name){
if(name){
delete this._filters[name]
}
else{
this._filters = {}
}
this._filtersChanged()
return this
},
_filtersChanged:function(){
this._clearTileCaches()
this._render()
},
_clearTileCaches: function() {
var t, tile;
for(t in this._tiles) {
@ -112,12 +145,101 @@ L.TorqueLayer = L.CanvasLayer.extend({
}
}
},
_clearCaches: function() {
this.renderer && this.renderer.clearSpriteCache();
this._clearTileCaches();
},
valuesForRangeVariable:function(variable){
var t, tile;
var variable_id = this.provider.idForRange(variable)
var values = [ ]
for(t in this._tiles){
tile = this._tiles[t]
var noPoints = tile.x.length;
for(var i=0; i < tile.x.length; i++){
if(tile.renderFlags[i] ){
value = tile.renderData[variable_id*noPoints + i]
values.push(value)
}
}
}
return values;
},
valuesForCatVariable:function(variable){
var t, tile;
var categories = this.provider.idsForCategory(variable)
var result = [ ]
for(t in this._tiles){
tile = this._tiles[t]
var noPoints = tile.x.length;
for(var i=0; i < tile.x.length; i++){
if(tile.renderFlags[i] ){
var vals={}
Object.keys(categories).forEach(function(categoryName){
var variable_id = categories[categoryName]
value = tile.renderData[variable_id*noPoints + i]
vals[categoryName] = value
}.bind(this))
result.push(vals)
}
}
}
return result;
},
getValues:function(variable,callback){
var type= this.provider._mapping[variable].type
if(type=='float'){
callback(this.valuesForRangeVariable(variable))
}
else{
callback(this.valuesForCatVariable(variable))
}
},
getHistogram:function(variable,callback,noBins){
var type= this.provider._mapping[variable].type
if(type=='float'){
callback(this.histogramForRangeVariable(variable,noBins))
}
else if(type='cat'){
callback(this.histogramForCatVariable(variable))
}
return this
},
histogramForCatVariable:function(variable){
var result = {}
this.valuesForCatVariable(variable).forEach(function(point){
Object.keys(point).forEach(function(key){
result[key] = result[key] || 0
result[key] += point[key]
})
})
return result
},
histogramForRangeVariable:function(variable,noBins){
noBins = noBins || 10
var vals = this.valuesForRangeVariable(variable)
var min = Math.min.apply(null, vals)
var max = Math.max.apply(null, vals)
var binSize = (max-min)/noBins
var result = []
vals.forEach(function(val){
var bin = (val -min)/binSize
result[bin]= result[bin] || 0
result[bin] += val
})
return result
},
onAdd: function (map) {
map.on({
'zoomend': this._clearCaches,
@ -208,6 +330,38 @@ L.TorqueLayer = L.CanvasLayer.extend({
canvas.width = canvas.width;
},
_filterTile:function(tile){
var noPoints = tile.x.length
var renderFlags = []
for(var i =0; i < noPoints; i++){
var includePoint = true
Object.keys(this._filters).forEach(function(key){
var filter = this._filters[key]
var variableId = this.provider.idForRange(key)
var value = tile.renderData[variableId*noPoints+i]
if(filter.type=='range'){
if(value < filter.range.start || value > filter.range.end){
includePoint = false;
}
}
else if (filter.type=='cat'){
var ids = this.provider.idsForCategory(key);
filter.categories.forEach(function(key){
var catId = ids[key]
var value = tile.renderData[catId*noPoints + i]
if(value==0){
includePoint= false
}
}.bind(this))
}
}.bind(this))
renderFlags[i] = includePoint
}
return renderFlags
},
/**
* render the selectef key
* don't call this function directly, it's called by
@ -236,10 +390,13 @@ L.TorqueLayer = L.CanvasLayer.extend({
// all the points
this.renderer._ctx.drawImage(tile._tileCache, 0, 0);
} else {
tile.renderFlags=this._filterTile(tile)
this.renderer.renderTile(tile, this.key);
}
}
}
this.renderer.applyFilters();
// prepare caches if the animation is not running

View File

@ -57,7 +57,7 @@ var Filters = require('./torque_filters');
this.TILE_SIZE = 256;
this._style = null;
this._gradients = {};
this._forcePoints = false;
}
@ -165,14 +165,14 @@ var Filters = require('./torque_filters');
i.src = canvas.toDataURL();
return i;
}
return canvas;
},
//
// renders all the layers (and frames for each layer) from cartocss
//
renderTile: function(tile, key, callback) {
renderTile: function(tile, key, renderFlags, callback) {
if (this._iconsToLoad > 0) {
this.on('allIconsLoaded', function() {
this.renderTile.apply(this, [tile, key, callback]);
@ -193,7 +193,7 @@ var Filters = require('./torque_filters');
}
}
}
prof.end(true);
return callback && callback(null);
@ -237,12 +237,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;
@ -254,27 +253,29 @@ var Filters = require('./torque_filters');
key = tile.maxDate;
}
var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1)
var activePixels = tile.timeCount[key];
var activePixels = tile.x.length;
var anchor = this.options.resolution/2;
if (activePixels) {
var pixelIndex = tile.timeIndex[key];
var pixelIndex = 0;//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));
}
if(tile.renderFlags[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));
}
}
}
}
}
prof.end(true);
},
@ -442,7 +443,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;