torque/dist/torque.uncompressed.js
2013-09-30 16:56:29 +02:00

3089 lines
84 KiB
JavaScript

(function(exports) {
exports.torque = exports.torque || {};
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(c) { setTimeout(c, 16); }
var cancelAnimationFrame = window.requestAnimationFrame || window.mozCancelAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(c) { cancelTimeout(c); };
/**
* options:
* animationDuration in seconds
* animationDelay in seconds
*/
function Animator(callback, options) {
if(!options.steps) {
throw new Error("steps option missing")
}
this.options = options;
this.running = false;
this._tick = this._tick.bind(this);
this._t0 = +new Date();
this.callback = callback;
this._time = 0.0;
_.defaults(this.options, {
animationDelay: 0,
maxDelta: 0.2,
loop: true
});
this.domainInv = torque.math.linear(this.options.animationDelay, this.options.animationDelay + this.options.animationDuration);
this.domain = this.domainInv.invert();
this.range = torque.math.linear(0, this.options.steps);
this.rangeInv = this.range.invert();
}
Animator.prototype = {
start: function() {
this.running = true;
requestAnimationFrame(this._tick);
},
stop: function() {
this.pause();
this._time = 0;
var t = this.range(this.domain(this._time));
this.callback(t);
},
toggle: function() {
if (this.running) {
this.pause()
} else {
this.start()
}
},
step: function(s) {
if(arguments.length === 0) return this.range(this.domain(this._time));
this._time = this.domainInv(this.rangeInv(s));
},
pause: function() {
this.running = false;
cancelAnimationFrame(this._tick);
},
_tick: function() {
var t1 = +new Date();
var delta = (t1 - this._t0)*0.001;
// if delta is really big means the tab lost the focus
// at some point, so limit delta change
delta = Math.min(this.options.maxDelta, delta);
this._t0 = t1;
this._time += delta;
var t = this.range(this.domain(this._time));
this.callback(t);
if(t >= this.options.steps) {
this._time = 0;
}
if(this.running) {
requestAnimationFrame(this._tick);
}
}
};
exports.torque.Animator = Animator;
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
var _torque_reference_latest = {
"version": "1.0.0",
"style": {
"comp-op": {
"css": "comp-op",
"default-value": "src-over",
"default-meaning": "add the current layer on top of other layers",
"doc": "Composite operation. This defines how this layer should behave relative to layers atop or below it.",
"type": [
"src", //
"src-over", //
"dst-over", //
"src-in", //
"dst-in", //
"src-out", //
"dst-out", //
"src-atop", //
"dst-atop", //
"xor", //
"darken", //
"lighten" //
]
}
},
"symbolizers" : {
"*": {
"comp-op": {
"css": "comp-op",
"default-value": "src-over",
"default-meaning": "add the current layer on top of other layers",
"doc": "Composite operation. This defines how this layer should behave relative to layers atop or below it.",
"type": [
"src", //
"src-over", //
"dst-over", //
"src-in", //
"dst-in", //
"src-out", //
"dst-out", //
"src-atop", //
"dst-atop", //
"xor", //
"darken", //
"lighten" //
]
},
"opacity": {
"css": "opacity",
"type": "float",
"doc": "An alpha value for the style (which means an alpha applied to all features in separate buffer and then composited back to main buffer)",
"default-value": 1,
"default-meaning": "no separate buffer will be used and no alpha will be applied to the style after rendering"
}
},
"trail": {
"steps": {
"css": "trail-steps",
"type": "float",
"default-value": 1,
"default-meaning": "no trail steps",
"doc": "How many steps of trails are going to be rendered"
}
},
"polygon": {
"fill": {
"css": "polygon-fill",
"type": "color",
"default-value": "rgba(128,128,128,1)",
"default-meaning": "gray and fully opaque (alpha = 1), same as rgb(128,128,128)",
"doc": "Fill color to assign to a polygon"
},
"fill-opacity": {
"css": "polygon-opacity",
"type": "float",
"doc": "The opacity of the polygon",
"default-value": 1,
"default-meaning": "opaque"
}
},
"line": {
"stroke": {
"css": "line-color",
"default-value": "rgba(0,0,0,1)",
"type": "color",
"default-meaning": "black and fully opaque (alpha = 1), same as rgb(0,0,0)",
"doc": "The color of a drawn line"
},
"stroke-width": {
"css": "line-width",
"default-value": 1,
"type": "float",
"doc": "The width of a line in pixels"
},
"stroke-opacity": {
"css": "line-opacity",
"default-value": 1,
"type": "float",
"default-meaning": "opaque",
"doc": "The opacity of a line"
},
"stroke-linejoin": {
"css": "line-join",
"default-value": "miter",
"type": [
"miter",
"round",
"bevel"
],
"doc": "The behavior of lines when joining"
},
"stroke-linecap": {
"css": "line-cap",
"default-value": "butt",
"type": [
"butt",
"round",
"square"
],
"doc": "The display of line endings"
}
},
"markers": {
"file": {
"css": "marker-file",
"doc": "An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse.",
"default-value": "",
"default-meaning": "An ellipse or circle, if width equals height",
"type": "uri"
},
"opacity": {
"css": "marker-opacity",
"doc": "The overall opacity of the marker, if set, overrides both the opacity of both the fill and stroke",
"default-value": 1,
"default-meaning": "The stroke-opacity and fill-opacity will be used",
"type": "float"
},
"fill-opacity": {
"css": "marker-fill-opacity",
"doc": "The fill opacity of the marker",
"default-value": 1,
"default-meaning": "opaque",
"type": "float"
},
"stroke": {
"css": "marker-line-color",
"doc": "The color of the stroke around a marker shape.",
"default-value": "black",
"type": "color"
},
"stroke-width": {
"css": "marker-line-width",
"doc": "The width of the stroke around a marker shape, in pixels. This is positioned on the boundary, so high values can cover the area itself.",
"type": "float"
},
"stroke-opacity": {
"css": "marker-line-opacity",
"default-value": 1,
"default-meaning": "opaque",
"doc": "The opacity of a line",
"type": "float"
},
"fill": {
"css": "marker-fill",
"default-value": "blue",
"doc": "The color of the area of the marker.",
"type": "color"
}
},
"point": {
"file": {
"css": "point-file",
"type": "uri",
"required": false,
"default-value": "none",
"doc": "Image file to represent a point"
},
"opacity": {
"css": "point-opacity",
"type": "float",
"default-value": 1.0,
"default-meaning": "Fully opaque",
"doc": "A value from 0 to 1 to control the opacity of the point"
}
}
},
"colors": {
"aliceblue": [240, 248, 255],
"antiquewhite": [250, 235, 215],
"aqua": [0, 255, 255],
"aquamarine": [127, 255, 212],
"azure": [240, 255, 255],
"beige": [245, 245, 220],
"bisque": [255, 228, 196],
"black": [0, 0, 0],
"blanchedalmond": [255,235,205],
"blue": [0, 0, 255],
"blueviolet": [138, 43, 226],
"brown": [165, 42, 42],
"burlywood": [222, 184, 135],
"cadetblue": [95, 158, 160],
"chartreuse": [127, 255, 0],
"chocolate": [210, 105, 30],
"coral": [255, 127, 80],
"cornflowerblue": [100, 149, 237],
"cornsilk": [255, 248, 220],
"crimson": [220, 20, 60],
"cyan": [0, 255, 255],
"darkblue": [0, 0, 139],
"darkcyan": [0, 139, 139],
"darkgoldenrod": [184, 134, 11],
"darkgray": [169, 169, 169],
"darkgreen": [0, 100, 0],
"darkgrey": [169, 169, 169],
"darkkhaki": [189, 183, 107],
"darkmagenta": [139, 0, 139],
"darkolivegreen": [85, 107, 47],
"darkorange": [255, 140, 0],
"darkorchid": [153, 50, 204],
"darkred": [139, 0, 0],
"darksalmon": [233, 150, 122],
"darkseagreen": [143, 188, 143],
"darkslateblue": [72, 61, 139],
"darkslategrey": [47, 79, 79],
"darkturquoise": [0, 206, 209],
"darkviolet": [148, 0, 211],
"deeppink": [255, 20, 147],
"deepskyblue": [0, 191, 255],
"dimgray": [105, 105, 105],
"dimgrey": [105, 105, 105],
"dodgerblue": [30, 144, 255],
"firebrick": [178, 34, 34],
"floralwhite": [255, 250, 240],
"forestgreen": [34, 139, 34],
"fuchsia": [255, 0, 255],
"gainsboro": [220, 220, 220],
"ghostwhite": [248, 248, 255],
"gold": [255, 215, 0],
"goldenrod": [218, 165, 32],
"gray": [128, 128, 128],
"grey": [128, 128, 128],
"green": [0, 128, 0],
"greenyellow": [173, 255, 47],
"honeydew": [240, 255, 240],
"hotpink": [255, 105, 180],
"indianred": [205, 92, 92],
"indigo": [75, 0, 130],
"ivory": [255, 255, 240],
"khaki": [240, 230, 140],
"lavender": [230, 230, 250],
"lavenderblush": [255, 240, 245],
"lawngreen": [124, 252, 0],
"lemonchiffon": [255, 250, 205],
"lightblue": [173, 216, 230],
"lightcoral": [240, 128, 128],
"lightcyan": [224, 255, 255],
"lightgoldenrodyellow": [250, 250, 210],
"lightgray": [211, 211, 211],
"lightgreen": [144, 238, 144],
"lightgrey": [211, 211, 211],
"lightpink": [255, 182, 193],
"lightsalmon": [255, 160, 122],
"lightseagreen": [32, 178, 170],
"lightskyblue": [135, 206, 250],
"lightslategray": [119, 136, 153],
"lightslategrey": [119, 136, 153],
"lightsteelblue": [176, 196, 222],
"lightyellow": [255, 255, 224],
"lime": [0, 255, 0],
"limegreen": [50, 205, 50],
"linen": [250, 240, 230],
"magenta": [255, 0, 255],
"maroon": [128, 0, 0],
"mediumaquamarine": [102, 205, 170],
"mediumblue": [0, 0, 205],
"mediumorchid": [186, 85, 211],
"mediumpurple": [147, 112, 219],
"mediumseagreen": [60, 179, 113],
"mediumslateblue": [123, 104, 238],
"mediumspringgreen": [0, 250, 154],
"mediumturquoise": [72, 209, 204],
"mediumvioletred": [199, 21, 133],
"midnightblue": [25, 25, 112],
"mintcream": [245, 255, 250],
"mistyrose": [255, 228, 225],
"moccasin": [255, 228, 181],
"navajowhite": [255, 222, 173],
"navy": [0, 0, 128],
"oldlace": [253, 245, 230],
"olive": [128, 128, 0],
"olivedrab": [107, 142, 35],
"orange": [255, 165, 0],
"orangered": [255, 69, 0],
"orchid": [218, 112, 214],
"palegoldenrod": [238, 232, 170],
"palegreen": [152, 251, 152],
"paleturquoise": [175, 238, 238],
"palevioletred": [219, 112, 147],
"papayawhip": [255, 239, 213],
"peachpuff": [255, 218, 185],
"peru": [205, 133, 63],
"pink": [255, 192, 203],
"plum": [221, 160, 221],
"powderblue": [176, 224, 230],
"purple": [128, 0, 128],
"red": [255, 0, 0],
"rosybrown": [188, 143, 143],
"royalblue": [65, 105, 225],
"saddlebrown": [139, 69, 19],
"salmon": [250, 128, 114],
"sandybrown": [244, 164, 96],
"seagreen": [46, 139, 87],
"seashell": [255, 245, 238],
"sienna": [160, 82, 45],
"silver": [192, 192, 192],
"skyblue": [135, 206, 235],
"slateblue": [106, 90, 205],
"slategray": [112, 128, 144],
"slategrey": [112, 128, 144],
"snow": [255, 250, 250],
"springgreen": [0, 255, 127],
"steelblue": [70, 130, 180],
"tan": [210, 180, 140],
"teal": [0, 128, 128],
"thistle": [216, 191, 216],
"tomato": [255, 99, 71],
"turquoise": [64, 224, 208],
"violet": [238, 130, 238],
"wheat": [245, 222, 179],
"white": [255, 255, 255],
"whitesmoke": [245, 245, 245],
"yellow": [255, 255, 0],
"yellowgreen": [154, 205, 50],
"transparent": [0, 0, 0, 0]
}
};
exports.torque['torque-reference'] = {
version: {
latest: _torque_reference_latest,
'1.0.0': _torque_reference_latest
}
}
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
var Event = {};
Event.on = function(evt, callback) {
var cb = this._evt_callbacks = this._evt_callbacks || {};
var l = cb[evt] || (cb[evt] = []);
l.push(callback);
};
Event.trigger = function(evt) {
var c = this._evt_callbacks && this._evt_callbacks[evt];
for(var i = 0; c && i < c.length; ++i) {
c[i].apply(this, Array.prototype.slice.call(arguments, 1));
}
};
Event.fire = Event.trigger;
Event.off = function (evt, callback) {
var c = this._evt_callbacks && this._evt_callbacks[evt];
if (c && !callback) {
delete this._evt_callbacks[evt];
return this;
}
var remove = [];
for(var i = 0; c && i < c.length; ++i) {
if(c[i] === callback) remove.push(i);
}
while(i = remove.pop()) c.splice(i, 1);
};
exports.torque.Event = Event;
// types
exports.torque.types = {
Uint8Array: typeof(window['Uint8Array']) !== 'undefined' ? window.Uint8Array : Array,
Uint32Array: typeof(window['Uint32Array']) !== 'undefined' ? window.Uint32Array : Array,
Int32Array: typeof(window['Int32Array']) !== 'undefined' ? window.Int32Array: Array,
};
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
function clamp(a, b) {
return function(t) {
return Math.max(Math.min(t, b), a);
};
}
function invLinear(a, b) {
var c = clamp(0, 1.0);
return function(t) {
return c((t - a)/(b - a));
};
}
function linear(a, b) {
var c = clamp(a, b);
function _linear(t) {
return c(a*(1.0 - t) + t*b);
}
_linear.invert = function() {
return invLinear(a, b);
};
return _linear;
}
exports.torque.math = {
clamp: clamp,
linear: linear,
invLinear: invLinear
};
})(typeof exports === "undefined" ? this : exports);
/*
# metrics profiler
## timing
```
var timer = Profiler.metric('resource:load')
time.start();
...
time.end();
```
## counters
```
var counter = Profiler.metric('requests')
counter.inc(); // 1
counter.inc(10); // 11
counter.dec() // 10
counter.dec(10) // 0
```
## Calls per second
```
var fps = Profiler.metric('fps')
function render() {
fps.mark();
}
```
*/
(function(exports) {
var MAX_HISTORY = 1024;
function Profiler() {}
Profiler.metrics = {};
Profiler.get = function(name) {
return Profiler.metrics[name] || {
max: 0,
min: 10000000,
avg: 0,
total: 0,
count: 0,
history: typeof(Float32Array) !== 'undefined' ? new Float32Array(MAX_HISTORY) : []
};
};
Profiler.new_value = function (name, value) {
var t = Profiler.metrics[name] = Profiler.get(name);
t.max = Math.max(t.max, value);
t.min = Math.min(t.min, value);
t.total += value;
++t.count;
t.avg = t.total / t.count;
t.history[t.count%MAX_HISTORY] = value;
};
Profiler.print_stats = function () {
for (k in Profiler.metrics) {
var t = Profiler.metrics[k];
console.log(" === " + k + " === ");
console.log(" max: " + t.max);
console.log(" min: " + t.min);
console.log(" avg: " + t.avg);
console.log(" count: " + t.count);
console.log(" total: " + t.total);
}
};
function Metric(name) {
this.t0 = null;
this.name = name;
this.count = 0;
}
Metric.prototype = {
//
// start a time measurement
//
start: function() {
this.t0 = +new Date();
return this;
},
// elapsed time since start was called
_elapsed: function() {
return +new Date() - this.t0;
},
//
// finish a time measurement and register it
// ``start`` should be called first, if not this
// function does not take effect
//
end: function() {
if (this.t0 !== null) {
Profiler.new_value(this.name, this._elapsed());
this.t0 = null;
}
},
//
// increments the value
// qty: how many, default = 1
//
inc: function(qty) {
qty = qty === undefined ? 1: qty;
Profiler.new_value(this.name, Profiler.get(this.name).count + (qty ? qty: 0));
},
//
// decrements the value
// qty: how many, default = 1
//
dec: function(qty) {
qty = qty === undefined ? 1: qty;
this.inc(-qty);
},
//
// measures how many times per second this function is called
//
mark: function() {
++this.count;
if(this.t0 === null) {
this.start();
return;
}
var elapsed = this._elapsed();
if(elapsed > 1) {
Profiler.new_value(this.name, this.count);
this.count = 0;
this.start();
}
}
};
Profiler.metric = function(name) {
return new Metric(name);
};
exports.Profiler = Profiler;
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
var torque = exports.torque = exports.torque || {};
var providers = exports.torque.providers = exports.torque.providers || {};
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;
// 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);
// count number of dates
var dates = 0;
var maxDateSlots = 0;
for (r = 0; r < rows.length; ++r) {
var row = rows[r];
if(this.options.cumulative) {
for (var s = 1; s < row.vals__uint8.length; ++s) {
row.vals__uint8[s] += row.vals__uint8[s - 1];
}
}
dates += row.dates__uint16.length;
for(var d = 0; d < row.dates__uint16.length; ++d) {
maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]);
}
}
// 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 || Uint8Array)(dates);
var renderDataPos = new Uint32Array(dates);
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;
y[r] = row.y__uint8 * this.options.resolution;
var dates = row.dates__uint16;
var vals = row.vals__uint8;
for (var j = 0, len = dates.length; j < len; ++j) {
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
rr.push([r, vals[j]]);
}
}
// for each timeslot search active buckets
var renderDataIndex = 0;
var timeSlotIndex = 0;
var i = 0;
for(var i = 0; i <= maxDateSlots; ++i) {
var c = 0;
var slotRows = rowsPerSlot[i]
if(slotRows) {
for (var r = 0; r < slotRows.length; ++r) {
var rr = slotRows[r];
++c;
renderDataPos[renderDataIndex] = rr[0]
renderData[renderDataIndex] = rr[1];
++renderDataIndex;
}
}
timeIndex[i] = timeSlotIndex;
timeCount[i] = c;
timeSlotIndex += c;
}
return {
x: x,
y: y,
coord: {
x: coord.x,
y: coord.y,
z: zoom
},
timeCount: timeCount,
timeIndex: timeIndex,
renderDataPos: renderDataPos,
renderData: renderData
};
},
url: function() {
return this.options.url || 'http://' + this.options.user + '.cartodb.com/api/v2/sql';
},
// execute actual query
sql: function(sql, callback, options) {
options = options || {};
torque.net.get(this.url() + "?q=" + encodeURIComponent(sql), function (data) {
if(options.parseJSON) {
data = JSON.parse(data.responseText);
}
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) {
this.table = this.options.table;
var numTiles = 1 << zoom;
var column_conv = this.options.column;
if(this.options.is_time) {
column_conv = format("date_part('epoch', {column})", this.options);
}
var sql = "" +
"WITH " +
"par AS (" +
" SELECT CDB_XYZ_Resolution({zoom})*{resolution} as res" +
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
")," +
"cte AS ( "+
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
", {countby} c" +
", floor(({column_conv} - {start})/{step}) d" +
" FROM {table} i, par p " +
" WHERE i.the_geom_webmercator && p.ext " +
" GROUP BY g, d" +
") " +
"" +
"SELECT least((st_x(g)-st_xmin(p.ext))/p.res, 255) x__uint8, " +
" least((st_y(g)-st_ymin(p.ext))/p.res, 255) y__uint8," +
" array_agg(c) vals__uint8," +
" array_agg(d) dates__uint16" +
" FROM cte, par p GROUP BY x__uint8, y__uint8";
var query = format(sql, this.options, {
zoom: zoom,
x: coord.x,
y: coord.y,
column_conv: column_conv
});
var self = this;
this.sql(query, function (data) {
var rows = JSON.parse(data.responseText).rows;
callback(self.proccessTile(rows, coord, zoom));
});
},
getKeySpan: function() {
return {
start: this.options.start,
end: this.options.end,
step: this.options.step,
steps: this.options.steps
};
},
getBounds: function() {
return this.options.bounds;
},
//
// 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 max_col, min_col, max_tmpl, min_tmpl;
if (this.options.is_time){
max_tmpl = "date_part('epoch', max({column}))";
min_tmpl = "date_part('epoch', min({column}))";
} else {
max_tmpl = "max({column})";
min_tmpl = "min({column})";
}
max_col = format(max_tmpl, { column: this.options.column });
min_col = format(min_tmpl, { column: this.options.column });
var sql = format("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 {table}", {
max_col: max_col,
min_col: min_col,
table: this.options.table
});
var self = this;
this.sql(sql, function(data) {
//TODO: manage bounds
data = data.rows[0];
self.options.start = data.min;
self.options.end = data.max;
self.options.step = (data.max - data.min)/self.options.steps;
self.options.bounds = [
[data.ymin, data.xmin],
[data.ymax, data.xmax]
];
self._setReady(true);
}, { parseJSON: true });
}
};
torque.providers.json = json;
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
var torque = exports.torque = exports.torque || {};
var providers = exports.torque.providers = exports.torque.providers || {};
var Uint8Array = torque.types.Uint8Array;
var Int32Array = torque.types.Int32Array;
var Uint32Array = torque.types.Uint32Array;
// format('hello, {0}', 'rambo') -> "hello, rambo"
function format(str, attrs) {
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) {
// check options
this.options = options;
};
json.prototype = {
//
// return the data aggregated by key:
// {
// key0: 12,
// key1: 32
// key2: 25
// }
//
aggregateByKey: function(rows) {
function getKeys(row) {
var HEADER_SIZE = 3;
var valuesCount = row.data[2];
var keys = {};
for (var s = 0; s < valuesCount; ++s) {
keys[row.data[HEADER_SIZE + s]] = row.data[HEADER_SIZE + valuesCount + s];
}
return keys;
}
var keys = {};
for (r = 0; r < rows.length; ++r) {
var rowKeys = getKeys(rows[r]);
for(var k in rowKeys) {
keys[k] = keys[k] || 0;
keys[k] += rowKeys[k];
}
}
return keys;
},
/**
*
*/
proccessTile: function(rows, coord, zoom) {
var r;
var x = new Uint8Array(rows.length);
var y = new Uint8Array(rows.length);
var self = this;
// decode into a javascript strcuture the array
function decode_row(row) {
var HEADER_SIZE = 3;
var o = {
x: row.data[0] * self.options.resolution,
y: row.data[1] * self.options.resolution,
valuesCount: row.data[2],
times: [],
values: [],
};
for (var s = 0; s < o.valuesCount; ++s) {
o.times.push(row.data[HEADER_SIZE + s]);
o.values.push(row.data[HEADER_SIZE + o.valuesCount + s]);
}
if(self.options.cumulative) {
for (var s = 1; s < o.valuesCount; ++s) {
o.values[s] += o.values[s - 1];
}
}
return o
}
// decode all the rows
for (r = 0; r < rows.length; ++r) {
rows[r] = decode_row(rows[r]);
}
// count number of dates
var dates = 0;
var maxDateSlots = 0;
for (r = 0; r < rows.length; ++r) {
var row = rows[r];
dates += row.times.length;
for(var d = 0; d < row.times.length; ++d) {
maxDateSlots = Math.max(maxDateSlots, row.times[d]);
}
}
// 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 || Uint8Array)(dates);
var renderDataPos = new Uint32Array(dates);
var rowsPerSlot = {};
// precache pixel positions
for (var r = 0; r < rows.length; ++r) {
var row = rows[r];
x[r] = row.x;
y[r] = row.y;
var dates = row.times;
var vals = row.values;
for (var j = 0, len = dates.length; j < len; ++j) {
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
rr.push([r, vals[j]]);
}
}
// for each timeslot search active buckets
var renderDataIndex = 0;
var timeSlotIndex = 0;
var i = 0;
for(var i = 0; i <= maxDateSlots; ++i) {
var c = 0;
var slotRows = rowsPerSlot[i]
if(slotRows) {
for (var r = 0; r < slotRows.length; ++r) {
var rr = slotRows[r];
++c;
renderDataPos[renderDataIndex] = rr[0]
renderData[renderDataIndex] = rr[1];
++renderDataIndex;
}
}
timeIndex[i] = timeSlotIndex;
timeCount[i] = c;
timeSlotIndex += c;
}
return {
x: x,
y: y,
coord: {
x: coord.x,
y: coord.y,
z: zoom
},
timeCount: timeCount,
timeIndex: timeIndex,
renderDataPos: renderDataPos,
renderData: renderData
};
},
url: function() {
return this.options.url;
},
tileUrl: function(coord, zoom) {
var template = this.url();
var s = (this.options.subdomains || 'abcd')[(coord.x + coord.y + zoom) % 4];
return template
.replace('{x}', coord.x)
.replace('{y}', coord.y)
.replace('{z}', zoom)
.replace('{s}', s);
},
getTile: function(coord, zoom, callback) {
var template = this.tileUrl(coord, zoom);
var self = this;
var fetchTime = Profiler.metric('jsonarray:fetch time');
fetchTime.start();
torque.net.get(template, function (data) {
fetchTime.end();
if(data) {
data = JSON.parse(data.responseText);
}
callback(data);
});
},
/**
* `coord` object like {x : tilex, y: tiley }
* `zoom` quadtree zoom level
*/
getTileData: function(coord, zoom, callback) {
var template = this.tileUrl(coord, zoom);
var self = this;
var fetchTime = Profiler.metric('jsonarray:fetch time');
fetchTime.start();
torque.net.get(template, function (data) {
fetchTime.end();
var processed = null;
var processingTime = Profiler.metric('jsonarray:processing time');
var parsingTime = Profiler.metric('jsonarray:parsing time');
try {
processingTime.start();
parsingTime.start();
var rows = JSON.parse(data.responseText || data.response).rows;
parsingTime.end();
processed = self.proccessTile(rows, coord, zoom);
processingTime.end();
} catch(e) {
console.error("problem parsing JSON on ", coord, zoom);
}
callback(processed);
});
}
};
torque.providers.JsonArray = json
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
var torque = exports.torque = exports.torque || {};
torque.net = torque.net || {};
function get(url, callback) {
var request = XMLHttpRequest;
// from d3.js
if (window.XDomainRequest
&& !("withCredentials" in request)
&& /^(http(s)?:)?\/\//.test(url)) request = XDomainRequest;
var req = new request();
function respond() {
var status = req.status, result;
if (!status && req.responseText || status >= 200 && status < 300 || status === 304) {
callback(req);
} else {
callback(null);
}
}
"onload" in req
? req.onload = req.onerror = respond
: req.onreadystatechange = function() { req.readyState > 3 && respond(); };
req.open("GET", url, true);
//req.responseType = 'arraybuffer';
req.send(null)
return req;
}
torque.net = {
get: get
};
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
var TAU = Math.PI*2;
function renderPoint(ctx, st) {
ctx.fillStyle = st.fillStyle;
ctx.strokStyle = st.strokStyle;
var pixel_size = st['point-radius']
// render a circle
ctx.beginPath();
ctx.arc(0, 0, pixel_size, 0, TAU, true, true);
ctx.closePath();
if(st.fillStyle) {
if(st.fillOpacity) {
ctx.globalAlpha = st.fillOpacity;
}
ctx.fill();
}
ctx.globalAlpha = 1.0;
if(st.strokeStyle) {
if(st.strokeOpacity) {
ctx.globalAlpha = st.strokeOpacity;
}
if(st.lineWidth) {
ctx.lineWidth = st.lineWidth;
}
ctx.strokeStyle = st.strokeStyle;
ctx.stroke();
}
}
function renderSprite(ctx, st) {
var img = st['point-file'] || st['marker-file'];
var ratio = img.height/img.width;
var w = st['point-radius'] || img.width;
var h = st['point-radius'] || st['marker-height'] || w*ratio;
ctx.drawImage(img, 0, 0, w, h);
}
exports.torque.cartocss = exports.torque.cartocss|| {};
exports.torque.cartocss = {
renderPoint: renderPoint,
renderSprite: renderSprite
};
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
exports.torque.renderer = exports.torque.renderer || {};
var TAU = Math.PI * 2;
var DEFAULT_CARTOCSS = [
'#layer {',
' marker-fill: #662506;',
' marker-width: 4;',
' [value > 1] { marker-fill: #FEE391; }',
' [value > 2] { marker-fill: #FEC44F; }',
' [value > 3] { marker-fill: #FE9929; }',
' [value > 4] { marker-fill: #EC7014; }',
' [value > 5] { marker-fill: #CC4C02; }',
' [value > 6] { marker-fill: #993404; }',
' [value > 7] { marker-fill: #662506; }',
'}'
].join('\n');
//
// this renderer just render points depending of the value
//
function PointRenderer(canvas, options) {
if (!canvas) {
throw new Error("canvas can't be undefined");
}
this.options = options;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
this._sprites = []; // sprites per layer
this._shader = null;
this._trailsShader = null;
//carto.tree.Reference.set(torque['torque-reference']);
this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS);
}
PointRenderer.prototype = {
setCanvas: function(canvas) {
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
},
//
// sets the cartocss style to render stuff
//
setCartoCSS: function(cartocss) {
// clean sprites
this._sprites = [];
this._cartoCssStyle = new carto.RendererJS().render(cartocss);
},
//
// generate sprite based on cartocss style
//
generateSprite: function(shader, value, shaderVars) {
var prof = Profiler.metric('PointRenderer:generateSprite').start();
var st = shader.getStyle('canvas-2d', {
value: value
}, shaderVars);
var pointSize = st['point-radius'];
if(!pointSize) {
throw new Error("marker-width property should be set");
}
var canvasSize = pointSize*2;
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.width = canvas.width = canvasSize;
ctx.height = canvas.height = canvasSize;
ctx.translate(pointSize, pointSize);
if(st['point-file'] || st['marker-fil']) {
torque.cartocss.renderSprite(ctx, st);
} else {
torque.cartocss.renderPoint(ctx, st);
}
prof.end();
return canvas;
},
//
// renders all the layers (and frames for each layer) from cartocss
//
renderTile: function(tile, key) {
var layers = this._cartoCssStyle.getLayers();
for(var i = 0, n = layers.length; i < n; ++i ) {
var layer = layers[i];
var sprites = this._sprites[i] || (this._sprites[i] = {});
// frames for each layer
for(var fr = 0; fr < layer.frames().length; ++fr) {
var frame = layer.frames()[fr];
var fr_sprites = sprites[frame] || (sprites[frame] = []);
this._renderTile(tile, key - frame, frame, fr_sprites, layer);
}
}
},
//
// renders a tile in the canvas for key defined in
// the torque tile
//
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
if(!this._canvas) return;
var prof = Profiler.metric('PointRenderer:renderTile').start();
var ctx = this._ctx;
//var res = 1;//this.options.resolution;
var activePixels = tile.timeCount[key];
if(this.options.blendmode) {
ctx.globalCompositeOperation = this.options.blendmode;
}
if(activePixels) {
var pixelIndex = tile.timeIndex[key];
for(var p = 0; p < activePixels; ++p) {
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
if(c) {
var sp = sprites[c];
if(!sp) {
sp = sprites[c] = this.generateSprite(shader, c, _.extend({ zoom: tile.zoom, 'frame-offset': frame_offset }, shaderVars));
}
//var x = tile.x[posIdx]*res - (sp.width >> 1);
//var y = (256 - res - res*tile.y[posIdx]) - (sp.height >> 1);
var x = tile.x[posIdx]- (sp.width >> 1);
var y = (256 - 1 - tile.y[posIdx]) - (sp.height >> 1);
ctx.drawImage(sp, x, y);
}
}
}
prof.end();
}
};
// exports public api
exports.torque.renderer.Point = PointRenderer;
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
exports.torque = exports.torque || {};
exports.torque.renderer = exports.torque.renderer || {};
var DEFAULT_CARTOCSS = [
'#layer {',
' polygon-fill: #FFFF00;',
' [value > 10] { polygon-fill: #FFFF00; }',
' [value > 100] { polygon-fill: #FFCC00; }',
' [value > 1000] { polygon-fill: #FE9929; }',
' [value > 10000] { polygon-fill: #FF6600; }',
' [value > 100000] { polygon-fill: #FF3300; }',
'}'
].join('\n');
var TAU = Math.PI * 2;
//
// this renderer just render points depending of the value
//
function RectanbleRenderer(canvas, options) {
this.options = options;
carto.tree.Reference.set(torque['torque-reference']);
this.setCanvas(canvas);
this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS);
}
RectanbleRenderer.prototype = {
//
// sets the cartocss style to render stuff
//
setCartoCSS: function(cartocss) {
this._cartoCssStyle = new carto.RendererJS().render(cartocss);
if(this._cartoCssStyle.getLayers().length > 1) {
throw new Error("only one CartoCSS layer is supported");
}
this._shader = this._cartoCssStyle.getLayers()[0].shader;
},
setCanvas: function(canvas) {
if(!canvas) return;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
},
accumulate: function(tile, keys) {
var prof = Profiler.metric('RectangleRender:accumulate').start();
var x, y, posIdx, p, k, key, activePixels, pixelIndex;
var res = this.options.resolution;
var s = 256/res;
var accum = new Float32Array(s*s);
if(typeof(keys) !== 'object') {
keys = [keys];
}
for(k = 0; k < keys.length; ++k) {
key = keys[k];
activePixels = tile.timeCount[key];
if(activePixels) {
pixelIndex = tile.timeIndex[key];
for(p = 0; p < activePixels; ++p) {
posIdx = tile.renderDataPos[pixelIndex + p];
x = tile.x[posIdx]/res;
y = tile.y[posIdx]/res;
accum[x*s + y] += tile.renderData[pixelIndex + p];
}
}
}
prof.end();
return accum;
},
renderTileAccum: function(accum, px, py) {
var prof = Profiler.metric('RectangleRender:renderTileAccum').start();
var color, x, y, alpha;
var res = this.options.resolution;
var ctx = this._ctx;
var s = (256/res) | 0;
var s2 = s*s;
var colors = this._colors;
if(this.options.blendmode) {
ctx.globalCompositeOperation = this.options.blendmode;
}
var polygon_alpha = this._shader['polygon-opacity'] || function() { return 1.0; };
for(var i = 0; i < s2; ++i) {
var xy = i;
var value = accum[i];
if(value) {
x = (xy/s) | 0;
y = xy % s;
// by-pass the style generation for improving performance
color = this._shader['polygon-fill']({ value: value }, { zoom: 0 });
ctx.fillStyle = color;
//TODO: each function should have a default value for each
//property defined in the cartocss
alpha = polygon_alpha({ value: value }, { zoom: 0 });
if(alpha === null) {
alpha = 1.0;
}
ctx.globalAlpha = alpha;
ctx.fillRect(x * res, 256 - res - y * res, res, res);
}
}
prof.end();
},
//
// renders a tile in the canvas for key defined in
// the torque tile
//
renderTile: function(tile, key, px, py) {
if(!this._canvas) return;
var res = this.options.resolution;
//var prof = Profiler.get('render').start();
var ctx = this._ctx;
var colors = this._colors;
var activepixels = tile.timeCount[key];
if(activepixels) {
var w = this._canvas.width;
var h = this._canvas.height;
//var imageData = ctx.getImageData(0, 0, w, h);
//var pixels = imageData.data;
var pixelIndex = tile.timeIndex[key];
for(var p = 0; p < activePixels; ++p) {
var posIdx = tile.renderDataPos[pixelIndex + p];
var c = tile.renderData[pixelIndex + p];
if(c) {
var color = colors[Math.min(c, colors.length - 1)];
var x = tile.x[posIdx];// + px;
var y = tile.y[posIdx]; //+ py;
ctx.fillStyle = color;
ctx.fillRect(x, y, res, res);
/*
for(var xx = 0; xx < res; ++xx) {
for(var yy = 0; yy < res; ++yy) {
var idx = 4*((x+xx) + w*(y + yy));
pixels[idx + 0] = color[0];
pixels[idx + 1] = color[1];
pixels[idx + 2] = color[2];
pixels[idx + 3] = color[3];
}
}
*/
}
}
//ctx.putImageData(imageData, 0, 0);
}
//prof.end();
}
};
// exports public api
exports.torque.renderer.Rectangle = RectanbleRenderer;
})(typeof exports === "undefined" ? this : exports);
/**
* @license
* Copyright 2013 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Extends OverlayView to provide a canvas "Layer".
* @author Brendan Kenny
*/
/**
* A map layer that provides a canvas over the slippy map and a callback
* system for efficient animation. Requires canvas and CSS 2D transform
* support.
* @constructor
* @extends google.maps.OverlayView
* @param {CanvasLayerOptions=} opt_options Options to set in this CanvasLayer.
*/
if(typeof(google) !== 'undefined' && typeof(google.maps) !== 'undefined') {
function CanvasLayer(opt_options) {
/**
* If true, canvas is in a map pane and the OverlayView is fully functional.
* See google.maps.OverlayView.onAdd for more information.
* @type {boolean}
* @private
*/
this.isAdded_ = false;
/**
* If true, each update will immediately schedule the next.
* @type {boolean}
* @private
*/
this.isAnimated_ = false;
/**
* The name of the MapPane in which this layer will be displayed.
* @type {string}
* @private
*/
this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_;
/**
* A user-supplied function called whenever an update is required. Null or
* undefined if a callback is not provided.
* @type {?function=}
* @private
*/
this.updateHandler_ = null;
/**
* A user-supplied function called whenever an update is required and the
* map has been resized since the last update. Null or undefined if a
* callback is not provided.
* @type {?function}
* @private
*/
this.resizeHandler_ = null;
/**
* The LatLng coordinate of the top left of the current view of the map. Will
* be null when this.isAdded_ is false.
* @type {google.maps.LatLng}
* @private
*/
this.topLeft_ = null;
/**
* The map-pan event listener. Will be null when this.isAdded_ is false. Will
* be null when this.isAdded_ is false.
* @type {?function}
* @private
*/
this.centerListener_ = null;
/**
* The map-resize event listener. Will be null when this.isAdded_ is false.
* @type {?function}
* @private
*/
this.resizeListener_ = null;
/**
* If true, the map size has changed and this.resizeHandler_ must be called
* on the next update.
* @type {boolean}
* @private
*/
this.needsResize_ = true;
/**
* A browser-defined id for the currently requested callback. Null when no
* callback is queued.
* @type {?number}
* @private
*/
this.requestAnimationFrameId_ = null;
var canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.top = 0;
canvas.style.left = 0;
canvas.style.pointerEvents = 'none';
/**
* The canvas element.
* @type {!HTMLCanvasElement}
*/
this.canvas = canvas;
/**
* Simple bind for functions with no args for bind-less browsers (Safari).
* @param {Object} thisArg The this value used for the target function.
* @param {function} func The function to be bound.
*/
function simpleBindShim(thisArg, func) {
return function() { func.apply(thisArg); };
}
/**
* A reference to this.repositionCanvas_ with this bound as its this value.
* @type {function}
* @private
*/
this.repositionFunction_ = simpleBindShim(this, this.repositionCanvas_);
/**
* A reference to this.resize_ with this bound as its this value.
* @type {function}
* @private
*/
this.resizeFunction_ = simpleBindShim(this, this.resize_);
/**
* A reference to this.update_ with this bound as its this value.
* @type {function}
* @private
*/
this.requestUpdateFunction_ = simpleBindShim(this, this.update_);
// set provided options, if any
if (opt_options) {
this.setOptions(opt_options);
}
}
CanvasLayer.prototype = new google.maps.OverlayView();
/**
* The default MapPane to contain the canvas.
* @type {string}
* @const
* @private
*/
CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer';
/**
* Transform CSS property name, with vendor prefix if required. If browser
* does not support transforms, property will be ignored.
* @type {string}
* @const
* @private
*/
CanvasLayer.CSS_TRANSFORM_ = (function() {
var div = document.createElement('div');
var transformProps = [
'transform',
'WebkitTransform',
'MozTransform',
'OTransform',
'msTransform'
];
for (var i = 0; i < transformProps.length; i++) {
var prop = transformProps[i];
if (div.style[prop] !== undefined) {
return prop;
}
}
// return unprefixed version by default
return transformProps[0];
})();
/**
* The requestAnimationFrame function, with vendor-prefixed or setTimeout-based
* fallbacks. MUST be called with window as thisArg.
* @type {function}
* @param {function} callback The function to add to the frame request queue.
* @return {number} The browser-defined id for the requested callback.
* @private
*/
CanvasLayer.prototype.requestAnimFrame_ =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
/**
* The cancelAnimationFrame function, with vendor-prefixed fallback. Does not
* fall back to clearTimeout as some platforms implement requestAnimationFrame
* but not cancelAnimationFrame, and the cost is an extra frame on onRemove.
* MUST be called with window as thisArg.
* @type {function}
* @param {number=} requestId The id of the frame request to cancel.
* @private
*/
CanvasLayer.prototype.cancelAnimFrame_ =
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(requestId) {};
/**
* Sets any options provided. See CanvasLayerOptions for more information.
* @param {CanvasLayerOptions} options The options to set.
*/
CanvasLayer.prototype.setOptions = function(options) {
if (options.animate !== undefined) {
this.setAnimate(options.animate);
}
if (options.paneName !== undefined) {
this.setPane(options.paneName);
}
if (options.updateHandler !== undefined) {
this.setUpdateHandler(options.updateHandler);
}
if (options.resizeHandler !== undefined) {
this.setResizeHandler(options.resizeHandler);
}
if(options.readyHandler) {
this.readyHandler = options.readyHandler;
}
};
/**
* Set the animated state of the layer. If true, updateHandler will be called
* repeatedly, once per frame. If false, updateHandler will only be called when
* a map property changes that could require the canvas content to be redrawn.
* @param {boolean} animate Whether the canvas is animated.
*/
CanvasLayer.prototype.setAnimate = function(animate) {
this.isAnimated_ = !!animate;
if (this.isAnimated_) {
this.scheduleUpdate();
}
};
/**
* @return {boolean} Whether the canvas is animated.
*/
CanvasLayer.prototype.isAnimated = function() {
return this.isAnimated_;
};
/**
* Set the MapPane in which this layer will be displayed, by name. See
* {@code google.maps.MapPanes} for the panes available.
* @param {string} paneName The name of the desired MapPane.
*/
CanvasLayer.prototype.setPaneName = function(paneName) {
this.paneName_ = paneName;
this.setPane_();
};
/**
* @return {string} The name of the current container pane.
*/
CanvasLayer.prototype.getPaneName = function() {
return this.paneName_;
};
/**
* Adds the canvas to the specified container pane. Since this is guaranteed to
* execute only after onAdd is called, this is when paneName's existence is
* checked (and an error is thrown if it doesn't exist).
* @private
*/
CanvasLayer.prototype.setPane_ = function() {
if (!this.isAdded_) {
return;
}
// onAdd has been called, so panes can be used
var panes = this.getPanes();
if (!panes[this.paneName_]) {
throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.');
}
panes[this.paneName_].appendChild(this.canvas);
};
/**
* Set a function that will be called whenever the parent map and the overlay's
* canvas have been resized. If opt_resizeHandler is null or unspecified, any
* existing callback is removed.
* @param {?function=} opt_resizeHandler The resize callback function.
*/
CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) {
this.resizeHandler_ = opt_resizeHandler;
};
/**
* Set a function that will be called when a repaint of the canvas is required.
* If opt_updateHandler is null or unspecified, any existing callback is
* removed.
* @param {?function=} opt_updateHandler The update callback function.
*/
CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) {
this.updateHandler_ = opt_updateHandler;
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.onAdd = function() {
if (this.isAdded_) {
return;
}
this.isAdded_ = true;
this.setPane_();
this.resizeListener_ = google.maps.event.addListener(this.getMap(),
'resize', this.resizeFunction_);
this.centerListener_ = google.maps.event.addListener(this.getMap(),
'center_changed', this.repositionFunction_);
this.resize_();
this.repositionCanvas_();
this.readyHandler && this.readyHandler();
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.onRemove = function() {
if (!this.isAdded_) {
return;
}
this.isAdded_ = false;
this.topLeft_ = null;
// remove canvas and listeners for pan and resize from map
this.canvas.parentElement.removeChild(this.canvas);
if (this.centerListener_) {
google.maps.event.removeListener(this.centerListener_);
this.centerListener_ = null;
}
if (this.resizeListener_) {
google.maps.event.removeListener(this.resizeListener_);
this.resizeListener_ = null;
}
// cease canvas update callbacks
if (this.requestAnimationFrameId_) {
this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_);
this.requestAnimationFrameId_ = null;
}
};
/**
* The internal callback for resize events that resizes the canvas to keep the
* map properly covered.
* @private
*/
CanvasLayer.prototype.resize_ = function() {
// TODO(bckenny): it's common to use a smaller canvas but use CSS to scale
// what is drawn by the browser to save on fill rate. Add an option to do
// this.
if (!this.isAdded_) {
return;
}
var map = this.getMap();
var width = map.getDiv().offsetWidth;
var height = map.getDiv().offsetHeight;
var oldWidth = this.canvas.width;
var oldHeight = this.canvas.height;
// resizing may allocate a new back buffer, so do so conservatively
if (oldWidth !== width || oldHeight !== height) {
this.canvas.width = width;
this.canvas.height = height;
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
this.needsResize_ = true;
this.scheduleUpdate();
}
};
/**
* @inheritDoc
*/
CanvasLayer.prototype.draw = function() {
this.repositionCanvas_();
};
/**
* Internal callback for map view changes. Since the Maps API moves the overlay
* along with the map, this function calculates the opposite translation to
* keep the canvas in place.
* @private
*/
CanvasLayer.prototype.repositionCanvas_ = function() {
// TODO(bckenny): *should* only be executed on RAF, but in current browsers
// this causes noticeable hitches in map and overlay relative
// positioning.
var bounds = this.getMap().getBounds();
this.topLeft_ = new google.maps.LatLng(bounds.getNorthEast().lat(),
bounds.getSouthWest().lng());
// canvas position relative to draggable map's conatainer depends on
// overlayView's projection, not the map's
var projection = this.getProjection();
var divTopLeft = projection.fromLatLngToDivPixel(this.topLeft_);
console.log(this.topLeft_.lng(), divTopLeft.x);
// when the zoom level is low, more than one map can be shown in the screen
// so the canvas should be attach to the map with more are in the screen
var mapSize = (1 << this.getMap().getZoom())*256;
if (Math.abs(divTopLeft.x) > mapSize) {
divTopLeft.x -= mapSize;
}
this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' +
Math.round(divTopLeft.x) + 'px,' + Math.round(divTopLeft.y) + 'px)';
this.scheduleUpdate();
};
/**
* Internal callback that serves as main animation scheduler via
* requestAnimationFrame. Calls resize and update callbacks if set, and
* schedules the next frame if overlay is animated.
* @private
*/
CanvasLayer.prototype.update_ = function() {
this.requestAnimationFrameId_ = null;
if (!this.isAdded_) {
return;
}
if (this.isAnimated_) {
this.scheduleUpdate();
}
if (this.needsResize_ && this.resizeHandler_) {
this.needsResize_ = false;
this.resizeHandler_();
}
if (this.updateHandler_) {
this.updateHandler_();
}
};
/**
* A convenience method to get the current LatLng coordinate of the top left of
* the current view of the map.
* @return {google.maps.LatLng} The top left coordinate.
*/
CanvasLayer.prototype.getTopLeft = function() {
return this.topLeft_;
};
/**
* Schedule a requestAnimationFrame callback to updateHandler. If one is
* already scheduled, there is no effect.
*/
CanvasLayer.prototype.scheduleUpdate = function() {
if (this.isAdded_ && !this.requestAnimationFrameId_) {
this.requestAnimationFrameId_ =
this.requestAnimFrame_.call(window, this.requestUpdateFunction_);
}
};
}
/*
====================
canvas setup for drawing tiles
====================
*/
if(typeof(google) !== 'undefined' && typeof(google.maps) !== 'undefined') {
function CanvasTileLayer(canvas_setup, render) {
this.tileSize = new google.maps.Size(256, 256);
this.maxZoom = 19;
this.name = "Tile #s";
this.alt = "Canvas tile layer";
this.tiles = {};
this.canvas_setup = canvas_setup;
this.render = render;
if (!render) {
this.render = canvas_setup;
}
}
// create a tile with a canvas element
CanvasTileLayer.prototype.create_tile_canvas = function (coord, zoom, ownerDocument) {
// create canvas and reset style
var canvas = ownerDocument.createElement('canvas');
var hit_canvas = ownerDocument.createElement('canvas');
canvas.style.border = hit_canvas.style.border = "none";
canvas.style.margin = hit_canvas.style.margin = "0";
canvas.style.padding = hit_canvas.style.padding = "0";
// prepare canvas and context sizes
var ctx = canvas.getContext('2d');
ctx.width = canvas.width = this.tileSize.width;
ctx.height = canvas.height = this.tileSize.height;
var hit_ctx = hit_canvas.getContext('2d');
hit_canvas.width = hit_ctx.width = this.tileSize.width;
hit_canvas.height = hit_ctx.height = this.tileSize.height;
//set unique id
var tile_id = coord.x + '_' + coord.y + '_' + zoom;
canvas.setAttribute('id', tile_id);
hit_canvas.setAttribute('id', tile_id);
if (tile_id in this.tiles)
delete this.tiles[tile_id];
this.tiles[tile_id] = {canvas:canvas, ctx:ctx, hit_canvas:hit_canvas, hit_ctx:hit_ctx, coord:coord, zoom:zoom, primitives:null};
// custom setup
//if (tile_id == '19295_24654_16'){
if (this.canvas_setup)
this.canvas_setup(this.tiles[tile_id], coord, zoom);
//}
return canvas;
}
CanvasTileLayer.prototype.each = function (callback) {
for (var t in this.tiles) {
var tile = this.tiles[t];
callback(tile);
}
}
CanvasTileLayer.prototype.recreate = function () {
for (var t in this.tiles) {
var tile = this.tiles[t];
this.canvas_setup(tile, tile.coord, tile.zoom);
}
};
CanvasTileLayer.prototype.redraw_tile = function (tile) {
this.render(tile, tile.coord, tile.zoom);
};
CanvasTileLayer.prototype.redraw = function () {
for (var t in this.tiles) {
var tile = this.tiles[t];
this.render(tile, tile.coord, tile.zoom);
}
};
// could be called directly...
CanvasTileLayer.prototype.getTile = function (coord, zoom, ownerDocument) {
return this.create_tile_canvas(coord, zoom, ownerDocument);
};
CanvasTileLayer.prototype.releaseTile = function (tile) {
var id = tile.getAttribute('id');
delete this.tiles[id];
};
}
(function(exports) {
if(typeof(google) === 'undefined' || typeof(google.maps) === 'undefined') return;
function GMapsTileLoader() {
}
GMapsTileLoader.prototype = {
_initTileLoader: function(map, projection) {
this._map = map;
this._projection = projection;
this._tiles = {};
this._tilesToLoad = 0;
this._updateTiles = this._updateTiles.bind(this);
google.maps.event.addListener(this._map, 'dragend', this._updateTiles);
google.maps.event.addListener(this._map, 'zoom_changed', this._updateTiles);
this.tileSize = 256;
this._updateTiles();
},
_removeTileLoader: function() {
//TODO: unbind events
//TODO: remove tiles
},
_removeTiles: function () {
for (var key in this._tiles) {
this._removeTile(key);
}
},
_updateTiles: function () {
if (!this._map) { return; }
var bounds = this._map.getBounds();
var zoom = this._map.getZoom();
var tileSize = this.tileSize;
var mzoom = (1 << zoom);
var topLeft = new google.maps.LatLng(
bounds.getNorthEast().lat(),
bounds.getSouthWest().lng()
);
var bottomRigth = new google.maps.LatLng(
bounds.getSouthWest().lat(),
bounds.getNorthEast().lng()
);
this._projection = this._map.getProjection();
var divTopLeft = this._projection.fromLatLngToPoint(topLeft);
var divBottomRight = this._projection.fromLatLngToPoint(bottomRigth);
var nwTilePoint = new google.maps.Point(
Math.floor(divTopLeft.x*mzoom / tileSize),
Math.floor(divTopLeft.y*mzoom / tileSize)),
seTilePoint = new google.maps.Point(
Math.floor(divBottomRight.x*mzoom / tileSize),
Math.floor(divBottomRight.y*mzoom / tileSize));
this._addTilesFromCenterOut(nwTilePoint, seTilePoint);
this._removeOtherTiles(nwTilePoint, seTilePoint);
},
_removeOtherTiles: function (nwTilePoint, seTilePoint) {
var kArr, x, y, key;
var zoom = this._map.getZoom();
for (key in this._tiles) {
if (this._tiles.hasOwnProperty(key)) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
z = parseInt(kArr[2], 10);
// remove tile if it's out of bounds
if (z !== zoom || x < nwTilePoint.x || x > seTilePoint.x || y < nwTilePoint.y || y > seTilePoint.y) {
this._removeTile(key);
}
}
}
},
_removeTile: function (key) {
this.onTileRemoved && this.onTileRemoved(this._tiles[key]);
delete this._tiles[key];
},
_tileShouldBeLoaded: function (tilePoint) {
return !((tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom) in this._tiles);
},
_tileLoaded: function(tilePoint, tileData) {
this._tilesToLoad--;
this._tiles[tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom] = tileData;
if(this._tilesToLoad === 0) {
this.onTilesLoaded && this.onTilesLoaded();
}
},
getTilePos: function (tilePoint) {
var limit = (1 << this._map.getZoom());
// wrap tile
tilePoint = {
x: ((tilePoint.x % limit) + limit) % limit,
y: tilePoint.y
};
tilePoint = new google.maps.Point(
tilePoint.x * this.tileSize,
tilePoint.y * this.tileSize
);
var bounds = this._map.getBounds();
var topLeft = new google.maps.LatLng(
bounds.getNorthEast().lat(),
bounds.getSouthWest().lng()
);
var divTopLeft = this._map.getProjection().fromLatLngToPoint(topLeft);
zoom = (1 << this._map.getZoom());
divTopLeft.x = divTopLeft.x * zoom;
divTopLeft.y = divTopLeft.y * zoom;
return new google.maps.Point(
tilePoint.x - divTopLeft.x,
tilePoint.y - divTopLeft.y
);
},
_addTilesFromCenterOut: function (nwTilePoint, seTilePoint) {
var queue = [],
center = new google.maps.Point(
(nwTilePoint.x + seTilePoint.x) * 0.5,
(nwTilePoint.y + seTilePoint.y) * 0.5
),
zoom = this._map.getZoom();
var j, i, point;
for (j = nwTilePoint.y; j <= seTilePoint.y; j++) {
for (i = nwTilePoint.x; i <= seTilePoint.x; i++) {
point = new google.maps.Point (i, j);
point.zoom = zoom;
if (this._tileShouldBeLoaded(point)) {
queue.push(point);
}
}
}
var tilesToLoad = queue.length;
if (tilesToLoad === 0) { return; }
function distanceToCenterSq(point) {
var dx = point.x - center.x;
var dy = point.y - center.y;
return dx * dx + dy * dy;
}
// load tiles in order of their distance to center
queue.sort(function (a, b) {
return distanceToCenterSq(a) - distanceToCenterSq(b);
});
this._tilesToLoad += tilesToLoad;
if (this.onTileAdded) {
for (i = 0; i < tilesToLoad; i++) {
this.onTileAdded(queue[i]);
}
}
}
}
torque.GMapsTileLoader = GMapsTileLoader;
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
if(typeof(google) === 'undefined' || typeof(google.maps) === 'undefined') return;
function GMapsTorqueLayer(options) {
var self = this;
this.key = 0;
this.cartocss = null;
this.ready = false;
this.options = _.extend({}, options);
_.defaults(this.options, {
provider: 'sql_api',
renderer: 'point',
resolution: 2,
steps: 100
});
this.animator = new torque.Animator(function(time) {
var k = time | 0;
if(self.key !== k) {
self.setKey(k);
}
}, this.options);
this.play = this.animator.start.bind(this.animator);
this.stop = this.animator.stop.bind(this.animator);
this.pause = this.animator.pause.bind(this.animator);
this.toggle = this.animator.toggle.bind(this.animator);
CanvasLayer.call(this, {
map: this.options.map,
//resizeHandler: this.redraw,
animate: false,
updateHandler: this.render,
readyHandler: this.initialize
});
}
/**
* torque layer
*/
GMapsTorqueLayer.prototype = _.extend({},
CanvasLayer.prototype,
torque.GMapsTileLoader.prototype,
torque.Event,
{
providers: {
'sql_api': torque.providers.json,
'url_template': torque.providers.jsonarray
},
renderers: {
'point': torque.renderer.Point,
'pixel': torque.renderer.Rectangle
},
initialize: function() {
var self = this;
this.onTileAdded = this.onTileAdded.bind(this);
this.options.ready = function() {
self.fire("change:bounds", {
bounds: self.provider.getBounds()
});
};
this.provider = new this.providers[this.options.provider](this.options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options);
this._initTileLoader(this.options.map, this.getProjection());
if (this.cartocss) {
this.renderer.setCartoCSS(this.cartocss);
}
},
getCanvas: function() {
return this.canvas;
},
// for each tile shown on the map request the data
onTileAdded: function(t) {
var self = this;
this.provider.getTileData(t, t.zoom, function(tileData) {
self._tileLoaded(t, tileData);
self.redraw();
});
},
/**
* render the selectef key
* don't call this function directly, it's called by
* requestAnimationFrame. Use redraw to refresh it
*/
render: function() {
var t, tile, pos;
var canvas = this.canvas;
canvas.width = canvas.width;
var ctx = canvas.getContext('2d');
// renders only a "frame"
for(t in this._tiles) {
tile = this._tiles[t];
pos = this.getTilePos(tile.coord);
ctx.setTransform(1, 0, 0, 1, pos.x, pos.y);
this.renderer.renderTile(tile, this.key, pos.x, pos.y);
}
},
/**
* set key to be shown. If it's a single value
* it renders directly, if it's an array it renders
* accumulated
*/
setKey: function(key) {
this.key = key;
this.animator.step(key);
this.redraw();
this.fire('change:time', { time: this.getTime(), step: this.key });
},
/**
* helper function, does the same than ``setKey`` but only
* accepts scalars.
*/
setStep: function(time) {
if(time === undefined || time.length !== undefined) {
throw new Error("setTime only accept scalars");
}
this.setKey(time);
},
/**
* transform from animation step to Date object
* that contains the animation time
*
* ``step`` should be between 0 and ``steps - 1``
*/
stepToTime: function(step) {
if (!this.provider) return 0;
var times = this.provider.getKeySpan();
var time = times.start + (times.end - times.start)*(step/this.options.steps);
return new Date(time*1000);
},
/**
* returns the animation time defined by the data
* in the defined column. Date object
*/
getTime: function() {
return this.stepToTime(this.key);
},
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
if (!this.renderer) {
this.cartocss = cartocss;
return this;
}
this.renderer.setCartoCSS(cartocss);
this.redraw();
return this;
},
redraw: function() {
this.scheduleUpdate();
}
});
function GMapsTiledTorqueLayer(options) {
this.options = _.extend({}, options);
CanvasTileLayer.call(this, this._loadTile.bind(this), this.drawTile.bind(this));
this.initialize(options);
}
GMapsTiledTorqueLayer.prototype = _.extend({}, CanvasTileLayer.prototype, {
providers: {
'sql_api': torque.providers.json,
'url_template': torque.providers.JsonArray
},
renderers: {
'point': torque.renderer.Point,
'pixel': torque.renderer.Rectangle
},
initialize: function(options) {
var self = this;
this.key = 0;
this.options.renderer = this.options.renderer || 'pixel';
this.options.provider = this.options.provider || 'sql_api';
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](null, options);
},
_tileLoaded: function(tile, tileData) {
tile.data = tileData;
this.drawTile(tile);
},
_loadTile: function(tile, coord, zoom) {
var self = this;
var limit = 1 << zoom;
// wrap tile
var wrappedCoord = {
x: ((coord.x % limit) + limit) % limit,
y: coord.y
};
this.provider.getTileData(wrappedCoord, zoom, function(tileData) {
self._tileLoaded(tile, tileData);
});
},
drawTile: function (tile) {
var canvas = tile.canvas;
if(!tile.data) return;
canvas.width = canvas.width;
this.renderer.setCanvas(canvas);
var accum = this.renderer.accumulate(tile.data, this.key);
this.renderer.renderTileAccum(accum, 0, 0);
},
setKey: function(key) {
this.key = key;
this.redraw();
},
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
if (!this.renderer) throw new Error('renderer is not valid');
return this.renderer.setCartoCSS(cartocss);
}
});
exports.torque.GMapsTiledTorqueLayer = GMapsTiledTorqueLayer;
exports.torque.GMapsTorqueLayer = GMapsTorqueLayer;
})(typeof exports === "undefined" ? this : exports);
if(typeof(L) !== 'undefined') {
L.Mixin.TileLoader = {
_initTileLoader: function() {
this._tiles = {}
this._tilesToLoad = 0;
this._map.on({
'moveend': this._updateTiles
}, this);
this._updateTiles();
},
_removeTileLoader: function() {
map.off({
'moveend': this._updateTiles
}, this);
this._removeTiles();
},
_updateTiles: function () {
if (!this._map) { return; }
var bounds = this._map.getPixelBounds(),
zoom = this._map.getZoom(),
tileSize = this.options.tileSize;
if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
return;
}
var nwTilePoint = new L.Point(
Math.floor(bounds.min.x / tileSize),
Math.floor(bounds.min.y / tileSize)),
seTilePoint = new L.Point(
Math.floor(bounds.max.x / tileSize),
Math.floor(bounds.max.y / tileSize)),
tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
this._addTilesFromCenterOut(tileBounds);
this._removeOtherTiles(tileBounds);
},
_removeTiles: function (bounds) {
for (var key in this._tiles) {
this._removeTile(key);
}
},
_removeOtherTiles: function (bounds) {
var kArr, x, y, z, key;
var zoom = this._map.getZoom();
for (key in this._tiles) {
if (this._tiles.hasOwnProperty(key)) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
z = parseInt(kArr[2], 10);
// remove tile if it's out of bounds
if (zoom !== z || x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
this._removeTile(key);
}
}
}
},
_removeTile: function (key) {
this.fire('tileRemoved', this._tiles[key]);
delete this._tiles[key];
},
_tileShouldBeLoaded: function (tilePoint) {
return !((tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom) in this._tiles);
},
_tileLoaded: function(tilePoint, tileData) {
this._tilesToLoad--;
this._tiles[tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom] = tileData;
if(this._tilesToLoad === 0) {
this.fire("tilesLoaded");
}
},
getTilePos: function (tilePoint) {
tilePoint = new L.Point(tilePoint.x, tilePoint.y);
var origin = this._map._getNewTopLeftPoint(this._map.getCenter()),
tileSize = this.options.tileSize;
return tilePoint.multiplyBy(tileSize).subtract(origin);
},
_addTilesFromCenterOut: function (bounds) {
var queue = [],
center = bounds.getCenter(),
zoom = this._map.getZoom();
var j, i, point;
for (j = bounds.min.y; j <= bounds.max.y; j++) {
for (i = bounds.min.x; i <= bounds.max.x; i++) {
point = new L.Point(i, j);
point.zoom = zoom;
if (this._tileShouldBeLoaded(point)) {
queue.push(point);
}
}
}
var tilesToLoad = queue.length;
if (tilesToLoad === 0) { return; }
// load tiles in order of their distance to center
queue.sort(function (a, b) {
return a.distanceTo(center) - b.distanceTo(center);
});
this._tilesToLoad += tilesToLoad;
for (i = 0; i < tilesToLoad; i++) {
this.fire('tileAdded', queue[i]);
}
}
}
} //L defined
if(typeof(L) !== 'undefined') {
/**
* full canvas layer implementation for Leaflet
*/
L.CanvasLayer = L.Class.extend({
includes: [L.Mixin.Events, L.Mixin.TileLoader],
options: {
minZoom: 0,
maxZoom: 28,
tileSize: 256,
subdomains: 'abc',
errorTileUrl: '',
attribution: '',
zoomOffset: 0,
opacity: 1,
unloadInvisibleTiles: L.Browser.mobile,
updateWhenIdle: L.Browser.mobile,
tileLoader: false // installs tile loading events
},
initialize: function (options) {
var self = this;
//this.project = this._project.bind(this);
this.render = this.render.bind(this);
L.Util.setOptions(this, options);
this._canvas = document.createElement('canvas');
this._ctx = this._canvas.getContext('2d');
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
this.requestAnimationFrame = requestAnimationFrame;
},
onAdd: function (map) {
this._map = map;
this._staticPane = map._createPane('leaflet-tile-pane', map._container);
this._staticPane.appendChild(this._canvas);
map.on({
'viewreset': this._reset
//'move': this._render
}, this);
map.on('move', this._render, this);//function(){ console.log("a"); }, this);
if(this.options.tileLoader) {
this._initTileLoader();
}
this._reset();
},
getCanvas: function() {
return this._canvas;
},
draw: function() {
return this._reset();
},
onRemove: function (map) {
map._container.removeChild(this._staticPane);
map.off({
'viewreset': this._reset,
'move': this._render
}, this);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
bringToFront: function () {
return this;
},
bringToBack: function () {
return this;
},
_reset: function () {
var size = this._map.getSize();
this._canvas.width = size.x;
this._canvas.height = size.y;
this.onResize();
this._render();
},
/*
_project: function(x) {
var point = this._map.latLngToLayerPoint(new L.LatLng(x[1], x[0]));
return [point.x, point.y];
},
*/
_updateOpacity: function () { },
_render: function() {
this.requestAnimationFrame.call(window, this.render);
},
redraw: function() {
this._render();
},
onResize: function() {
},
render: function() {
throw new Error('render function should be implemented');
}
});
} //L defined
if(typeof(L) !== 'undefined') {
/**
* torque layer
*/
L.TorqueLayer = L.CanvasLayer.extend({
providers: {
'sql_api': torque.providers.json,
'url_template': torque.providers.jsonarray
},
renderers: {
'point': torque.renderer.Point,
'pixel': torque.renderer.Rectangle
},
initialize: function(options) {
var self = this;
options.tileLoader = true;
this.key = 0;
options.resolution = options.resolution || 2;
options.steps = options.steps || 100;
this.animator = new torque.Animator(function(time) {
var k = time | 0;
if(self.key !== k) {
self.setKey(k);
}
}, options);
this.play = this.animator.start.bind(this.animator);
this.stop = this.animator.stop.bind(this.animator);
this.pause = this.animator.pause.bind(this.animator);
this.toggle = this.animator.toggle.bind(this.animator);
L.CanvasLayer.prototype.initialize.call(this, options);
this.options.renderer = this.options.renderer || 'point';
this.options.provider = this.options.provider || 'sql_api';
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options);
// 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) {
self._tileLoaded(t, tileData);
self.redraw();
});
}, this);
},
/**
* render the selectef key
* don't call this function directly, it's called by
* requestAnimationFrame. Use redraw to refresh it
*/
render: function() {
var t, tile, pos;
var canvas = this.getCanvas();
canvas.width = canvas.width;
var ctx = canvas.getContext('2d');
for(t in this._tiles) {
tile = this._tiles[t];
pos = this.getTilePos(tile.coord);
ctx.setTransform(1, 0, 0, 1, pos.x, pos.y);
this.renderer.renderTile(tile, this.key, pos.x, pos.y);
}
},
/**
* set key to be shown. If it's a single value
* it renders directly, if it's an array it renders
* accumulated
*/
setKey: function(key) {
this.key = key;
this.animator.step(key);
this.redraw();
this.fire('change:time', { time: this.getTime(), step: this.key });
},
/**
* helper function, does the same than ``setKey`` but only
* accepts scalars.
*/
setStep: function(time) {
if(time === undefined || time.length !== undefined) {
throw new Error("setTime only accept scalars");
}
this.setKey(time);
},
/**
* transform from animation step to Date object
* that contains the animation time
*
* ``step`` should be between 0 and ``steps - 1``
*/
stepToTime: function(step) {
var times = this.provider.getKeySpan();
var time = times.start + (times.end - times.start)*(step/this.options.steps);
return new Date(time*1000);
},
/**
* returns the animation time defined by the data
* in the defined column. Date object
*/
getTime: function() {
return this.stepToTime(this.key);
},
/**
* returns an object with the start and end times
*/
getTimeSpan: function() {
var times = this.provider.getKeySpan();
},
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
if (!this.renderer) throw new Error('renderer is not valid');
this.renderer.setCartoCSS(cartocss);
this.redraw();
return this;
}
});
//_.extend(L.TorqueLayer.prototype, torque.Event);
L.TiledTorqueLayer = L.TileLayer.Canvas.extend({
providers: {
'sql_api': torque.providers.json,
'url_template': torque.providers.JsonArray
},
renderers: {
'point': torque.renderer.Point,
'pixel': torque.renderer.Rectangle
},
initialize: function(options) {
var self = this;
this.key = 0;
options.async = true;
L.TileLayer.Canvas.prototype.initialize.call(this, options);
this.options.renderer = this.options.renderer || 'pixel';
this.options.provider = this.options.provider || 'sql_api';
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](null, options);
},
_tileLoaded: function(tile, tilePoint, tileData) {
if(this._tiles[tilePoint.x + ':' + tilePoint.y] !== undefined) {
this._tiles[tilePoint.x + ':' + tilePoint.y].data = tileData;
this.drawTile(tile);
}
L.TileLayer.Canvas.prototype._tileLoaded.call(this);
},
redraw: function() {
for (var i in this._tiles) {
this._redrawTile(this._tiles[i]);
}
},
_loadTile: function(tile, tilePoint) {
var self = this;
L.TileLayer.Canvas.prototype._loadTile.apply(this, arguments);
// get the data from adjusted point but render in the right canvas
var adjusted = tilePoint.clone()
this._adjustTilePoint(adjusted);
this.provider.getTileData(adjusted, this._map.getZoom(), function(tileData) {
self._tileLoaded(tile, tilePoint, tileData);
L.DomUtil.addClass(tile, 'leaflet-tile-loaded');
});
},
drawTile: function (tile) {
var canvas = tile;
if(!tile.data) return;
canvas.width = canvas.width;
this.renderer.setCanvas(canvas);
var accum = this.renderer.accumulate(tile.data, this.key);
this.renderer.renderTileAccum(accum, 0, 0);
},
setKey: function(key) {
this.key = key;
this.redraw();
},
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
if (!this.renderer) throw new Error('renderer is not valid');
return this.renderer.setCartoCSS(cartocss);
}
});
} //L defined