redshift provider and example for torque
This commit is contained in:
parent
69b3f97a4d
commit
28aa2ef8c8
14
dist/torque.full.js
vendored
14
dist/torque.full.js
vendored
File diff suppressed because one or more lines are too long
708
dist/torque.full.uncompressed.js
vendored
708
dist/torque.full.uncompressed.js
vendored
File diff suppressed because it is too large
Load Diff
6
dist/torque.js
vendored
6
dist/torque.js
vendored
File diff suppressed because one or more lines are too long
612
dist/torque.uncompressed.js
vendored
612
dist/torque.uncompressed.js
vendored
@ -1613,7 +1613,8 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
providers: {
|
||||
'sql_api': torque.providers.json,
|
||||
'url_template': torque.providers.JsonArray,
|
||||
'windshaft': torque.providers.windshaft
|
||||
'windshaft': torque.providers.windshaft,
|
||||
'redshift': torgue.redshift
|
||||
},
|
||||
|
||||
renderers: {
|
||||
@ -1772,7 +1773,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
},
|
||||
|
||||
/**
|
||||
* helper function, does the same than ``setKey`` but only
|
||||
* helper function, does the same than ``setKey`` but only
|
||||
* accepts scalars.
|
||||
*/
|
||||
setStep: function(time) {
|
||||
@ -1783,10 +1784,10 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
},
|
||||
|
||||
/**
|
||||
* transform from animation step to Date object
|
||||
* transform from animation step to Date object
|
||||
* that contains the animation time
|
||||
*
|
||||
* ``step`` should be between 0 and ``steps - 1``
|
||||
* ``step`` should be between 0 and ``steps - 1``
|
||||
*/
|
||||
stepToTime: function(step) {
|
||||
if (!this.provider) return 0;
|
||||
@ -1890,7 +1891,7 @@ GMapsTorqueLayer.prototype = torque.extend({},
|
||||
}
|
||||
return sum;
|
||||
},
|
||||
|
||||
|
||||
error: function (callback) {
|
||||
this.options.errorCallback = callback;
|
||||
return this;
|
||||
@ -2000,7 +2001,7 @@ module.exports.GMapsTileLoader = gmaps.GMapsTileLoader;
|
||||
module.exports.GMapsTorqueLayer = gmaps.GMapsTorqueLayer;
|
||||
module.exports.GMapsTiledTorqueLayer = gmaps.GMapsTiledTorqueLayer;
|
||||
|
||||
},{"./animator":1,"./cartocss_reference":2,"./common":3,"./core":4,"./gmaps":8,"./leaflet":12,"./math":15,"./mercator":16,"./provider":18,"./renderer":23,"./request":27}],11:[function(require,module,exports){
|
||||
},{"./animator":1,"./cartocss_reference":2,"./common":3,"./core":4,"./gmaps":8,"./leaflet":12,"./math":15,"./mercator":16,"./provider":18,"./renderer":24,"./request":28}],11:[function(require,module,exports){
|
||||
require('./leaflet_tileloader_mixin');
|
||||
|
||||
/**
|
||||
@ -2425,7 +2426,8 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
providers: {
|
||||
'sql_api': torque.providers.json,
|
||||
'url_template': torque.providers.JsonArray,
|
||||
'windshaft': torque.providers.windshaft
|
||||
'windshaft': torque.providers.windshaft,
|
||||
'redshift' : torque.providers.redshift
|
||||
},
|
||||
|
||||
renderers: {
|
||||
@ -2501,6 +2503,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
// for each tile shown on the map request the data
|
||||
this.on('tileAdded', function(t) {
|
||||
var tileData = this.provider.getTileData(t, t.zoom, function(tileData) {
|
||||
console.log("tile data is ", tileData)
|
||||
// don't load tiles that are not being shown
|
||||
if (t.zoom !== self._map.getZoom()) return;
|
||||
self._tileLoaded(t, tileData);
|
||||
@ -3102,10 +3105,11 @@ module.exports = Profiler;
|
||||
module.exports = {
|
||||
json: require('./json'),
|
||||
JsonArray: require('./jsonarray'),
|
||||
windshaft: require('./windshaft')
|
||||
windshaft: require('./windshaft'),
|
||||
redshift: require('./redshift')
|
||||
};
|
||||
|
||||
},{"./json":19,"./jsonarray":20,"./windshaft":21}],19:[function(require,module,exports){
|
||||
},{"./json":19,"./jsonarray":20,"./redshift":21,"./windshaft":22}],19:[function(require,module,exports){
|
||||
var torque = require('../');
|
||||
var Profiler = require('../profiler');
|
||||
|
||||
@ -3230,13 +3234,11 @@ var Profiler = require('../profiler');
|
||||
var val_keys = Object.keys(row).filter(function(k){return (k.indexOf("vals__uint8") > -1) })
|
||||
var val_arr = []
|
||||
|
||||
|
||||
val_keys.forEach(function(key){
|
||||
var i = (key=='vals_uint8' ? 0 : key.match(/vals__uint8_(\d+)/)[1])
|
||||
val_arr[i] = row[key];
|
||||
})
|
||||
|
||||
|
||||
if (!this.options.cumulative) {
|
||||
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
|
||||
@ -3967,6 +3969,582 @@ var Profiler = require('../profiler');
|
||||
module.exports = json;
|
||||
|
||||
},{"../":10,"../profiler":17}],21:[function(require,module,exports){
|
||||
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 json = function (options) {
|
||||
this._ready = false;
|
||||
this._tileQueue = [];
|
||||
this.options = options;
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
json.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 val_keys = []
|
||||
if(rows.length>0){
|
||||
val_keys = Object.keys(rows[0]).filter(function(k){return (k.indexOf("vals__uint8") > -1) })
|
||||
}
|
||||
|
||||
|
||||
var renderData = []
|
||||
|
||||
val_keys.forEach(function(key,index){
|
||||
renderData[index] = new (this.options.valueDataType || type)(dates);
|
||||
}.bind(this))
|
||||
|
||||
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 val_keys = Object.keys(row).filter(function(k){return (k.indexOf("vals__uint8") > -1) })
|
||||
var val_arr = []
|
||||
|
||||
val_keys.forEach(function(key){
|
||||
var i = (key=='vals_uint8' ? 0 : key.match(/vals__uint8_(\d+)/)[1])
|
||||
val_arr[i] = row[key];
|
||||
})
|
||||
|
||||
if (!this.options.cumulative) {
|
||||
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
|
||||
//Stuart: Not sure I understand why this is here?
|
||||
// if(this.options.cumulative) {
|
||||
// vals[j] += prev_val;
|
||||
// }
|
||||
|
||||
// prev_val = vals[j];
|
||||
var all_vals = []
|
||||
val_arr.forEach(function(vals){
|
||||
all_vals.push(vals[j])
|
||||
})
|
||||
rr.push([r, all_vals]);
|
||||
|
||||
}
|
||||
} 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]
|
||||
rr[1].forEach(function(rrr,index){
|
||||
renderData[index][renderDataIndex] = rrr;
|
||||
})
|
||||
++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
|
||||
};
|
||||
},
|
||||
|
||||
_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) {
|
||||
|
||||
console.log("GETTING TILE!!!!!!!!")
|
||||
|
||||
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;
|
||||
var aggcol = this.options.countby
|
||||
var host = "http://rs-torque.cartodb.io/" // "http://localhost:3322/"
|
||||
var request_url = host+this.table+"/"+zoom+"/"+coord.x+"/"+coord.y+"?datecol="+column_conv+"&aggcol="+aggcol+"&callback=?"
|
||||
console.log("request url ", request_url)
|
||||
var self = this;
|
||||
|
||||
$.getJSON(request_url, function (data) {
|
||||
callback && callback(self.proccessTile(data.rows, coord, zoom));
|
||||
}.bind(this));
|
||||
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;
|
||||
},
|
||||
|
||||
// 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 = json;
|
||||
|
||||
},{"../":10,"../profiler":17}],22:[function(require,module,exports){
|
||||
var torque = require('../');
|
||||
var Profiler = require('../profiler');
|
||||
|
||||
@ -4456,7 +5034,7 @@ var Profiler = require('../profiler');
|
||||
|
||||
module.exports = windshaft;
|
||||
|
||||
},{"../":10,"../profiler":17}],22:[function(require,module,exports){
|
||||
},{"../":10,"../profiler":17}],23:[function(require,module,exports){
|
||||
var TAU = Math.PI*2;
|
||||
// min value to render a line.
|
||||
// it does not make sense to render a line of a width is not even visible
|
||||
@ -4590,13 +5168,13 @@ module.exports = {
|
||||
MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS
|
||||
};
|
||||
|
||||
},{}],23:[function(require,module,exports){
|
||||
},{}],24:[function(require,module,exports){
|
||||
module.exports = {
|
||||
cartocss: require('./cartocss_render'),
|
||||
Point: require('./point'),
|
||||
Rectangle: require('./rectangle')
|
||||
};
|
||||
},{"./cartocss_render":22,"./point":24,"./rectangle":25}],24:[function(require,module,exports){
|
||||
},{"./cartocss_render":23,"./point":25,"./rectangle":26}],25:[function(require,module,exports){
|
||||
(function (global){
|
||||
var torque = require('../');
|
||||
var cartocss = require('./cartocss_render');
|
||||
@ -5109,7 +5687,7 @@ var Filters = require('./torque_filters');
|
||||
module.exports = PointRenderer;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"../":10,"../profiler":17,"./cartocss_render":22,"./torque_filters":26,"carto":undefined}],25:[function(require,module,exports){
|
||||
},{"../":10,"../profiler":17,"./cartocss_render":23,"./torque_filters":27,"carto":undefined}],26:[function(require,module,exports){
|
||||
(function (global){
|
||||
var carto = global.carto || require('carto');
|
||||
|
||||
@ -5273,7 +5851,7 @@ var carto = global.carto || require('carto');
|
||||
module.exports = RectanbleRenderer;
|
||||
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"carto":undefined}],26:[function(require,module,exports){
|
||||
},{"carto":undefined}],27:[function(require,module,exports){
|
||||
/*
|
||||
Based on simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas,
|
||||
by Vladimir Agafonkin
|
||||
@ -5365,7 +5943,7 @@ torque_filters.prototype = {
|
||||
|
||||
module.exports = torque_filters;
|
||||
|
||||
},{}],27:[function(require,module,exports){
|
||||
},{}],28:[function(require,module,exports){
|
||||
(function (global){
|
||||
var torque = require('./core');
|
||||
|
||||
|
80
examples/ReshiftTest.html
Normal file
80
examples/ReshiftTest.html
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
<html>
|
||||
<link rel="stylesheet" href="vendor/leaflet.css" />
|
||||
<style>
|
||||
#map, html, body {
|
||||
width: 100%; height: 100%; padding: 0; margin: 0;
|
||||
}
|
||||
#title {
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 50px;
|
||||
color: white;
|
||||
font-size: 27px;
|
||||
font-family: Helvetica, sans-serif;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div id="title">Average temperature collected by Britain's Royal Navy (1913-1925)</div>
|
||||
<script src='https://code.jquery.com/jquery-2.1.4.min.js'></script>
|
||||
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
|
||||
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
|
||||
<script src="../dist/torque.full.uncompressed.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
// define the torque layer style using cartocss
|
||||
// this creates a kind of density map
|
||||
//color scale from http://colorbrewer2.org/
|
||||
var CARTOCSS = [
|
||||
'Map {',
|
||||
'-torque-time-attribute: "pickup_datetime";',
|
||||
'-torque-aggregation-function: "rate_code";',
|
||||
'-torque-frame-count: 256;',
|
||||
'-torque-animation-duration: 60;',
|
||||
'-torque-resolution: 1',
|
||||
|
||||
'}',
|
||||
'#layer {',
|
||||
' marker-width: 1;',
|
||||
' marker-fill-opacity: 1.0;',
|
||||
' marker-fill: #fff5eb; ',
|
||||
' marker-type: rectangle;',
|
||||
' [value > 1] { marker-fill: #fee6ce; }',
|
||||
' [value > 2] { marker-fill: #fdd0a2; }',
|
||||
' [value > 4] { marker-fill: #fdae6b; }',
|
||||
' [value > 10] { marker-fill: #fd8d3c; }',
|
||||
' [value > 15] { marker-fill: #f16913; }',
|
||||
' [value > 20] { marker-fill: #d94801; }',
|
||||
' [value > 25] { marker-fill: #8c2d04; }',
|
||||
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
|
||||
var map = new L.Map('map', {
|
||||
zoomControl: true,
|
||||
center: [40.730738560234485, -73.9738655090332],
|
||||
zoom: 12
|
||||
});
|
||||
|
||||
|
||||
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
|
||||
attribution: 'CartoDB'
|
||||
}).addTo(map);
|
||||
|
||||
var torqueLayer = new L.TorqueLayer({
|
||||
table : 'taxi',
|
||||
provider : 'redshift',
|
||||
cartocss: CARTOCSS,
|
||||
start : '1',
|
||||
url: "http://rs-torque.cartodb.io/"
|
||||
});
|
||||
torqueLayer.addTo(map);
|
||||
torqueLayer.play();
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
574
lib/torque/provider/redshift.js
Normal file
574
lib/torque/provider/redshift.js
Normal file
@ -0,0 +1,574 @@
|
||||
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 json = function (options) {
|
||||
this._ready = false;
|
||||
this._tileQueue = [];
|
||||
this.options = options;
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
json.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 val_keys = []
|
||||
if(rows.length>0){
|
||||
val_keys = Object.keys(rows[0]).filter(function(k){return (k.indexOf("vals__uint8") > -1) })
|
||||
}
|
||||
|
||||
|
||||
var renderData = []
|
||||
|
||||
val_keys.forEach(function(key,index){
|
||||
renderData[index] = new (this.options.valueDataType || type)(dates);
|
||||
}.bind(this))
|
||||
|
||||
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 val_keys = Object.keys(row).filter(function(k){return (k.indexOf("vals__uint8") > -1) })
|
||||
var val_arr = []
|
||||
|
||||
val_keys.forEach(function(key){
|
||||
var i = (key=='vals_uint8' ? 0 : key.match(/vals__uint8_(\d+)/)[1])
|
||||
val_arr[i] = row[key];
|
||||
})
|
||||
|
||||
if (!this.options.cumulative) {
|
||||
for (var j = 0, len = dates.length; j < len; ++j) {
|
||||
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
|
||||
//Stuart: Not sure I understand why this is here?
|
||||
// if(this.options.cumulative) {
|
||||
// vals[j] += prev_val;
|
||||
// }
|
||||
|
||||
// prev_val = vals[j];
|
||||
var all_vals = []
|
||||
val_arr.forEach(function(vals){
|
||||
all_vals.push(vals[j])
|
||||
})
|
||||
rr.push([r, all_vals]);
|
||||
|
||||
}
|
||||
} 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]
|
||||
rr[1].forEach(function(rrr,index){
|
||||
renderData[index][renderDataIndex] = rrr;
|
||||
})
|
||||
++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
|
||||
};
|
||||
},
|
||||
|
||||
_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) {
|
||||
|
||||
console.log("GETTING TILE!!!!!!!!")
|
||||
|
||||
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;
|
||||
var aggcol = this.options.countby
|
||||
var host = "http://rs-torque.cartodb.io/" // "http://localhost:3322/"
|
||||
var request_url = host+this.table+"/"+zoom+"/"+coord.x+"/"+coord.y+"?datecol="+column_conv+"&aggcol="+aggcol+"&callback=?"
|
||||
console.log("request url ", request_url)
|
||||
var self = this;
|
||||
|
||||
$.getJSON(request_url, function (data) {
|
||||
callback && callback(self.proccessTile(data.rows, coord, zoom));
|
||||
}.bind(this));
|
||||
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;
|
||||
},
|
||||
|
||||
// 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 = json;
|
Loading…
Reference in New Issue
Block a user