Compare commits
33 Commits
master
...
multi_vari
Author | SHA1 | Date | |
---|---|---|---|
|
dc389b7f1b | ||
|
69b3f97a4d | ||
|
7292104895 | ||
|
a27e8f88ff | ||
|
a47279e11a | ||
|
120e383630 | ||
|
f41d79a045 | ||
|
29220a16ec | ||
|
dfc8345f35 | ||
|
ed585820ac | ||
|
3964b32b2e | ||
|
05aa3db45c | ||
|
1b93a6c67e | ||
|
70c34d2d30 | ||
|
3a9f47b9f2 | ||
|
25d0fb77a5 | ||
|
73ed8bdfd7 | ||
|
cd7bb694bf | ||
|
73e2a3736c | ||
|
5763feb0bd | ||
|
a2f7ce04ea | ||
|
b24b4b0db9 | ||
|
266919ad1b | ||
|
c75271dacf | ||
|
e9bde54840 | ||
|
b9bcb1b33f | ||
|
35e1044f1a | ||
|
17f2c2aee0 | ||
|
6b0e946e41 | ||
|
e2bcae0c84 | ||
|
a03f9ca4e2 | ||
|
2a47541cc5 | ||
|
85f11a73c8 |
12
dist/torque.full.js
vendored
12
dist/torque.full.js
vendored
File diff suppressed because one or more lines are too long
215
dist/torque.full.uncompressed.js
vendored
215
dist/torque.full.uncompressed.js
vendored
@ -3181,7 +3181,19 @@ var Profiler = require('../profiler');
|
||||
// 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 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(
|
||||
@ -3208,15 +3220,29 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
|
||||
var dates = row.dates__uint16;
|
||||
var vals = row.vals__uint8;
|
||||
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]] = []);
|
||||
if(this.options.cumulative) {
|
||||
vals[j] += prev_val;
|
||||
}
|
||||
prev_val = vals[j];
|
||||
rr.push([r, vals[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 = {}
|
||||
@ -3245,6 +3271,7 @@ var Profiler = require('../profiler');
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for each timeslot search active buckets
|
||||
var renderDataIndex = 0;
|
||||
var timeSlotIndex = 0;
|
||||
@ -3254,10 +3281,13 @@ var Profiler = require('../profiler');
|
||||
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];
|
||||
rr[1].forEach(function(rrr,index){
|
||||
renderData[index][renderDataIndex] = rrr;
|
||||
})
|
||||
++renderDataIndex;
|
||||
}
|
||||
}
|
||||
@ -3346,7 +3376,6 @@ var Profiler = require('../profiler');
|
||||
subdomains = [null]; // no subdomain
|
||||
}
|
||||
|
||||
|
||||
var url;
|
||||
if (options.no_cdn) {
|
||||
url = this._host();
|
||||
@ -3363,6 +3392,7 @@ var Profiler = require('../profiler');
|
||||
},
|
||||
|
||||
getTileData: function(coord, zoom, callback) {
|
||||
|
||||
if(!this._ready) {
|
||||
this._tileQueue.push([coord, zoom, callback]);
|
||||
} else {
|
||||
@ -3394,6 +3424,14 @@ var Profiler = require('../profiler');
|
||||
|
||||
var column_conv = this.options.column;
|
||||
|
||||
var agg_columns;
|
||||
if(this.options.countby.indexOf(";") > 0){
|
||||
agg_columns = this.options.countby.split(";")
|
||||
}
|
||||
else{
|
||||
agg_columns = [this.options.countby]
|
||||
}
|
||||
|
||||
if(this.options.is_time) {
|
||||
column_conv = format("date_part('epoch', {column})", this.options);
|
||||
}
|
||||
@ -3406,8 +3444,13 @@ var Profiler = require('../profiler');
|
||||
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
|
||||
")," +
|
||||
"cte AS ( "+
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
|
||||
", {countby} c" +
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", "+col+" c"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", floor(({column_conv} - {start})/{step}) d" +
|
||||
" FROM ({_sql}) i, par p " +
|
||||
" WHERE i.the_geom_webmercator && p.ext " +
|
||||
@ -3415,14 +3458,20 @@ var Profiler = require('../profiler');
|
||||
") " +
|
||||
"" +
|
||||
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
|
||||
" array_agg(c) vals__uint8," +
|
||||
" array_agg(d) dates__uint16" +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", array_agg(c"+index+") vals__uint8_"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", array_agg(d) 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 GROUP BY x__uint8, y__uint8";
|
||||
|
||||
|
||||
|
||||
var query = format(sql, this.options, {
|
||||
zoom: zoom,
|
||||
x: coord.x,
|
||||
@ -3431,6 +3480,7 @@ var Profiler = require('../profiler');
|
||||
_sql: this.getSQL()
|
||||
});
|
||||
|
||||
|
||||
var self = this;
|
||||
this.sql(query, function (data) {
|
||||
if (data) {
|
||||
@ -3551,7 +3601,7 @@ var Profiler = require('../profiler');
|
||||
"layers": [{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss": "#layer {}",
|
||||
"sql": this.getSQL()
|
||||
}
|
||||
@ -4399,9 +4449,9 @@ var Profiler = require('../profiler');
|
||||
|
||||
},{"../":10,"../profiler":17}],22:[function(require,module,exports){
|
||||
var TAU = Math.PI*2;
|
||||
// min value to render a line.
|
||||
// min value to render a line.
|
||||
// it does not make sense to render a line of a width is not even visible
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var MAX_SPRITE_RADIUS = 255;
|
||||
|
||||
function renderPoint(ctx, st) {
|
||||
@ -4442,6 +4492,31 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
}
|
||||
|
||||
function renderVector(ctx, st){
|
||||
var angle = st['marker-angle']
|
||||
var color = st['marker-stroke']
|
||||
var mag = st['marker-mag']
|
||||
var max_mag = st['marker-max-mag'] ? st['marker-max-mag'] : 10
|
||||
var max_line_length = st['marker-width']
|
||||
|
||||
var scaled_mag = (mag/max_mag)*max_line_length
|
||||
|
||||
ctx.lineWidth = 2
|
||||
|
||||
// ctx.strokeStyle='green'
|
||||
// ctx.strokeRect(-max_line_length,-max_line_length,max_line_length*2,max_line_length*2)
|
||||
// ctx.translate(max_line_length/2.0, 0)
|
||||
ctx.rotate(angle)
|
||||
ctx.strokeStyle = color
|
||||
|
||||
ctx.moveTo(0,-max_line_length*2)
|
||||
ctx.lineTo(0,max_line_length*2)
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function renderRectangle(ctx, st) {
|
||||
ctx.fillStyle = st['marker-fill'];
|
||||
var pixel_size = st['marker-width'];
|
||||
@ -4473,6 +4548,20 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(ctx,st){
|
||||
var width = st['marker-width']
|
||||
|
||||
var text = st['text-name']
|
||||
var font = st['text-face-name'] ? st['text-face-name'] : 'Droid Sans Regular'
|
||||
var textSize = st['text-size'] ? st['text-size'] : '10px'
|
||||
var color = st['text-fill'] ? st['text-fill'] : 'white'
|
||||
|
||||
// ctx.font = textSize+"px "+font
|
||||
ctx.fillStyle = color
|
||||
ctx.font= textSize + " " + font
|
||||
ctx.fillText(text, -width/2.0, width/2.0 )
|
||||
}
|
||||
|
||||
function renderSprite(ctx, img, st) {
|
||||
|
||||
if(img.complete){
|
||||
@ -4487,6 +4576,8 @@ module.exports = {
|
||||
renderPoint: renderPoint,
|
||||
renderSprite: renderSprite,
|
||||
renderRectangle: renderRectangle,
|
||||
renderVector: renderVector,
|
||||
renderText: renderText,
|
||||
MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS
|
||||
};
|
||||
|
||||
@ -4557,7 +4648,7 @@ var Filters = require('./torque_filters');
|
||||
this.TILE_SIZE = 256;
|
||||
this._style = null;
|
||||
this._gradients = {};
|
||||
|
||||
|
||||
this._forcePoints = false;
|
||||
}
|
||||
|
||||
@ -4594,6 +4685,7 @@ var Filters = require('./torque_filters');
|
||||
|
||||
setShader: function(shader) {
|
||||
// clean sprites
|
||||
|
||||
this._sprites = [];
|
||||
this._shader = shader;
|
||||
this._Map = this._shader.getDefault().getStyle({}, { zoom: 0 });
|
||||
@ -4605,6 +4697,30 @@ var Filters = require('./torque_filters');
|
||||
this._sprites = [];
|
||||
},
|
||||
|
||||
processScaleFunction:function(input, opts){
|
||||
|
||||
if(typeof(input)!='string'){
|
||||
return input;
|
||||
}
|
||||
|
||||
var matches = input.match(/scale_(.*)\((.*)\)/)
|
||||
if(matches){
|
||||
var type = matches[1];
|
||||
var params = matches[2].split(",");
|
||||
|
||||
var variable = params[0]
|
||||
var domain = params.slice(1,3);
|
||||
var range = params.slice(3,params.length);
|
||||
|
||||
var scale = {lin: d3.scale.linear, log: d3.scale.log, quant: d3.scale.quantize, sqrt: d3.scale.sqrt}[type]
|
||||
|
||||
var s = scale().domain(domain).range(range)
|
||||
return s(opts[variable])
|
||||
}
|
||||
else{
|
||||
return input;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// generate sprite based on cartocss style
|
||||
@ -4612,9 +4728,31 @@ var Filters = require('./torque_filters');
|
||||
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 values = {}
|
||||
if(value.length ==1){
|
||||
values["value"] = value[0]
|
||||
}
|
||||
else{
|
||||
value.forEach(function(val,index){
|
||||
values["value"+index] = val;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var st = shader.getStyle(values, shaderVars);
|
||||
|
||||
var processingVars = {}
|
||||
for(var key in shaderVars){ processingVars[key]= shaderVars[key]}
|
||||
for(var key in values){processingVars[key] = values[key]}
|
||||
|
||||
var varsToProcess = ['marker-width', 'marker-fill-opacity', 'marker-fill', 'marker-stroke', 'marker-angle', 'marker-mag']
|
||||
varsToProcess.forEach(function(key){
|
||||
if(st[key]){
|
||||
st[key] = this.processScaleFunction(st[key], processingVars)
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
if(this._style === null || this._style !== st){
|
||||
this._style = st;
|
||||
}
|
||||
@ -4655,17 +4793,30 @@ var Filters = require('./torque_filters');
|
||||
var mt = st['marker-type'];
|
||||
if (mt && mt === 'rectangle') {
|
||||
cartocss.renderRectangle(ctx, st);
|
||||
} else {
|
||||
} else if(mt && mt === 'vector') {
|
||||
cartocss.renderVector(ctx,st);
|
||||
}
|
||||
else{
|
||||
cartocss.renderPoint(ctx, st);
|
||||
}
|
||||
}
|
||||
|
||||
if(st['text-name']){
|
||||
var captures = st['text-name'].match(/\{\{(.*)\}\}/)
|
||||
captures.slice(1,captures.length).forEach(function(rep){
|
||||
st['text-name'] = st['text-name'].replace("{{"+rep+"}}", values[rep])
|
||||
})
|
||||
// st['text-name'] = st['text-name'].replace("{{value}}",value)
|
||||
cartocss.renderText(ctx,st)
|
||||
}
|
||||
|
||||
prof.end(true);
|
||||
if (torque.flags.sprites_to_images) {
|
||||
var i = this._createImage();
|
||||
i.src = canvas.toDataURL();
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
@ -4693,7 +4844,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
prof.end(true);
|
||||
|
||||
return callback && callback(null);
|
||||
@ -4737,7 +4888,7 @@ 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) {
|
||||
@ -4760,7 +4911,7 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[key];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
var c = tile.renderData.map(function(a){return a[pixelIndex + p]});
|
||||
if (c) {
|
||||
var sp = sprites[c];
|
||||
if (sp === undefined) {
|
||||
@ -4774,7 +4925,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
prof.end(true);
|
||||
},
|
||||
@ -4826,7 +4977,9 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[step];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
|
||||
var c =tile.renderData.map(function(r){ return r[pixelIndex + p]})
|
||||
|
||||
if (c) {
|
||||
var x = tile.x[posIdx];
|
||||
var y = tileMax - tile.y[posIdx];
|
||||
@ -4925,7 +5078,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;
|
||||
@ -13932,7 +14085,7 @@ module.exports={
|
||||
"url": "https://github.com/cartodb/carto",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/cartodb/carto.git"
|
||||
"url": "git+ssh://git@github.com/cartodb/carto.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "CartoDB",
|
||||
@ -14003,7 +14156,7 @@ module.exports={
|
||||
"bugs": {
|
||||
"url": "https://github.com/cartodb/carto/issues"
|
||||
},
|
||||
"homepage": "https://github.com/cartodb/carto",
|
||||
"homepage": "https://github.com/cartodb/carto#readme",
|
||||
"_id": "carto@0.15.1-cdb1",
|
||||
"_shasum": "62534c2975cbee073f10c6c14a0c7e889c9469e7",
|
||||
"_resolved": "https://github.com/CartoDB/carto/archive/master.tar.gz",
|
||||
|
4
dist/torque.js
vendored
4
dist/torque.js
vendored
File diff suppressed because one or more lines are too long
211
dist/torque.uncompressed.js
vendored
211
dist/torque.uncompressed.js
vendored
@ -3188,7 +3188,19 @@ var Profiler = require('../profiler');
|
||||
// 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 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(
|
||||
@ -3215,15 +3227,29 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
|
||||
var dates = row.dates__uint16;
|
||||
var vals = row.vals__uint8;
|
||||
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]] = []);
|
||||
if(this.options.cumulative) {
|
||||
vals[j] += prev_val;
|
||||
}
|
||||
prev_val = vals[j];
|
||||
rr.push([r, vals[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 = {}
|
||||
@ -3252,6 +3278,7 @@ var Profiler = require('../profiler');
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for each timeslot search active buckets
|
||||
var renderDataIndex = 0;
|
||||
var timeSlotIndex = 0;
|
||||
@ -3261,10 +3288,13 @@ var Profiler = require('../profiler');
|
||||
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];
|
||||
rr[1].forEach(function(rrr,index){
|
||||
renderData[index][renderDataIndex] = rrr;
|
||||
})
|
||||
++renderDataIndex;
|
||||
}
|
||||
}
|
||||
@ -3353,7 +3383,6 @@ var Profiler = require('../profiler');
|
||||
subdomains = [null]; // no subdomain
|
||||
}
|
||||
|
||||
|
||||
var url;
|
||||
if (options.no_cdn) {
|
||||
url = this._host();
|
||||
@ -3370,6 +3399,7 @@ var Profiler = require('../profiler');
|
||||
},
|
||||
|
||||
getTileData: function(coord, zoom, callback) {
|
||||
|
||||
if(!this._ready) {
|
||||
this._tileQueue.push([coord, zoom, callback]);
|
||||
} else {
|
||||
@ -3401,6 +3431,14 @@ var Profiler = require('../profiler');
|
||||
|
||||
var column_conv = this.options.column;
|
||||
|
||||
var agg_columns;
|
||||
if(this.options.countby.indexOf(";") > 0){
|
||||
agg_columns = this.options.countby.split(";")
|
||||
}
|
||||
else{
|
||||
agg_columns = [this.options.countby]
|
||||
}
|
||||
|
||||
if(this.options.is_time) {
|
||||
column_conv = format("date_part('epoch', {column})", this.options);
|
||||
}
|
||||
@ -3413,8 +3451,13 @@ var Profiler = require('../profiler');
|
||||
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
|
||||
")," +
|
||||
"cte AS ( "+
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
|
||||
", {countby} c" +
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", "+col+" c"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", floor(({column_conv} - {start})/{step}) d" +
|
||||
" FROM ({_sql}) i, par p " +
|
||||
" WHERE i.the_geom_webmercator && p.ext " +
|
||||
@ -3422,14 +3465,20 @@ var Profiler = require('../profiler');
|
||||
") " +
|
||||
"" +
|
||||
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
|
||||
" array_agg(c) vals__uint8," +
|
||||
" array_agg(d) dates__uint16" +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", array_agg(c"+index+") vals__uint8_"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", array_agg(d) 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 GROUP BY x__uint8, y__uint8";
|
||||
|
||||
|
||||
|
||||
var query = format(sql, this.options, {
|
||||
zoom: zoom,
|
||||
x: coord.x,
|
||||
@ -3438,6 +3487,7 @@ var Profiler = require('../profiler');
|
||||
_sql: this.getSQL()
|
||||
});
|
||||
|
||||
|
||||
var self = this;
|
||||
this.sql(query, function (data) {
|
||||
if (data) {
|
||||
@ -3558,7 +3608,7 @@ var Profiler = require('../profiler');
|
||||
"layers": [{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss": "#layer {}",
|
||||
"sql": this.getSQL()
|
||||
}
|
||||
@ -4406,9 +4456,9 @@ var Profiler = require('../profiler');
|
||||
|
||||
},{"../":10,"../profiler":17}],22:[function(require,module,exports){
|
||||
var TAU = Math.PI*2;
|
||||
// min value to render a line.
|
||||
// min value to render a line.
|
||||
// it does not make sense to render a line of a width is not even visible
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var MAX_SPRITE_RADIUS = 255;
|
||||
|
||||
function renderPoint(ctx, st) {
|
||||
@ -4449,6 +4499,31 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
}
|
||||
|
||||
function renderVector(ctx, st){
|
||||
var angle = st['marker-angle']
|
||||
var color = st['marker-stroke']
|
||||
var mag = st['marker-mag']
|
||||
var max_mag = st['marker-max-mag'] ? st['marker-max-mag'] : 10
|
||||
var max_line_length = st['marker-width']
|
||||
|
||||
var scaled_mag = (mag/max_mag)*max_line_length
|
||||
|
||||
ctx.lineWidth = 2
|
||||
|
||||
// ctx.strokeStyle='green'
|
||||
// ctx.strokeRect(-max_line_length,-max_line_length,max_line_length*2,max_line_length*2)
|
||||
// ctx.translate(max_line_length/2.0, 0)
|
||||
ctx.rotate(angle)
|
||||
ctx.strokeStyle = color
|
||||
|
||||
ctx.moveTo(0,-max_line_length*2)
|
||||
ctx.lineTo(0,max_line_length*2)
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function renderRectangle(ctx, st) {
|
||||
ctx.fillStyle = st['marker-fill'];
|
||||
var pixel_size = st['marker-width'];
|
||||
@ -4480,6 +4555,20 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(ctx,st){
|
||||
var width = st['marker-width']
|
||||
|
||||
var text = st['text-name']
|
||||
var font = st['text-face-name'] ? st['text-face-name'] : 'Droid Sans Regular'
|
||||
var textSize = st['text-size'] ? st['text-size'] : '10px'
|
||||
var color = st['text-fill'] ? st['text-fill'] : 'white'
|
||||
|
||||
// ctx.font = textSize+"px "+font
|
||||
ctx.fillStyle = color
|
||||
ctx.font= textSize + " " + font
|
||||
ctx.fillText(text, -width/2.0, width/2.0 )
|
||||
}
|
||||
|
||||
function renderSprite(ctx, img, st) {
|
||||
|
||||
if(img.complete){
|
||||
@ -4494,6 +4583,8 @@ module.exports = {
|
||||
renderPoint: renderPoint,
|
||||
renderSprite: renderSprite,
|
||||
renderRectangle: renderRectangle,
|
||||
renderVector: renderVector,
|
||||
renderText: renderText,
|
||||
MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS
|
||||
};
|
||||
|
||||
@ -4564,7 +4655,7 @@ var Filters = require('./torque_filters');
|
||||
this.TILE_SIZE = 256;
|
||||
this._style = null;
|
||||
this._gradients = {};
|
||||
|
||||
|
||||
this._forcePoints = false;
|
||||
}
|
||||
|
||||
@ -4601,6 +4692,7 @@ var Filters = require('./torque_filters');
|
||||
|
||||
setShader: function(shader) {
|
||||
// clean sprites
|
||||
|
||||
this._sprites = [];
|
||||
this._shader = shader;
|
||||
this._Map = this._shader.getDefault().getStyle({}, { zoom: 0 });
|
||||
@ -4612,6 +4704,30 @@ var Filters = require('./torque_filters');
|
||||
this._sprites = [];
|
||||
},
|
||||
|
||||
processScaleFunction:function(input, opts){
|
||||
|
||||
if(typeof(input)!='string'){
|
||||
return input;
|
||||
}
|
||||
|
||||
var matches = input.match(/scale_(.*)\((.*)\)/)
|
||||
if(matches){
|
||||
var type = matches[1];
|
||||
var params = matches[2].split(",");
|
||||
|
||||
var variable = params[0]
|
||||
var domain = params.slice(1,3);
|
||||
var range = params.slice(3,params.length);
|
||||
|
||||
var scale = {lin: d3.scale.linear, log: d3.scale.log, quant: d3.scale.quantize, sqrt: d3.scale.sqrt}[type]
|
||||
|
||||
var s = scale().domain(domain).range(range)
|
||||
return s(opts[variable])
|
||||
}
|
||||
else{
|
||||
return input;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// generate sprite based on cartocss style
|
||||
@ -4619,9 +4735,31 @@ var Filters = require('./torque_filters');
|
||||
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 values = {}
|
||||
if(value.length ==1){
|
||||
values["value"] = value[0]
|
||||
}
|
||||
else{
|
||||
value.forEach(function(val,index){
|
||||
values["value"+index] = val;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var st = shader.getStyle(values, shaderVars);
|
||||
|
||||
var processingVars = {}
|
||||
for(var key in shaderVars){ processingVars[key]= shaderVars[key]}
|
||||
for(var key in values){processingVars[key] = values[key]}
|
||||
|
||||
var varsToProcess = ['marker-width', 'marker-fill-opacity', 'marker-fill', 'marker-stroke', 'marker-angle', 'marker-mag']
|
||||
varsToProcess.forEach(function(key){
|
||||
if(st[key]){
|
||||
st[key] = this.processScaleFunction(st[key], processingVars)
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
if(this._style === null || this._style !== st){
|
||||
this._style = st;
|
||||
}
|
||||
@ -4662,17 +4800,30 @@ var Filters = require('./torque_filters');
|
||||
var mt = st['marker-type'];
|
||||
if (mt && mt === 'rectangle') {
|
||||
cartocss.renderRectangle(ctx, st);
|
||||
} else {
|
||||
} else if(mt && mt === 'vector') {
|
||||
cartocss.renderVector(ctx,st);
|
||||
}
|
||||
else{
|
||||
cartocss.renderPoint(ctx, st);
|
||||
}
|
||||
}
|
||||
|
||||
if(st['text-name']){
|
||||
var captures = st['text-name'].match(/\{\{(.*)\}\}/)
|
||||
captures.slice(1,captures.length).forEach(function(rep){
|
||||
st['text-name'] = st['text-name'].replace("{{"+rep+"}}", values[rep])
|
||||
})
|
||||
// st['text-name'] = st['text-name'].replace("{{value}}",value)
|
||||
cartocss.renderText(ctx,st)
|
||||
}
|
||||
|
||||
prof.end(true);
|
||||
if (torque.flags.sprites_to_images) {
|
||||
var i = this._createImage();
|
||||
i.src = canvas.toDataURL();
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
return canvas;
|
||||
},
|
||||
|
||||
@ -4700,7 +4851,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
prof.end(true);
|
||||
|
||||
return callback && callback(null);
|
||||
@ -4744,7 +4895,7 @@ 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) {
|
||||
@ -4767,7 +4918,7 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[key];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
var c = tile.renderData.map(function(a){return a[pixelIndex + p]});
|
||||
if (c) {
|
||||
var sp = sprites[c];
|
||||
if (sp === undefined) {
|
||||
@ -4781,7 +4932,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
prof.end(true);
|
||||
},
|
||||
@ -4833,7 +4984,9 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[step];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
|
||||
var c =tile.renderData.map(function(r){ return r[pixelIndex + p]})
|
||||
|
||||
if (c) {
|
||||
var x = tile.x[posIdx];
|
||||
var y = tileMax - tile.y[posIdx];
|
||||
@ -4932,7 +5085,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;
|
||||
|
80
examples/leaflet_with_scaling_functions.html
Normal file
80
examples/leaflet_with_scaling_functions.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>
|
||||
|
||||
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
|
||||
|
||||
<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: "time";',
|
||||
'-torque-aggregation-function: "avg(abs(depth));avg(mag)";',
|
||||
'-torque-frame-count: 200;',
|
||||
'-torque-animation-duration: 20;',
|
||||
'-torque-resolution: 1',
|
||||
'}',
|
||||
'#all_month_3 {',
|
||||
' marker-width: "scale_lin(value0,0.0,10,0,5)";',
|
||||
' marker-fill-opacity: "scale_sqrt(frame-offset,0,9,1,0)";',
|
||||
' marker-type: ellipse;',
|
||||
' marker-fill: "scale_lin(value1,0.1,5,blue,red)";',
|
||||
'}',
|
||||
'#all_month_3[frame-offset=1]{}',
|
||||
'#all_month_3[frame-offset=2]{}',
|
||||
'#all_month_3[frame-offset=3]{}',
|
||||
'#all_month_3[frame-offset=4]{}',
|
||||
'#all_month_3[frame-offset=5]{}',
|
||||
'#all_month_3[frame-offset=6]{}',
|
||||
'#all_month_3[frame-offset=7]{}',
|
||||
'#all_month_3[frame-offset=8]{}',
|
||||
'#all_month_3[frame-offset=9]{}',
|
||||
].join('\n');
|
||||
|
||||
|
||||
var map = new L.Map('map', {
|
||||
zoomControl: true,
|
||||
center: [40, 0],
|
||||
zoom: 3
|
||||
});
|
||||
|
||||
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
|
||||
attribution: 'CartoDB'
|
||||
}).addTo(map);
|
||||
|
||||
var torqueLayer = new L.TorqueLayer({
|
||||
user : 'eschbacher',
|
||||
table : 'all_month_3',
|
||||
provider : 'sql_api',
|
||||
cartocss: CARTOCSS
|
||||
});
|
||||
torqueLayer.addTo(map);
|
||||
torqueLayer.play();
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
152
examples/multi_variable_test.html
Normal file
152
examples/multi_variable_test.html
Normal file
@ -0,0 +1,152 @@
|
||||
|
||||
<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>
|
||||
|
||||
<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: "time";',
|
||||
'-torque-aggregation-function: "avg(mag*10);avg(depth)";',
|
||||
'-torque-frame-count: 256;',
|
||||
'-torque-animation-duration: 30;',
|
||||
'-torque-resolution: 1',
|
||||
'}',
|
||||
'#all_day {',
|
||||
' marker-width: 4;',
|
||||
' marker-fill-opacity: 0.7;',
|
||||
' marker-type: ellipse;',
|
||||
'marker-fill: #FFFFB2;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 250] {',
|
||||
' marker-width: 15.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 50.15] {',
|
||||
' marker-width: 14.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 18.5095] {',
|
||||
' marker-width: 13.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 14.5909] {',
|
||||
' marker-width: 12.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 11.785] {',
|
||||
' marker-width: 11.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 9.30425] {',
|
||||
' marker-width: 10.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 7.10635] {',
|
||||
' marker-width: 9.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 5.4714] {',
|
||||
' marker-width: 8.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 2.825] {',
|
||||
' marker-width: 7.0;',
|
||||
'}',
|
||||
'#all_day [ value1 <= 1.7035] {',
|
||||
' marker-width: 6.0;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 80.2] {',
|
||||
'marker-fill: #B10026;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 60.2] {',
|
||||
'marker-fill: #E31A1C;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 40.58] {',
|
||||
'marker-fill: #FC4E2A;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 30.92] {',
|
||||
'marker-fill: #FD8D3C;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 30.6] {',
|
||||
'marker-fill: #FEB24C;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 30.35] {',
|
||||
'marker-fill: #FED976;',
|
||||
'}',
|
||||
'#all_day [ value0 <= 3.16] {',
|
||||
'marker-fill: #FFFFB2;',
|
||||
'}'
|
||||
|
||||
].join('\n');
|
||||
|
||||
|
||||
var map = new L.Map('map', {
|
||||
zoomControl: true,
|
||||
center: [40, 0],
|
||||
zoom: 3
|
||||
});
|
||||
|
||||
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
|
||||
attribution: 'CartoDB'
|
||||
}).addTo(map);
|
||||
|
||||
var torqueLayer = new L.TorqueLayer({
|
||||
user : 'stuartlynn',
|
||||
table : 'quakes_2014',
|
||||
provider : 'sql_api',
|
||||
cartocss: CARTOCSS
|
||||
});
|
||||
torqueLayer.addTo(map);
|
||||
torqueLayer.play();
|
||||
|
||||
map.on('click', function(e) {
|
||||
var p = e.containerPoint
|
||||
var value = torqueLayer.getValueForPos(p.x, p.y);
|
||||
if (value !== null) {
|
||||
map.openPopup('average temperature: ' + value.value + "C", e.latlng);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// show small rectable and change cursor on hover
|
||||
var hover = null;
|
||||
map.on('mousemove', function(e) {
|
||||
var p = e.containerPoint
|
||||
var value = torqueLayer.getValueForPos(p.x, p.y);
|
||||
|
||||
// remove previous hover box
|
||||
if (hover) {
|
||||
map.removeLayer(hover);
|
||||
hover = null;
|
||||
}
|
||||
|
||||
if (value !== null) {
|
||||
hover = L.rectangle(value.bbox, {
|
||||
color: '#000',
|
||||
weight: 1
|
||||
}).addTo(map);
|
||||
map._container.style.cursor = 'pointer';
|
||||
} else {
|
||||
map._container.style.cursor = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
113
examples/text_render_example.html
Normal file
113
examples/text_render_example.html
Normal file
@ -0,0 +1,113 @@
|
||||
|
||||
<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>
|
||||
|
||||
<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: "date";',
|
||||
'-torque-aggregation-function: "avg(temp::float)";',
|
||||
'-torque-frame-count: 1;',
|
||||
'-torque-animation-duration: 15;',
|
||||
'-torque-resolution: 32',
|
||||
|
||||
'}',
|
||||
'#layer {',
|
||||
' marker-width: 16;',
|
||||
' marker-fill-opacity: 1.0;',
|
||||
' marker-fill: #fff5eb; ',
|
||||
' marker-type: rectangle;',
|
||||
'text-name: "{{value}}c";',
|
||||
'text-face-name: "Arial";',
|
||||
'text-fill: #036;',
|
||||
'text-size: 100px;',
|
||||
' [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, 0],
|
||||
zoom: 3
|
||||
});
|
||||
|
||||
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
|
||||
attribution: 'CartoDB'
|
||||
}).addTo(map);
|
||||
|
||||
var torqueLayer = new L.TorqueLayer({
|
||||
user : 'viz2',
|
||||
table : 'ow',
|
||||
provider : 'sql_api',
|
||||
cartocss: CARTOCSS
|
||||
});
|
||||
torqueLayer.addTo(map);
|
||||
|
||||
map.on('click', function(e) {
|
||||
var p = e.containerPoint
|
||||
var value = torqueLayer.getValueForPos(p.x, p.y);
|
||||
if (value !== null) {
|
||||
map.openPopup('average temperature: ' + value.value + "C", e.latlng);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// show small rectable and change cursor on hover
|
||||
var hover = null;
|
||||
map.on('mousemove', function(e) {
|
||||
var p = e.containerPoint
|
||||
var value = torqueLayer.getValueForPos(p.x, p.y);
|
||||
|
||||
// remove previous hover box
|
||||
if (hover) {
|
||||
map.removeLayer(hover);
|
||||
hover = null;
|
||||
}
|
||||
|
||||
if (value !== null) {
|
||||
hover = L.rectangle(value.bbox, {
|
||||
color: '#000',
|
||||
weight: 1
|
||||
}).addTo(map);
|
||||
map._container.style.cursor = 'pointer';
|
||||
} else {
|
||||
map._container.style.cursor = 'auto';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
78
examples/vector_test.html
Normal file
78
examples/vector_test.html
Normal file
@ -0,0 +1,78 @@
|
||||
|
||||
<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">Example wind map using torque vector</div>
|
||||
|
||||
<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="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></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-frame-count:1;',
|
||||
' -torque-animation-duration:1;',
|
||||
' -torque-time-attribute:"date";',
|
||||
' -torque-aggregation-function: "avg(angle*40);avg(mag)";',
|
||||
' -torque-resolution:4;',
|
||||
' -torque-data-aggregation:linear;',
|
||||
'}',
|
||||
'#wind {',
|
||||
' marker-width: 5;',
|
||||
' marker-fill-opacity: 1.0;',
|
||||
' marker-max-mag: 7; ',
|
||||
' marker-type: vector;',
|
||||
' marker-mag: "scale_log(value1, 0,61.76,0,7)";',
|
||||
' marker-stroke : "scale_log(value1,0.1,61.76,#FFFFB2,#B10026)";',
|
||||
' marker-angle : "scale_lin(value0,0,255,0,6.283185)";',
|
||||
'}'
|
||||
|
||||
]
|
||||
|
||||
CARTOCSS = CARTOCSS.join("\n")
|
||||
var map = new L.Map('map', {
|
||||
zoomControl: true,
|
||||
center: [40, 0],
|
||||
zoom: 3
|
||||
});
|
||||
|
||||
L.tileLayer('http://{s}.api.cartocdn.com/base-dark/{z}/{x}/{y}.png', {
|
||||
attribution: 'CartoDB'
|
||||
}).addTo(map);
|
||||
|
||||
var torqueLayer = new L.TorqueLayer({
|
||||
user : 'stuartlynn',
|
||||
table : 'wind',
|
||||
provider : 'sql_api',
|
||||
cartocss: CARTOCSS
|
||||
});
|
||||
|
||||
torqueLayer.addTo(map);
|
||||
torqueLayer.play();
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -80,7 +80,19 @@ var Profiler = require('../profiler');
|
||||
// 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 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(
|
||||
@ -107,15 +119,29 @@ var Profiler = require('../profiler');
|
||||
}
|
||||
|
||||
var dates = row.dates__uint16;
|
||||
var vals = row.vals__uint8;
|
||||
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]] = []);
|
||||
if(this.options.cumulative) {
|
||||
vals[j] += prev_val;
|
||||
}
|
||||
prev_val = vals[j];
|
||||
rr.push([r, vals[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 = {}
|
||||
@ -144,6 +170,7 @@ var Profiler = require('../profiler');
|
||||
|
||||
}
|
||||
|
||||
|
||||
// for each timeslot search active buckets
|
||||
var renderDataIndex = 0;
|
||||
var timeSlotIndex = 0;
|
||||
@ -153,10 +180,13 @@ var Profiler = require('../profiler');
|
||||
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];
|
||||
rr[1].forEach(function(rrr,index){
|
||||
renderData[index][renderDataIndex] = rrr;
|
||||
})
|
||||
++renderDataIndex;
|
||||
}
|
||||
}
|
||||
@ -245,7 +275,6 @@ var Profiler = require('../profiler');
|
||||
subdomains = [null]; // no subdomain
|
||||
}
|
||||
|
||||
|
||||
var url;
|
||||
if (options.no_cdn) {
|
||||
url = this._host();
|
||||
@ -262,6 +291,7 @@ var Profiler = require('../profiler');
|
||||
},
|
||||
|
||||
getTileData: function(coord, zoom, callback) {
|
||||
|
||||
if(!this._ready) {
|
||||
this._tileQueue.push([coord, zoom, callback]);
|
||||
} else {
|
||||
@ -293,6 +323,14 @@ var Profiler = require('../profiler');
|
||||
|
||||
var column_conv = this.options.column;
|
||||
|
||||
var agg_columns;
|
||||
if(this.options.countby.indexOf(";") > 0){
|
||||
agg_columns = this.options.countby.split(";")
|
||||
}
|
||||
else{
|
||||
agg_columns = [this.options.countby]
|
||||
}
|
||||
|
||||
if(this.options.is_time) {
|
||||
column_conv = format("date_part('epoch', {column})", this.options);
|
||||
}
|
||||
@ -305,8 +343,13 @@ var Profiler = require('../profiler');
|
||||
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
|
||||
")," +
|
||||
"cte AS ( "+
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
|
||||
", {countby} c" +
|
||||
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", "+col+" c"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", floor(({column_conv} - {start})/{step}) d" +
|
||||
" FROM ({_sql}) i, par p " +
|
||||
" WHERE i.the_geom_webmercator && p.ext " +
|
||||
@ -314,14 +357,20 @@ var Profiler = require('../profiler');
|
||||
") " +
|
||||
"" +
|
||||
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
|
||||
" array_agg(c) vals__uint8," +
|
||||
" array_agg(d) dates__uint16" +
|
||||
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8"
|
||||
|
||||
agg_columns.forEach(function(col,index){
|
||||
sql = sql + ", array_agg(c"+index+") vals__uint8_"+index
|
||||
}.bind(this))
|
||||
|
||||
sql = sql +
|
||||
", array_agg(d) 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 GROUP BY x__uint8, y__uint8";
|
||||
|
||||
|
||||
|
||||
var query = format(sql, this.options, {
|
||||
zoom: zoom,
|
||||
x: coord.x,
|
||||
@ -330,6 +379,7 @@ var Profiler = require('../profiler');
|
||||
_sql: this.getSQL()
|
||||
});
|
||||
|
||||
|
||||
var self = this;
|
||||
this.sql(query, function (data) {
|
||||
if (data) {
|
||||
@ -450,7 +500,7 @@ var Profiler = require('../profiler');
|
||||
"layers": [{
|
||||
"type": "cartodb",
|
||||
"options": {
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss_version": "2.1.1",
|
||||
"cartocss": "#layer {}",
|
||||
"sql": this.getSQL()
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
var TAU = Math.PI*2;
|
||||
// min value to render a line.
|
||||
// min value to render a line.
|
||||
// it does not make sense to render a line of a width is not even visible
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var LINEWIDTH_MIN_VALUE = 0.05;
|
||||
var MAX_SPRITE_RADIUS = 255;
|
||||
|
||||
function renderPoint(ctx, st) {
|
||||
@ -42,6 +42,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderVector(ctx, st){
|
||||
var angle = st['marker-angle']
|
||||
var color = st['marker-stroke']
|
||||
var mag = st['marker-mag']
|
||||
var max_mag = st['marker-max-mag'] ? st['marker-max-mag'] : 10
|
||||
var max_line_length = st['marker-width']
|
||||
|
||||
var scaled_mag = (mag/max_mag)*max_line_length
|
||||
|
||||
ctx.lineWidth = 2
|
||||
|
||||
// ctx.strokeStyle='green'
|
||||
// ctx.strokeRect(-max_line_length,-max_line_length,max_line_length*2,max_line_length*2)
|
||||
// ctx.translate(max_line_length/2.0, 0)
|
||||
ctx.rotate(angle)
|
||||
ctx.strokeStyle = color
|
||||
|
||||
ctx.moveTo(0,-max_line_length*2)
|
||||
ctx.lineTo(0,max_line_length*2)
|
||||
ctx.stroke();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function renderRectangle(ctx, st) {
|
||||
ctx.fillStyle = st['marker-fill'];
|
||||
var pixel_size = st['marker-width'];
|
||||
@ -73,6 +98,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
function renderText(ctx,st){
|
||||
var width = st['marker-width']
|
||||
|
||||
var text = st['text-name']
|
||||
var font = st['text-face-name'] ? st['text-face-name'] : 'Droid Sans Regular'
|
||||
var textSize = st['text-size'] ? st['text-size'] : '10px'
|
||||
var color = st['text-fill'] ? st['text-fill'] : 'white'
|
||||
|
||||
// ctx.font = textSize+"px "+font
|
||||
ctx.fillStyle = color
|
||||
ctx.font= textSize + " " + font
|
||||
ctx.fillText(text, -width/2.0, width/2.0 )
|
||||
}
|
||||
|
||||
function renderSprite(ctx, img, st) {
|
||||
|
||||
if(img.complete){
|
||||
@ -87,5 +126,7 @@ module.exports = {
|
||||
renderPoint: renderPoint,
|
||||
renderSprite: renderSprite,
|
||||
renderRectangle: renderRectangle,
|
||||
renderVector: renderVector,
|
||||
renderText: renderText,
|
||||
MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS
|
||||
};
|
||||
|
@ -57,7 +57,7 @@ var Filters = require('./torque_filters');
|
||||
this.TILE_SIZE = 256;
|
||||
this._style = null;
|
||||
this._gradients = {};
|
||||
|
||||
|
||||
this._forcePoints = false;
|
||||
}
|
||||
|
||||
@ -94,6 +94,7 @@ var Filters = require('./torque_filters');
|
||||
|
||||
setShader: function(shader) {
|
||||
// clean sprites
|
||||
|
||||
this._sprites = [];
|
||||
this._shader = shader;
|
||||
this._Map = this._shader.getDefault().getStyle({}, { zoom: 0 });
|
||||
@ -105,6 +106,30 @@ var Filters = require('./torque_filters');
|
||||
this._sprites = [];
|
||||
},
|
||||
|
||||
processScaleFunction:function(input, opts){
|
||||
|
||||
if(typeof(input)!='string'){
|
||||
return input;
|
||||
}
|
||||
|
||||
var matches = input.match(/scale_(.*)\((.*)\)/)
|
||||
if(matches){
|
||||
var type = matches[1];
|
||||
var params = matches[2].split(",");
|
||||
|
||||
var variable = params[0]
|
||||
var domain = params.slice(1,3);
|
||||
var range = params.slice(3,params.length);
|
||||
|
||||
var scale = {lin: d3.scale.linear, log: d3.scale.log, quant: d3.scale.quantize, sqrt: d3.scale.sqrt}[type]
|
||||
|
||||
var s = scale().domain(domain).range(range)
|
||||
return s(opts[variable])
|
||||
}
|
||||
else{
|
||||
return input;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// generate sprite based on cartocss style
|
||||
@ -112,9 +137,31 @@ var Filters = require('./torque_filters');
|
||||
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 values = {}
|
||||
if(value.length ==1){
|
||||
values["value"] = value[0]
|
||||
}
|
||||
else{
|
||||
value.forEach(function(val,index){
|
||||
values["value"+index] = val;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var st = shader.getStyle(values, shaderVars);
|
||||
|
||||
var processingVars = {}
|
||||
for(var key in shaderVars){ processingVars[key]= shaderVars[key]}
|
||||
for(var key in values){processingVars[key] = values[key]}
|
||||
|
||||
var varsToProcess = ['marker-width', 'marker-fill-opacity', 'marker-fill', 'marker-stroke', 'marker-angle', 'marker-mag']
|
||||
varsToProcess.forEach(function(key){
|
||||
if(st[key]){
|
||||
st[key] = this.processScaleFunction(st[key], processingVars)
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
if(this._style === null || this._style !== st){
|
||||
this._style = st;
|
||||
}
|
||||
@ -155,17 +202,30 @@ var Filters = require('./torque_filters');
|
||||
var mt = st['marker-type'];
|
||||
if (mt && mt === 'rectangle') {
|
||||
cartocss.renderRectangle(ctx, st);
|
||||
} else {
|
||||
} else if(mt && mt === 'vector') {
|
||||
cartocss.renderVector(ctx,st);
|
||||
}
|
||||
else{
|
||||
cartocss.renderPoint(ctx, st);
|
||||
}
|
||||
}
|
||||
|
||||
if(st['text-name']){
|
||||
var captures = st['text-name'].match(/\{\{(.*)\}\}/)
|
||||
captures.slice(1,captures.length).forEach(function(rep){
|
||||
st['text-name'] = st['text-name'].replace("{{"+rep+"}}", values[rep])
|
||||
})
|
||||
// st['text-name'] = st['text-name'].replace("{{value}}",value)
|
||||
cartocss.renderText(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 +253,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
prof.end(true);
|
||||
|
||||
return callback && callback(null);
|
||||
@ -237,7 +297,7 @@ 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) {
|
||||
@ -260,7 +320,7 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[key];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
var c = tile.renderData.map(function(a){return a[pixelIndex + p]});
|
||||
if (c) {
|
||||
var sp = sprites[c];
|
||||
if (sp === undefined) {
|
||||
@ -274,7 +334,7 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
prof.end(true);
|
||||
},
|
||||
@ -326,7 +386,9 @@ var Filters = require('./torque_filters');
|
||||
var pixelIndex = tile.timeIndex[step];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
|
||||
var c =tile.renderData.map(function(r){ return r[pixelIndex + p]})
|
||||
|
||||
if (c) {
|
||||
var x = tile.x[posIdx];
|
||||
var y = tileMax - tile.y[posIdx];
|
||||
@ -425,7 +487,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;
|
||||
|
Loading…
Reference in New Issue
Block a user