adding a filterable json provider.
This commit is contained in:
parent
d6b15282b7
commit
130d72c872
663
lib/torque/provider/filterableJson.js
Normal file
663
lib/torque/provider/filterableJson.js
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
var torque = require('../');
|
||||||
|
var Profiler = require('../profiler');
|
||||||
|
|
||||||
|
var Uint8Array = torque.types.Uint8Array;
|
||||||
|
var Int32Array = torque.types.Int32Array;
|
||||||
|
var Uint32Array = torque.types.Uint32Array;
|
||||||
|
|
||||||
|
// format('hello, {0}', 'rambo') -> "hello, rambo"
|
||||||
|
function format(str) {
|
||||||
|
for(var i = 1; i < arguments.length; ++i) {
|
||||||
|
var attrs = arguments[i];
|
||||||
|
for(var attr in attrs) {
|
||||||
|
str = str.replace(RegExp('\\{' + attr + '\\}', 'g'), attrs[attr]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterableJson = function (options) {
|
||||||
|
this._ready = false;
|
||||||
|
this._tileQueue = [];
|
||||||
|
this.options = options;
|
||||||
|
this._filters = {};
|
||||||
|
this._mapping = {
|
||||||
|
no:{
|
||||||
|
type: 'float',
|
||||||
|
col_id:6
|
||||||
|
},
|
||||||
|
passenger_count:{
|
||||||
|
type: 'float',
|
||||||
|
col_id: 0
|
||||||
|
},
|
||||||
|
tip_amount:{
|
||||||
|
type:'float',
|
||||||
|
col_id:1
|
||||||
|
},
|
||||||
|
payment_type:{
|
||||||
|
type:'cat',
|
||||||
|
col_ids:{
|
||||||
|
"Credit card": 2,
|
||||||
|
"Cash": 3,
|
||||||
|
"No charge": 4,
|
||||||
|
"Dispute": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.options.is_time = this.options.is_time === undefined ? true: this.options.is_time;
|
||||||
|
this.options.tiler_protocol = options.tiler_protocol || 'http';
|
||||||
|
this.options.tiler_domain = options.tiler_domain || 'cartodb.com';
|
||||||
|
this.options.tiler_port = options.tiler_port || 80;
|
||||||
|
|
||||||
|
if (this.options.data_aggregation) {
|
||||||
|
this.options.cumulative = this.options.data_aggregation === 'cumulative';
|
||||||
|
}
|
||||||
|
|
||||||
|
// check options
|
||||||
|
if (options.resolution === undefined ) throw new Error("resolution should be provided");
|
||||||
|
if (options.steps === undefined ) throw new Error("steps should be provided");
|
||||||
|
if(options.start === undefined) {
|
||||||
|
this._fetchKeySpan();
|
||||||
|
} else {
|
||||||
|
this._setReady(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
filterableJson.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return the torque tile encoded in an efficient javascript
|
||||||
|
* structure:
|
||||||
|
* {
|
||||||
|
* x:Uint8Array x coordinates in tile reference system, normally from 0-255
|
||||||
|
* y:Uint8Array y coordinates in tile reference system
|
||||||
|
* Index: Array index to the properties
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
proccessTile: function(rows, coord, zoom) {
|
||||||
|
var r;
|
||||||
|
var x = new Uint8Array(rows.length);
|
||||||
|
var y = new Uint8Array(rows.length);
|
||||||
|
|
||||||
|
var prof_mem = Profiler.metric('ProviderJSON:mem');
|
||||||
|
var prof_point_count = Profiler.metric('ProviderJSON:point_count');
|
||||||
|
var prof_process_time = Profiler.metric('ProviderJSON:process_time').start()
|
||||||
|
|
||||||
|
// count number of dates
|
||||||
|
var dates = 0;
|
||||||
|
var maxDateSlots = -1;
|
||||||
|
for (r = 0; r < rows.length; ++r) {
|
||||||
|
var row = rows[r];
|
||||||
|
dates += row.dates__uint16.length;
|
||||||
|
for(var d = 0; d < row.dates__uint16.length; ++d) {
|
||||||
|
maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.options.cumulative) {
|
||||||
|
dates = (1 + maxDateSlots) * rows.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var type = this.options.cumulative ? Uint32Array: Uint8Array;
|
||||||
|
|
||||||
|
// reserve memory for all the dates
|
||||||
|
var timeIndex = new Int32Array(maxDateSlots + 1); //index-size
|
||||||
|
var timeCount = new Int32Array(maxDateSlots + 1);
|
||||||
|
var renderData = new (this.options.valueDataType || type)(dates);
|
||||||
|
var renderDataPos = new Uint32Array(dates);
|
||||||
|
|
||||||
|
prof_mem.inc(
|
||||||
|
4 * maxDateSlots + // timeIndex
|
||||||
|
4 * maxDateSlots + // timeCount
|
||||||
|
dates + //renderData
|
||||||
|
dates * 4
|
||||||
|
); //renderDataPos
|
||||||
|
|
||||||
|
prof_point_count.inc(rows.length);
|
||||||
|
|
||||||
|
var rowsPerSlot = {};
|
||||||
|
|
||||||
|
// precache pixel positions
|
||||||
|
for (var r = 0; r < rows.length; ++r) {
|
||||||
|
var row = rows[r];
|
||||||
|
x[r] = row.x__uint8 * this.options.resolution;
|
||||||
|
// fix value when it's in the tile EDGE
|
||||||
|
// TODO: this should be fixed in SQL query
|
||||||
|
if (row.y__uint8 === -1) {
|
||||||
|
y[r] = 0;
|
||||||
|
} else {
|
||||||
|
y[r] = row.y__uint8 * this.options.resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dates = row.dates__uint16;
|
||||||
|
var vals = row.vals__uint8;
|
||||||
|
if (!this.options.cumulative) {
|
||||||
|
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||||
|
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
|
||||||
|
if(this.options.cumulative) {
|
||||||
|
vals[j] += prev_val;
|
||||||
|
}
|
||||||
|
prev_val = vals[j];
|
||||||
|
rr.push([r, vals[j]]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var valByDate = {}
|
||||||
|
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||||
|
valByDate[dates[j]] = vals[j];
|
||||||
|
}
|
||||||
|
var accum = 0;
|
||||||
|
|
||||||
|
// extend the latest to the end
|
||||||
|
for (var j = dates[0]; j <= maxDateSlots; ++j) {
|
||||||
|
var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []);
|
||||||
|
var v = valByDate[j];
|
||||||
|
if (v) {
|
||||||
|
accum += v;
|
||||||
|
}
|
||||||
|
rr.push([r, accum]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*var lastDateSlot = dates[dates.length - 1];
|
||||||
|
for (var j = lastDateSlot + 1; j <= maxDateSlots; ++j) {
|
||||||
|
var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []);
|
||||||
|
rr.push([r, prev_val]);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// for each timeslot search active buckets
|
||||||
|
var renderDataIndex = 0;
|
||||||
|
var timeSlotIndex = 0;
|
||||||
|
var i = 0;
|
||||||
|
for(var i = 0; i <= maxDateSlots; ++i) {
|
||||||
|
var c = 0;
|
||||||
|
var slotRows = rowsPerSlot[i]
|
||||||
|
if(slotRows) {
|
||||||
|
for (var r = 0; r < slotRows.length; ++r) {
|
||||||
|
var rr = slotRows[r];
|
||||||
|
++c;
|
||||||
|
renderDataPos[renderDataIndex] = rr[0]
|
||||||
|
renderData[renderDataIndex] = rr[1];
|
||||||
|
++renderDataIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeIndex[i] = timeSlotIndex;
|
||||||
|
timeCount[i] = c;
|
||||||
|
timeSlotIndex += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
prof_process_time.end();
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: zoom,
|
||||||
|
coord: {
|
||||||
|
x: coord.x,
|
||||||
|
y: coord.y,
|
||||||
|
z: zoom
|
||||||
|
},
|
||||||
|
timeCount: timeCount,
|
||||||
|
timeIndex: timeIndex,
|
||||||
|
renderDataPos: renderDataPos,
|
||||||
|
renderData: renderData,
|
||||||
|
maxDate: maxDateSlots
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_generateFilterSQLForCat:function(name,categories){
|
||||||
|
return name+" in "+categories;
|
||||||
|
},
|
||||||
|
_generateFilterSQLForRange:function(name,range){
|
||||||
|
var result = ""
|
||||||
|
if(range.start){
|
||||||
|
result += " " + name + " > " + range.start;
|
||||||
|
}
|
||||||
|
if(range.end){
|
||||||
|
result += " " + name + " < " + range.end;
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
_setFilters:function(filters){
|
||||||
|
this.filters = filters
|
||||||
|
},
|
||||||
|
_generateFiltersSQL:function(){
|
||||||
|
return Object.keys(this._filters).map(function(filterName){
|
||||||
|
var filter = this._filters[filterName]
|
||||||
|
if(filter){
|
||||||
|
if(filter.type == 'range'){
|
||||||
|
return this._generateFilterSQLForRange(filterName, filter.range)
|
||||||
|
}
|
||||||
|
else if(filter.type == 'cat'){
|
||||||
|
return this._generateFilterSQLForCat(filterName, filter.categories)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
_host: function() {
|
||||||
|
var opts = this.options;
|
||||||
|
var port = opts.sql_api_port;
|
||||||
|
var domain = ((opts.user_name || opts.user) + '.' + (opts.sql_api_domain || 'cartodb.com')) + (port ? ':' + port: '');
|
||||||
|
var protocol = opts.sql_api_protocol || 'http';
|
||||||
|
return this.options.url || protocol + '://' + domain + '/api/v2/sql';
|
||||||
|
},
|
||||||
|
|
||||||
|
url: function(subhost) {
|
||||||
|
var opts = this.options;
|
||||||
|
var protocol = opts.sql_api_protocol || 'http';
|
||||||
|
if (!this.options.cdn_url) {
|
||||||
|
return this._host();
|
||||||
|
}
|
||||||
|
var h = protocol+ "://";
|
||||||
|
if (subhost) {
|
||||||
|
h += subhost + ".";
|
||||||
|
}
|
||||||
|
var cdn_host = opts.cdn_url;
|
||||||
|
if(!cdn_host.http && !cdn_host.https) {
|
||||||
|
throw new Error("cdn_host should contain http and/or https entries");
|
||||||
|
}
|
||||||
|
h += cdn_host[protocol] + "/" + (opts.user_name || opts.user) + '/api/v2/sql';
|
||||||
|
return h;
|
||||||
|
},
|
||||||
|
|
||||||
|
_hash: function(str) {
|
||||||
|
var hash = 0;
|
||||||
|
if (!str || str.length == 0) return hash;
|
||||||
|
for (var i = 0, l = str.length; i < l; ++i) {
|
||||||
|
hash = (( (hash << 5 ) - hash ) + str.charCodeAt(i)) | 0;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
},
|
||||||
|
|
||||||
|
_extraParams: function() {
|
||||||
|
if (this.options.extra_params) {
|
||||||
|
var p = [];
|
||||||
|
for(var k in this.options.extra_params) {
|
||||||
|
var v = this.options.extra_params[k];
|
||||||
|
if (v) {
|
||||||
|
p.push(k + "=" + encodeURIComponent(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.join('&');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
isHttps: function() {
|
||||||
|
return this.options.sql_api_protocol && this.options.sql_api_protocol === 'https';
|
||||||
|
},
|
||||||
|
|
||||||
|
// execute actual query
|
||||||
|
sql: function(sql, callback, options) {
|
||||||
|
options = options || {};
|
||||||
|
var subdomains = this.options.subdomains || '0123';
|
||||||
|
if(this.isHttps()) {
|
||||||
|
subdomains = [null]; // no subdomain
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var url;
|
||||||
|
if (options.no_cdn) {
|
||||||
|
url = this._host();
|
||||||
|
} else {
|
||||||
|
url = this.url(subdomains[Math.abs(this._hash(sql))%subdomains.length]);
|
||||||
|
}
|
||||||
|
var extra = this._extraParams();
|
||||||
|
torque.net.get( url + "?q=" + encodeURIComponent(sql) + (extra ? "&" + extra: ''), function (data) {
|
||||||
|
if(options.parseJSON) {
|
||||||
|
data = JSON.parse(data && data.responseText);
|
||||||
|
}
|
||||||
|
callback && callback(data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getTileData: function(coord, zoom, callback) {
|
||||||
|
if(!this._ready) {
|
||||||
|
this._tileQueue.push([coord, zoom, callback]);
|
||||||
|
} else {
|
||||||
|
this._getTileData(coord, zoom, callback);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setReady: function(ready) {
|
||||||
|
this._ready = true;
|
||||||
|
this._processQueue();
|
||||||
|
this.options.ready && this.options.ready();
|
||||||
|
},
|
||||||
|
|
||||||
|
_processQueue: function() {
|
||||||
|
var item;
|
||||||
|
while (item = this._tileQueue.pop()) {
|
||||||
|
this._getTileData.apply(this, item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `coord` object like {x : tilex, y: tiley }
|
||||||
|
* `zoom` quadtree zoom level
|
||||||
|
*/
|
||||||
|
|
||||||
|
_getTileData: function(coord, zoom, callback) {
|
||||||
|
var prof_fetch_time = Profiler.metric('ProviderJSON:tile_fetch_time').start()
|
||||||
|
this.table = this.options.table;
|
||||||
|
var numTiles = 1 << zoom;
|
||||||
|
|
||||||
|
var column_conv = this.options.column;
|
||||||
|
|
||||||
|
if(this.options.is_time) {
|
||||||
|
column_conv = format("date_part('epoch', {column})", this.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sql = "" +
|
||||||
|
"WITH " +
|
||||||
|
"par AS (" +
|
||||||
|
" SELECT CDB_XYZ_Resolution({zoom})*{resolution} as res" +
|
||||||
|
", 256/{resolution} as tile_size" +
|
||||||
|
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
|
||||||
|
")," +
|
||||||
|
"cte AS ( "+
|
||||||
|
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
|
||||||
|
", avg(passenger_count) c1, avg(tip_amount) c2, sum( case when payment_type=1 then 1 else 0 end) c3, sum(case when payment_type=2 then 1 else 0 end ) c4, sum(case when payment_type=3 then 1 else 0 end ) c5,sum(case when payment_type=4 then 1 else 0 end ) c6, count(cartodb_id) c7 " +
|
||||||
|
" FROM ({_sql}) i, par p " +
|
||||||
|
" WHERE i.the_geom_webmercator && p.ext " +
|
||||||
|
" GROUP BY g" +
|
||||||
|
") " +
|
||||||
|
"" +
|
||||||
|
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
|
||||||
|
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
|
||||||
|
" Array[c1,c2,c3,c4,c5,c6,c7] vals__uint8," +
|
||||||
|
" Array[0,1,2,3,4,5,6] dates__uint16" +
|
||||||
|
// the tile_size where are needed because the overlaps query in cte subquery includes the points
|
||||||
|
// in the left and bottom borders of the tile
|
||||||
|
" FROM cte, par p where (st_y(g)-st_ymin(p.ext))/p.res < tile_size and (st_x(g)-st_xmin(p.ext))/p.res < tile_size ";
|
||||||
|
|
||||||
|
|
||||||
|
var query = format(sql, this.options, {
|
||||||
|
zoom: zoom,
|
||||||
|
x: coord.x,
|
||||||
|
y: coord.y,
|
||||||
|
column_conv: column_conv,
|
||||||
|
_sql: this.getSQL()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.sql(query, function (data) {
|
||||||
|
if (data) {
|
||||||
|
var rows = JSON.parse(data.responseText).rows;
|
||||||
|
callback(self.proccessTile(rows, coord, zoom));
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
prof_fetch_time.end();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getKeySpan: function() {
|
||||||
|
return {
|
||||||
|
start: this.options.start * 1000,
|
||||||
|
end: this.options.end * 1000,
|
||||||
|
step: this.options.step,
|
||||||
|
steps: this.options.steps,
|
||||||
|
columnType: this.options.is_time ? 'date': 'number'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
setColumn: function(column, isTime) {
|
||||||
|
this.options.column = column;
|
||||||
|
this.options.is_time = isTime === undefined ? true: false;
|
||||||
|
this.reload();
|
||||||
|
},
|
||||||
|
|
||||||
|
setResolution: function(res) {
|
||||||
|
this.options.resolution = res;
|
||||||
|
},
|
||||||
|
|
||||||
|
idForRange: function(variable){
|
||||||
|
return this._mapping[variable].col_id
|
||||||
|
},
|
||||||
|
idsForCategory:function(variable){
|
||||||
|
return this._mapping[variable].col_ids
|
||||||
|
},
|
||||||
|
idForCategoryOption:function(variable, option){
|
||||||
|
return this._mapping[variable].col_ids.to_a.select(function(a){return option==a[0]})[0][1]
|
||||||
|
},
|
||||||
|
optionForId:function(id){
|
||||||
|
Object.keys(this._mapping).forEach(function(varaibleName){
|
||||||
|
var variable = this._mapping[variableName]
|
||||||
|
if(variable.type=='range' && variable.col_id==id){
|
||||||
|
return variableName
|
||||||
|
}
|
||||||
|
else if(variable.type=='cat') {
|
||||||
|
Object.keys(variable.col_ids).forEach(function(key){
|
||||||
|
if(variable.col_ids[key] == id){
|
||||||
|
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
// return true if tiles has been changed
|
||||||
|
setOptions: function(opt) {
|
||||||
|
var refresh = false;
|
||||||
|
|
||||||
|
if(opt.resolution !== undefined && opt.resolution !== this.options.resolution) {
|
||||||
|
this.options.resolution = opt.resolution;
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opt.steps !== undefined && opt.steps !== this.options.steps) {
|
||||||
|
this.setSteps(opt.steps, { silent: true });
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opt.column !== undefined && opt.column !== this.options.column) {
|
||||||
|
this.options.column = opt.column;
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opt.countby !== undefined && opt.countby !== this.options.countby) {
|
||||||
|
this.options.countby = opt.countby;
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(opt.data_aggregation !== undefined) {
|
||||||
|
var c = opt.data_aggregation === 'cumulative';
|
||||||
|
if (this.options.cumulative !== c) {
|
||||||
|
this.options.cumulative = c;
|
||||||
|
refresh = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refresh) this.reload();
|
||||||
|
return refresh;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
reload: function() {
|
||||||
|
this._ready = false;
|
||||||
|
this._fetchKeySpan();
|
||||||
|
},
|
||||||
|
|
||||||
|
setSQL: function(sql) {
|
||||||
|
if (this.options.sql != sql) {
|
||||||
|
this.options.sql = sql;
|
||||||
|
this.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getSteps: function() {
|
||||||
|
return Math.min(this.options.steps, this.options.data_steps);
|
||||||
|
},
|
||||||
|
|
||||||
|
setSteps: function(steps, opt) {
|
||||||
|
opt = opt || {};
|
||||||
|
if (this.options.steps !== steps) {
|
||||||
|
this.options.steps = steps;
|
||||||
|
this.options.step = (this.options.end - this.options.start)/this.getSteps();
|
||||||
|
this.options.step = this.options.step || 1;
|
||||||
|
if (!opt.silent) this.reload();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getBounds: function() {
|
||||||
|
return this.options.bounds;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSQL: function() {
|
||||||
|
return this.options.sql || "select * from " + this.options.table;
|
||||||
|
},
|
||||||
|
|
||||||
|
_tilerHost: function() {
|
||||||
|
var opts = this.options;
|
||||||
|
var user = (opts.user_name || opts.user);
|
||||||
|
return opts.tiler_protocol +
|
||||||
|
"://" + (user ? user + "." : "") +
|
||||||
|
opts.tiler_domain +
|
||||||
|
((opts.tiler_port != "") ? (":" + opts.tiler_port) : "");
|
||||||
|
},
|
||||||
|
|
||||||
|
_fetchUpdateAt: function(callback) {
|
||||||
|
var self = this;
|
||||||
|
var layergroup = {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"stat_tag": this.options.stat_tag || 'torque',
|
||||||
|
"layers": [{
|
||||||
|
"type": "cartodb",
|
||||||
|
"options": {
|
||||||
|
"cartocss_version": "2.1.1",
|
||||||
|
"cartocss": "#layer {}",
|
||||||
|
"sql": this.getSQL()
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
var url = this._tilerHost() + "/tiles/layergroup";
|
||||||
|
var extra = this._extraParams();
|
||||||
|
|
||||||
|
// tiler needs map_key instead of api_key
|
||||||
|
// so replace it
|
||||||
|
if (extra) {
|
||||||
|
extra = extra.replace('api_key=', 'map_key=');
|
||||||
|
}
|
||||||
|
|
||||||
|
url = url +
|
||||||
|
"?config=" + encodeURIComponent(JSON.stringify(layergroup)) +
|
||||||
|
"&callback=?" + (extra ? "&" + extra: '');
|
||||||
|
|
||||||
|
torque.net.jsonp(url, function (data) {
|
||||||
|
var query = format("select * from ({sql}) __torque_wrap_sql limit 0", { sql: self.getSQL() });
|
||||||
|
self.sql(query, function (queryData) {
|
||||||
|
if (data && queryData) {
|
||||||
|
callback({
|
||||||
|
updated_at: data.last_updated,
|
||||||
|
fields: queryData.fields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, { parseJSON: true });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// the data range could be set by the user though ``start``
|
||||||
|
// option. It can be fecthed from the table when the start
|
||||||
|
// is not specified.
|
||||||
|
//
|
||||||
|
_fetchKeySpan: function() {
|
||||||
|
var self = this;
|
||||||
|
var max_col, min_col, max_tmpl, min_tmpl;
|
||||||
|
|
||||||
|
this._fetchUpdateAt(function(data) {
|
||||||
|
if (!data) return;
|
||||||
|
self.options.extra_params = self.options.extra_params || {};
|
||||||
|
self.options.extra_params.last_updated = data.updated_at || 0;
|
||||||
|
self.options.extra_params.cache_policy = 'persist';
|
||||||
|
self.options.is_time = data.fields[self.options.column].type === 'date';
|
||||||
|
|
||||||
|
var column_conv = self.options.column;
|
||||||
|
if (self.options.is_time){
|
||||||
|
max_tmpl = "date_part('epoch', max({column}))";
|
||||||
|
min_tmpl = "date_part('epoch', min({column}))";
|
||||||
|
column_conv = format("date_part('epoch', {column})", self.options);
|
||||||
|
} else {
|
||||||
|
max_tmpl = "max({column})";
|
||||||
|
min_tmpl = "min({column})";
|
||||||
|
}
|
||||||
|
|
||||||
|
max_col = format(max_tmpl, { column: self.options.column });
|
||||||
|
min_col = format(min_tmpl, { column: self.options.column });
|
||||||
|
|
||||||
|
/*var sql_stats = "" +
|
||||||
|
"WITH summary_groups as ( " +
|
||||||
|
"WITH summary as ( " +
|
||||||
|
"select (row_number() over (order by __time_col asc nulls last)+1)/2 as rownum, __time_col " +
|
||||||
|
"from (select *, {column} as __time_col from ({sql}) __s) __torque_wrap_sql " +
|
||||||
|
"order by __time_col asc " +
|
||||||
|
") " +
|
||||||
|
"SELECT " +
|
||||||
|
"max(__time_col) OVER(PARTITION BY rownum) - " +
|
||||||
|
"min(__time_col) OVER(PARTITION BY rownum) diff " +
|
||||||
|
"FROM summary " +
|
||||||
|
"), subq as ( " +
|
||||||
|
" SELECT " +
|
||||||
|
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
|
||||||
|
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
|
||||||
|
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
|
||||||
|
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
|
||||||
|
"{max_col} max, " +
|
||||||
|
"{min_col} min FROM ({sql}) __torque_wrap_sql " +
|
||||||
|
")" +
|
||||||
|
"SELECT " +
|
||||||
|
"xmax, xmin, ymax, ymin, a.max as max_date, a.min as min_date, " +
|
||||||
|
"avg(diff) as diffavg," +
|
||||||
|
"(a.max - a.min)/avg(diff) as num_steps " +
|
||||||
|
"FROM summary_groups, subq a " +
|
||||||
|
"WHERE diff > 0 group by xmax, xmin, ymax, ymin, max_date, min_date";
|
||||||
|
*/
|
||||||
|
var sql_stats = " SELECT " +
|
||||||
|
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
|
||||||
|
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
|
||||||
|
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
|
||||||
|
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
|
||||||
|
"count(*) as num_steps, " +
|
||||||
|
"{max_col} max_date, " +
|
||||||
|
"{min_col} min_date FROM ({sql}) __torque_wrap_sql ";
|
||||||
|
|
||||||
|
var sql = format(sql_stats, {
|
||||||
|
max_col: max_col,
|
||||||
|
min_col: min_col,
|
||||||
|
column: column_conv,
|
||||||
|
sql: self.getSQL()
|
||||||
|
});
|
||||||
|
|
||||||
|
self.sql(sql, function(data) {
|
||||||
|
//TODO: manage bounds
|
||||||
|
data = data.rows[0];
|
||||||
|
self.options.start = data.min_date;
|
||||||
|
self.options.end = data.max_date;
|
||||||
|
self.options.step = (data.max_date - data.min_date)/Math.min(self.options.steps, data.num_steps>>0);
|
||||||
|
self.options.data_steps = data.num_steps >> 0;
|
||||||
|
// step can't be 0
|
||||||
|
self.options.step = self.options.step || 1;
|
||||||
|
self.options.bounds = [
|
||||||
|
[data.ymin, data.xmin],
|
||||||
|
[data.ymax, data.xmax]
|
||||||
|
];
|
||||||
|
self._setReady(true);
|
||||||
|
}, { parseJSON: true, no_cdn: true });
|
||||||
|
}, { parseJSON: true, no_cdn: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = filterableJson;
|
@ -1,5 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
json: require('./json'),
|
json: require('./json'),
|
||||||
|
filterableJson: require('./filterableJson'),
|
||||||
JsonArray: require('./jsonarray'),
|
JsonArray: require('./jsonarray'),
|
||||||
windshaft: require('./windshaft'),
|
windshaft: require('./windshaft'),
|
||||||
tileJSON: require('./tilejson')
|
tileJSON: require('./tilejson')
|
||||||
|
Loading…
Reference in New Issue
Block a user