torque/dist/torque.uncompressed.js

3915 lines
108 KiB
JavaScript
Raw Normal View History

2013-08-06 23:48:25 +08:00
(function(exports) {
2013-08-29 21:22:57 +08:00
exports.torque = exports.torque || {};
2013-09-11 19:21:33 +08:00
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(c) { setTimeout(c, 16); }
2013-08-06 23:48:25 +08:00
2013-11-23 01:31:04 +08:00
var cancelAnimationFrame = window.requestAnimationFrame || window.mozCancelAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(c) { };
2013-08-06 23:48:25 +08:00
/**
* 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;
2013-08-29 21:22:57 +08:00
_.defaults(this.options, {
animationDelay: 0,
maxDelta: 0.2,
loop: true
});
2013-08-06 23:48:25 +08:00
2013-10-11 17:09:30 +08:00
this.rescale();
2013-08-06 23:48:25 +08:00
}
Animator.prototype = {
start: function() {
this.running = true;
2013-08-29 21:22:57 +08:00
requestAnimationFrame(this._tick);
2013-08-06 23:48:25 +08:00
},
2013-10-29 01:41:27 +08:00
isRunning: function() {
return this.running;
},
2013-08-06 23:48:25 +08:00
stop: function() {
2013-08-29 21:22:57 +08:00
this.pause();
2013-10-11 17:09:30 +08:00
this.time(0);
},
// real animation time
time: function(_) {
if (!arguments.length) return this._time;
this._time = _;
2013-08-29 21:22:57 +08:00
var t = this.range(this.domain(this._time));
this.callback(t);
},
toggle: function() {
if (this.running) {
this.pause()
} else {
this.start()
}
},
2013-10-11 17:09:30 +08:00
rescale: function() {
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();
2013-10-29 01:41:27 +08:00
this.time(this._time);
2013-10-11 17:09:30 +08:00
return this;
},
duration: function(_) {
if (!arguments.length) return this.options.animationDuration;
this.options.animationDuration = _;
if (this.time() > _) {
this.time(0);
}
2013-10-29 01:41:27 +08:00
this.rescale();
2013-10-11 17:09:30 +08:00
return this;
},
2013-10-29 01:41:27 +08:00
steps: function(_) {
this.options.steps = _;
return this.rescale();
},
2013-09-11 19:21:33 +08:00
step: function(s) {
if(arguments.length === 0) return this.range(this.domain(this._time));
this._time = this.domainInv(this.rangeInv(s));
},
2013-08-29 21:22:57 +08:00
pause: function() {
this.running = false;
cancelAnimationFrame(this._tick);
2013-08-06 23:48:25 +08:00
},
_tick: function() {
var t1 = +new Date();
var delta = (t1 - this._t0)*0.001;
2013-08-29 21:22:57 +08:00
// if delta is really big means the tab lost the focus
// at some point, so limit delta change
2013-09-11 19:21:33 +08:00
delta = Math.min(this.options.maxDelta, delta);
2013-08-06 23:48:25 +08:00
this._t0 = t1;
this._time += delta;
2013-10-11 17:09:30 +08:00
this.time(this._time);
if(this.step() >= this.options.steps) {
2013-08-29 21:22:57 +08:00
this._time = 0;
}
if(this.running) {
requestAnimationFrame(this._tick);
}
2013-08-06 23:48:25 +08:00
}
};
2013-08-29 21:22:57 +08:00
exports.torque.Animator = Animator;
2013-08-06 23:48:25 +08:00
})(typeof exports === "undefined" ? this : exports);
2013-08-01 18:04:39 +08:00
(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" //
]
}
},
2013-11-23 01:31:04 +08:00
"layer" : {
"buffer-size": {
"default-value": "0",
"type":"float",
"default-meaning": "No buffer will be used",
"doc": "Extra tolerance around the Layer extent (in pixels) used to when querying and (potentially) clipping the layer data during rendering"
},
2013-11-25 20:07:34 +08:00
"-torque-frame-count": {
2013-11-23 01:31:04 +08:00
"default-value": "128",
"type":"number",
2013-11-25 20:07:34 +08:00
"default-meaning": "the data is broken into 128 time frames",
"doc": "Number of animation steps/frames used in the animation. If the data contains a fewere number of total frames, the lesser value will be used."
2013-11-23 01:31:04 +08:00
},
"-torque-resolution": {
"default-value": "2",
"type":"number",
"default-meaning": "",
"doc": "Spatial resolution in pixels. A resolution of 1 means no spatial aggregation of the data. Any other resolution of N results in spatial aggregation into cells of NxN pixels. The value N must be power of 2"
},
"-torque-animation-duration": {
"default-value": "30",
"type":"number",
"default-meaning": "the animation lasts 30 seconds",
"doc": "Animation duration in seconds"
},
"-torque-aggregation-function": {
"default-value": "count(cartodb_id)",
"type": "string",
"default-meaning": "the value for each cell is the count of points in that cell",
"doc": "A function used to calculate a value from the aggregate data for each cell. See -torque-resolution"
},
"-torque-time-attribute": {
"default-value": "time",
"type": "string",
"default-meaning": "the data column in your table that is of a time based type",
"doc": "The table column that contains the time information used create the animation"
},
"-torque-data-aggregation": {
"default-value": "linear",
"type": [
"cumulative"
],
"default-meaning": "previous values are discarded",
"doc": "A linear animation will discard previous values while a cumulative animation will accumulate them until it restarts"
}
},
2013-08-01 18:04:39 +08:00
"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"
}
},
2013-08-29 21:22:57 +08:00
"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"
}
},
2013-08-01 18:04:39 +08:00
"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"
2013-11-23 01:31:04 +08:00
},
"marker-type": {
"css": "marker-type",
"type": [
"rectangle",
"ellipse"
],
"default-value": "ellipse",
"doc": "The default marker-type. If a SVG file is not given as the marker-file parameter, the renderer provides either an rectangle or an ellipse (a circle if height is equal to width)"
2013-08-01 18:04:39 +08:00
}
},
"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
}
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
})(typeof exports === "undefined" ? this : exports);
//
// common functionallity for torque layers
//
(function(exports) {
function TorqueLayer() {}
TorqueLayer.prototype = {
};
TorqueLayer.optionsFromLayer = function(mapConfig) {
var opts = {};
if (!mapConfig) return opts;
var attrs = {
'buffer-size': 'buffer-size',
2013-11-25 20:07:34 +08:00
'-torque-frame-count': 'steps',
2013-11-23 01:31:04 +08:00
'-torque-resolution': 'resolution',
'-torque-animation-duration': 'animationDuration',
'-torque-aggregation-function': 'countby',
'-torque-time-attribute': 'column',
'-torque-data-aggregation': 'data_aggregation'
};
for (var i in attrs) {
var v = mapConfig.eval(i);
if (v !== undefined) {
var a = attrs[i];
opts[a] = v;
}
}
return opts;
};
TorqueLayer.optionsFromCartoCSS = function(cartocss) {
var shader = new carto.RendererJS().render(cartocss);
var mapConfig = shader.findLayer({ name: 'Map' });
return TorqueLayer.optionsFromLayer(mapConfig);
};
exports.torque.common = torque.common || {};
exports.torque.common.TorqueLayer = TorqueLayer;
2013-08-01 18:04:39 +08:00
})(typeof exports === "undefined" ? this : exports);
2013-09-11 19:21:33 +08:00
(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);
}
2013-10-29 01:41:27 +08:00
while((i = remove.pop()) !== undefined) c.splice(i, 1);
};
Event.callbacks = function(evt) {
return (this._evt_callbacks && this._evt_callbacks[evt]) || [];
2013-09-11 19:21:33 +08:00
};
exports.torque.Event = Event;
// types
exports.torque.types = {
Uint8Array: typeof(window['Uint8Array']) !== 'undefined' ? window.Uint8Array : Array,
Uint32Array: typeof(window['Uint32Array']) !== 'undefined' ? window.Uint32Array : Array,
2013-11-05 15:57:40 +08:00
Int32Array: typeof(window['Int32Array']) !== 'undefined' ? window.Int32Array: Array
};
exports.torque.isBrowserSupported = function() {
return !!document.createElement('canvas');
2013-09-11 19:21:33 +08:00
};
})(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);
2013-08-01 18:04:39 +08:00
/*
# 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,
2013-11-23 01:31:04 +08:00
min: Number.MAX_VALUE,
2013-08-01 18:04:39 +08:00
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;
2013-07-30 16:45:35 +08:00
};
Profiler.print_stats = function () {
2013-08-01 18:04:39 +08:00
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();
2013-08-29 21:22:57 +08:00
return this;
2013-08-01 18:04:39 +08:00
},
// 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;
2013-07-30 16:45:35 +08:00
}
2013-08-01 18:04:39 +08:00
var elapsed = this._elapsed();
if(elapsed > 1) {
Profiler.new_value(this.name, this.count);
this.count = 0;
this.start();
}
}
2013-07-30 16:45:35 +08:00
};
2013-08-01 18:04:39 +08:00
Profiler.metric = function(name) {
return new Metric(name);
2013-07-30 16:45:35 +08:00
};
2013-08-01 18:04:39 +08:00
exports.Profiler = Profiler;
})(typeof exports === "undefined" ? this : exports);
2013-07-30 16:45:35 +08:00
(function(exports) {
2013-09-11 19:21:33 +08:00
var torque = exports.torque = exports.torque || {};
2013-07-30 16:45:35 +08:00
var providers = exports.torque.providers = exports.torque.providers || {};
2013-09-11 19:21:33 +08:00
var Uint8Array = torque.types.Uint8Array;
var Int32Array = torque.types.Int32Array;
var Uint32Array = torque.types.Uint32Array;
2013-07-30 16:45:35 +08:00
// 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) {
2013-09-30 19:31:27 +08:00
this._ready = false;
this._tileQueue = [];
this.options = options;
2013-07-30 16:45:35 +08:00
2013-09-30 19:31:27 +08:00
this.options.is_time = this.options.is_time === undefined ? true: this.options.is_time;
2013-11-09 00:09:23 +08:00
this.options.tiler_protocol = options.tiler_protocol || 'http';
this.options.tiler_domain = options.tiler_domain || 'cartodb.com';
this.options.tiler_port = options.tiler_port || 80;
2013-09-28 00:56:03 +08:00
2013-11-23 01:31:04 +08:00
if (this.options.data_aggregation) {
this.options.cumulative = this.options.data_aggregation === 'cumulative';
}
2013-09-30 19:31:27 +08:00
// 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);
}
};
2013-09-28 00:56:03 +08:00
2013-09-30 19:31:27 +08:00
json.prototype = {
2013-09-28 00:56:03 +08:00
2013-07-30 16:45:35 +08:00
/**
* 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) {
2013-09-11 19:21:33 +08:00
var r;
2013-07-30 16:45:35 +08:00
var x = new Uint8Array(rows.length);
var y = new Uint8Array(rows.length);
2013-11-23 01:31:04 +08:00
var prof_mem = Profiler.metric('ProviderJSON:mem');
var prof_point_count = Profiler.metric('ProviderJSON:point_count');
var prof_process_time = Profiler.metric('ProviderJSON:process_time').start()
2013-09-11 19:21:33 +08:00
2013-07-30 16:45:35 +08:00
// count number of dates
var dates = 0;
2013-11-23 01:31:04 +08:00
var maxDateSlots = -1;
2013-09-11 19:21:33 +08:00
for (r = 0; r < rows.length; ++r) {
2013-07-30 16:45:35 +08:00
var row = rows[r];
2013-09-11 19:21:33 +08:00
dates += row.dates__uint16.length;
for(var d = 0; d < row.dates__uint16.length; ++d) {
maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]);
}
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
if(this.options.cumulative) {
dates = (1 + maxDateSlots) * rows.length;
}
var type = this.options.cumulative ? Uint32Array: Uint8Array;
2013-07-30 16:45:35 +08:00
// reserve memory for all the dates
2013-09-11 19:21:33 +08:00
var timeIndex = new Int32Array(maxDateSlots + 1); //index-size
var timeCount = new Int32Array(maxDateSlots + 1);
2013-11-23 01:31:04 +08:00
var renderData = new (this.options.valueDataType || type)(dates);
2013-07-30 16:45:35 +08:00
var renderDataPos = new Uint32Array(dates);
2013-11-23 01:31:04 +08:00
prof_mem.inc(
4 * maxDateSlots + // timeIndex
4 * maxDateSlots + // timeCount
dates + //renderData
dates * 4
); //renderDataPos
prof_point_count.inc(rows.length);
2013-09-11 19:21:33 +08:00
var rowsPerSlot = {};
2013-07-30 16:45:35 +08:00
// precache pixel positions
for (var r = 0; r < rows.length; ++r) {
var row = rows[r];
2013-09-11 19:21:33 +08:00
x[r] = row.x__uint8 * this.options.resolution;
2013-11-23 01:31:04 +08:00
// fix value when it's in the tile EDGE
// TODO: this should be fixed in SQL query
if (row.y__uint8 === -1) {
y[r] = 0;
} else {
y[r] = row.y__uint8 * this.options.resolution;
}
2013-07-30 16:45:35 +08:00
2013-09-11 19:21:33 +08:00
var dates = row.dates__uint16;
var vals = row.vals__uint8;
2013-11-23 01:31:04 +08:00
if (!this.options.cumulative) {
for (var j = 0, len = dates.length; j < len; ++j) {
var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []);
if(this.options.cumulative) {
vals[j] += prev_val;
}
prev_val = vals[j];
rr.push([r, vals[j]]);
}
} else {
var valByDate = {}
for (var j = 0, len = dates.length; j < len; ++j) {
valByDate[dates[j]] = vals[j];
}
var accum = 0;
// extend the latest to the end
for (var j = dates[0]; j <= maxDateSlots; ++j) {
var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []);
var v = valByDate[j];
if (v) {
accum += v;
}
rr.push([r, accum]);
}
/*var lastDateSlot = dates[dates.length - 1];
for (var j = lastDateSlot + 1; j <= maxDateSlots; ++j) {
var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []);
rr.push([r, prev_val]);
}
*/
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
2013-07-30 16:45:35 +08:00
}
// for each timeslot search active buckets
var renderDataIndex = 0;
var timeSlotIndex = 0;
2013-09-11 19:21:33 +08:00
var i = 0;
for(var i = 0; i <= maxDateSlots; ++i) {
2013-07-30 16:45:35 +08:00
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;
}
2013-11-23 01:31:04 +08:00
prof_process_time.end();
2013-07-30 16:45:35 +08:00
return {
x: x,
y: y,
2013-11-25 23:36:58 +08:00
z: zoom,
2013-07-30 16:45:35 +08:00
coord: {
x: coord.x,
y: coord.y,
2013-09-11 19:21:33 +08:00
z: zoom
2013-07-30 16:45:35 +08:00
},
timeCount: timeCount,
timeIndex: timeIndex,
renderDataPos: renderDataPos,
2013-11-23 01:31:04 +08:00
renderData: renderData,
maxDate: maxDateSlots
2013-07-30 16:45:35 +08:00
};
},
2013-11-01 02:07:31 +08:00
_host: function() {
2013-10-11 17:09:30 +08:00
var opts = this.options;
var port = opts.sql_api_port;
2013-10-31 21:41:23 +08:00
var domain = ((opts.user_name || opts.user) + '.' + (opts.sql_api_domain || 'cartodb.com')) + (port ? ':' + port: '');
2013-10-11 17:09:30 +08:00
var protocol = opts.sql_api_protocol || 'http';
return this.options.url || protocol + '://' + domain + '/api/v2/sql';
2013-07-30 16:45:35 +08:00
},
2013-11-01 02:07:31 +08:00
url: function(subhost) {
var opts = this.options;
var protocol = opts.sql_api_protocol || 'http';
if (!this.options.cdn_url) {
return this._host();
}
var h = protocol+ "://";
if (subhost) {
h += subhost + ".";
}
var cdn_host = opts.cdn_url;
if(!cdn_host.http && !cdn_host.https) {
throw new Error("cdn_host should contain http and/or https entries");
}
2013-11-09 00:09:23 +08:00
h += cdn_host[protocol] + "/" + (opts.user_name || opts.user) + '/api/v2/sql';
2013-11-01 02:07:31 +08:00
return h;
},
_hash: function(str) {
var hash = 0;
if (!str || str.length == 0) return hash;
for (var i = 0, l = str.length; i < l; ++i) {
hash = (( (hash << 5 ) - hash ) + str.charCodeAt(i)) | 0;
}
return hash;
},
2013-10-29 01:41:27 +08:00
_extraParams: function() {
if (this.options.extra_params) {
var p = [];
for(var k in this.options.extra_params) {
var v = this.options.extra_params[k];
2013-11-09 00:09:23 +08:00
if (v) {
p.push(k + "=" + encodeURIComponent(v));
}
2013-10-29 01:41:27 +08:00
}
return p.join('&');
}
return null;
},
2013-07-30 16:45:35 +08:00
2013-11-11 23:46:42 +08:00
isHttps: function() {
return this.options.sql_api_protocol && this.options.sql_api_protocol === 'https';
},
2013-07-30 16:45:35 +08:00
// execute actual query
sql: function(sql, callback, options) {
2013-11-09 00:09:23 +08:00
options = options || {};
2013-11-01 02:07:31 +08:00
var subdomains = this.options.subdomains || '0123';
2013-11-11 23:46:42 +08:00
if(this.isHttps()) {
subdomains = [null]; // no subdomain
}
2013-11-09 00:09:23 +08:00
var url;
if (options.no_cdn) {
url = this._host();
} else {
url = this.url(subdomains[Math.abs(this._hash(sql))%subdomains.length]);
}
2013-10-29 01:41:27 +08:00
var extra = this._extraParams();
2013-11-01 02:07:31 +08:00
torque.net.get( url + "?q=" + encodeURIComponent(sql) + (extra ? "&" + extra: ''), function (data) {
2013-07-30 16:45:35 +08:00
if(options.parseJSON) {
2013-10-29 01:41:27 +08:00
data = JSON.parse(data && data.responseText);
2013-07-30 16:45:35 +08:00
}
2013-10-29 01:41:27 +08:00
callback && callback(data);
2013-07-30 16:45:35 +08:00
});
},
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();
2013-09-30 19:31:27 +08:00
this.options.ready && this.options.ready();
2013-07-30 16:45:35 +08:00
},
_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) {
2013-11-23 01:31:04 +08:00
var prof_fetch_time = Profiler.metric('ProviderJSON:tile_fetch_time').start()
2013-07-30 16:45:35 +08:00
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" +
2013-11-23 01:31:04 +08:00
", 256/{resolution} as tile_size" +
2013-07-30 16:45:35 +08:00
", 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" +
2013-10-29 01:41:27 +08:00
" FROM ({_sql}) i, par p " +
2013-07-30 16:45:35 +08:00
" WHERE i.the_geom_webmercator && p.ext " +
" GROUP BY g, d" +
") " +
"" +
2013-11-23 01:31:04 +08:00
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
2013-07-30 16:45:35 +08:00
" array_agg(c) vals__uint8," +
" array_agg(d) dates__uint16" +
2013-11-23 01:31:04 +08:00
// 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";
2013-07-30 16:45:35 +08:00
var query = format(sql, this.options, {
zoom: zoom,
x: coord.x,
y: coord.y,
2013-10-29 01:41:27 +08:00
column_conv: column_conv,
_sql: this.getSQL()
2013-07-30 16:45:35 +08:00
});
var self = this;
this.sql(query, function (data) {
2013-11-07 22:54:10 +08:00
if (data) {
var rows = JSON.parse(data.responseText).rows;
callback(self.proccessTile(rows, coord, zoom));
} else {
callback(null);
}
2013-11-23 01:31:04 +08:00
prof_fetch_time.end();
2013-07-30 16:45:35 +08:00
});
},
2013-09-11 19:21:33 +08:00
getKeySpan: function() {
return {
2013-11-08 23:14:52 +08:00
start: this.options.start * 1000,
end: this.options.end * 1000,
2013-09-11 19:21:33 +08:00
step: this.options.step,
2013-11-11 23:46:42 +08:00
steps: this.options.steps,
columnType: this.options.is_time ? 'date': 'number'
2013-09-11 19:21:33 +08:00
};
},
2013-10-29 01:41:27 +08:00
setColumn: function(column, isTime) {
this.options.column = column;
this.options.is_time = isTime === undefined ? true: false;
2013-11-23 01:31:04 +08:00
this.reload();
},
setResolution: function(res) {
this.options.resolution = res;
},
// return true if tiles has been changed
setOptions: function(opt) {
var refresh = false;
if(opt.resolution !== undefined && opt.resolution !== this.options.resolution) {
this.options.resolution = opt.resolution;
refresh = true;
}
if(opt.steps !== undefined && opt.steps !== this.options.steps) {
this.setSteps(opt.steps, { silent: true });
refresh = true;
}
if(opt.column !== undefined && opt.column !== this.options.column) {
this.options.column = opt.column;
refresh = true;
}
2013-12-12 21:03:35 +08:00
if(opt.countby !== undefined && opt.countby !== this.options.countby) {
this.options.countby = opt.countby;
refresh = true;
}
2013-11-23 01:31:04 +08:00
if(opt.data_aggregation !== undefined) {
var c = opt.data_aggregation === 'cumulative';
if (this.options.cumulative !== c) {
this.options.cumulative = c;
refresh = true;
}
}
if (refresh) this.reload();
return refresh;
},
reload: function() {
2013-10-29 01:41:27 +08:00
this._ready = false;
this._fetchKeySpan();
},
setSQL: function(sql) {
if (this.options.sql != sql) {
this.options.sql = sql;
2013-11-23 01:31:04 +08:00
this.reload();
2013-10-29 01:41:27 +08:00
}
},
2013-11-07 22:54:10 +08:00
getSteps: function() {
return Math.min(this.options.steps, this.options.data_steps);
},
2013-11-23 01:31:04 +08:00
setSteps: function(steps, opt) {
opt = opt || {};
2013-10-11 17:09:30 +08:00
if (this.options.steps !== steps) {
this.options.steps = steps;
2013-11-07 22:54:10 +08:00
this.options.step = (this.options.end - this.options.start)/this.getSteps();
this.options.step = this.options.step || 1;
2013-11-23 01:31:04 +08:00
if (!opt.silent) this.reload();
2013-10-11 17:09:30 +08:00
}
},
2013-09-30 19:31:27 +08:00
getBounds: function() {
return this.options.bounds;
},
2013-10-29 01:41:27 +08:00
getSQL: function() {
return this.options.sql || "select * from " + this.options.table;
},
2013-11-09 00:09:23 +08:00
_tilerHost: function() {
var opts = this.options;
var user = (opts.user_name || opts.user);
return opts.tiler_protocol +
"://" + (user ? user + "." : "") +
opts.tiler_domain +
((opts.tiler_port != "") ? (":" + opts.tiler_port) : "");
},
_fetchUpdateAt: function(callback) {
var self = this;
var layergroup = {
"version": "1.0.1",
"stat_tag": this.options.stat_tag || 'torque',
"layers": [{
"type": "cartodb",
"options": {
"cartocss_version": "2.1.1",
"cartocss": "#layer {}",
"sql": this.getSQL()
}
}]
};
var url = this._tilerHost() + "/tiles/layergroup";
var extra = this._extraParams();
2013-11-11 23:46:42 +08:00
// tiler needs map_key instead of api_key
// so replace it
if (extra) {
extra = extra.replace('api_key=', 'map_key=');
}
url = url +
"?config=" + encodeURIComponent(JSON.stringify(layergroup)) +
"&callback=?" + (extra ? "&" + extra: '');
torque.net.jsonp(url, function (data) {
2013-11-09 00:09:23 +08:00
var query = format("select * from ({sql}) __torque_wrap_sql limit 0", { sql: self.getSQL() });
self.sql(query, function (queryData) {
2013-11-11 23:46:42 +08:00
if (data) {
callback({
updated_at: data.last_updated,
fields: queryData.fields
});
}
2013-11-09 00:09:23 +08:00
}, { parseJSON: true });
});
},
2013-07-30 16:45:35 +08:00
//
// 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.
//
2013-09-11 19:21:33 +08:00
_fetchKeySpan: function() {
2013-10-29 01:41:27 +08:00
var self = this;
2013-07-30 16:45:35 +08:00
var max_col, min_col, max_tmpl, min_tmpl;
2013-11-09 00:09:23 +08:00
this._fetchUpdateAt(function(data) {
2013-10-29 01:41:27 +08:00
if (!data) return;
2013-11-01 02:07:31 +08:00
self.options.extra_params = self.options.extra_params || {};
2013-11-09 00:09:23 +08:00
self.options.extra_params.last_updated = data.updated_at || 0;
2014-01-09 23:56:06 +08:00
self.options.extra_params.cache_policy = 'persist';
2013-10-29 01:41:27 +08:00
self.options.is_time = data.fields[self.options.column].type === 'date';
2013-11-07 22:54:10 +08:00
var column_conv = self.options.column;
2013-10-29 01:41:27 +08:00
if (self.options.is_time){
max_tmpl = "date_part('epoch', max({column}))";
min_tmpl = "date_part('epoch', min({column}))";
2013-11-07 22:54:10 +08:00
column_conv = format("date_part('epoch', {column})", self.options);
2013-10-29 01:41:27 +08:00
} else {
max_tmpl = "max({column})";
min_tmpl = "min({column})";
}
max_col = format(max_tmpl, { column: self.options.column });
min_col = format(min_tmpl, { column: self.options.column });
2013-11-09 00:09:23 +08:00
/*var sql_stats = "" +
2013-11-08 23:14:52 +08:00
"WITH summary_groups as ( " +
"WITH summary as ( " +
"select (row_number() over (order by __time_col asc nulls last)+1)/2 as rownum, __time_col " +
"from (select *, {column} as __time_col from ({sql}) __s) __torque_wrap_sql " +
"order by __time_col asc " +
") " +
"SELECT " +
"max(__time_col) OVER(PARTITION BY rownum) - " +
"min(__time_col) OVER(PARTITION BY rownum) diff " +
"FROM summary " +
"), subq as ( " +
2013-11-07 22:54:10 +08:00
" SELECT " +
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
"{max_col} max, " +
"{min_col} min FROM ({sql}) __torque_wrap_sql " +
")" +
2013-11-08 23:14:52 +08:00
"SELECT " +
2013-11-07 22:54:10 +08:00
"xmax, xmin, ymax, ymin, a.max as max_date, a.min as min_date, " +
2013-11-08 23:14:52 +08:00
"avg(diff) as diffavg," +
"(a.max - a.min)/avg(diff) as num_steps " +
"FROM summary_groups, subq a " +
2013-11-07 22:54:10 +08:00
"WHERE diff > 0 group by xmax, xmin, ymax, ymin, max_date, min_date";
2013-11-09 00:09:23 +08:00
*/
var sql_stats = " SELECT " +
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
"count(*) as num_steps, " +
"{max_col} max_date, " +
"{min_col} min_date FROM ({sql}) __torque_wrap_sql ";
2013-11-07 22:54:10 +08:00
var sql = format(sql_stats, {
2013-10-29 01:41:27 +08:00
max_col: max_col,
min_col: min_col,
2013-11-07 22:54:10 +08:00
column: column_conv,
2013-10-29 01:41:27 +08:00
sql: self.getSQL()
});
self.sql(sql, function(data) {
//TODO: manage bounds
data = data.rows[0];
2013-11-07 22:54:10 +08:00
self.options.start = data.min_date;
self.options.end = data.max_date;
self.options.step = (data.max_date - data.min_date)/Math.min(self.options.steps, data.num_steps>>0);
self.options.data_steps = data.num_steps >> 0;
// step can't be 0
self.options.step = self.options.step || 1;
2013-11-08 23:14:52 +08:00
self.options.bounds = [
2013-10-29 01:41:27 +08:00
[data.ymin, data.xmin],
2013-11-08 23:14:52 +08:00
[data.ymax, data.xmax]
2013-10-29 01:41:27 +08:00
];
self._setReady(true);
2013-11-09 00:09:23 +08:00
}, { parseJSON: true, no_cdn: true });
}, { parseJSON: true, no_cdn: true})
2013-07-30 16:45:35 +08:00
}
};
2013-09-11 19:21:33 +08:00
torque.providers.json = json;
2013-07-30 16:45:35 +08:00
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
2013-09-11 19:21:33 +08:00
var torque = exports.torque = exports.torque || {};
2013-07-30 16:45:35 +08:00
var providers = exports.torque.providers = exports.torque.providers || {};
2013-09-11 19:21:33 +08:00
var Uint8Array = torque.types.Uint8Array;
var Int32Array = torque.types.Int32Array;
var Uint32Array = torque.types.Uint32Array;
2013-07-30 16:45:35 +08:00
// 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;
};
2013-08-01 18:04:39 +08:00
2013-07-30 16:45:35 +08:00
json.prototype = {
2013-08-01 18:04:39 +08:00
//
// 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;
},
2013-09-11 19:21:33 +08:00
2013-08-01 18:04:39 +08:00
2013-07-30 16:45:35 +08:00
/**
*
*/
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: [],
2013-11-05 15:57:40 +08:00
values: []
2013-07-30 16:45:35 +08:00
};
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]);
}
2013-09-11 19:21:33 +08:00
if(self.options.cumulative) {
2013-07-30 16:45:35 +08:00
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);
2013-08-29 21:22:57 +08:00
var renderData = new (this.options.valueDataType || Uint8Array)(dates);
2013-07-30 16:45:35 +08:00
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;
},
2013-08-01 18:04:39 +08:00
2013-08-29 21:22:57 +08:00
tileUrl: function(coord, zoom) {
2013-08-01 18:04:39 +08:00
var template = this.url();
2013-08-29 21:22:57 +08:00
var s = (this.options.subdomains || 'abcd')[(coord.x + coord.y + zoom) % 4];
return template
2013-08-01 18:04:39 +08:00
.replace('{x}', coord.x)
.replace('{y}', coord.y)
2013-08-29 21:22:57 +08:00
.replace('{z}', zoom)
.replace('{s}', s);
},
getTile: function(coord, zoom, callback) {
var template = this.tileUrl(coord, zoom);
2013-08-01 18:04:39 +08:00
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);
});
},
2013-07-30 16:45:35 +08:00
/**
* `coord` object like {x : tilex, y: tiley }
* `zoom` quadtree zoom level
*/
getTileData: function(coord, zoom, callback) {
2013-08-29 21:22:57 +08:00
var template = this.tileUrl(coord, zoom);
2013-07-30 16:45:35 +08:00
var self = this;
2013-08-01 18:04:39 +08:00
var fetchTime = Profiler.metric('jsonarray:fetch time');
fetchTime.start();
2013-07-30 16:45:35 +08:00
torque.net.get(template, function (data) {
2013-08-01 18:04:39 +08:00
fetchTime.end();
2013-07-30 16:45:35 +08:00
var processed = null;
2013-08-01 18:04:39 +08:00
var processingTime = Profiler.metric('jsonarray:processing time');
2013-08-29 21:22:57 +08:00
var parsingTime = Profiler.metric('jsonarray:parsing time');
2013-07-30 16:45:35 +08:00
try {
2013-08-01 18:04:39 +08:00
processingTime.start();
2013-08-29 21:22:57 +08:00
parsingTime.start();
var rows = JSON.parse(data.responseText || data.response).rows;
parsingTime.end();
2013-07-30 16:45:35 +08:00
processed = self.proccessTile(rows, coord, zoom);
2013-08-01 18:04:39 +08:00
processingTime.end();
2013-07-30 16:45:35 +08:00
} catch(e) {
console.error("problem parsing JSON on ", coord, zoom);
}
2013-08-29 21:22:57 +08:00
callback(processed);
2013-07-30 16:45:35 +08:00
});
}
};
torque.providers.JsonArray = json
})(typeof exports === "undefined" ? this : exports);
(function(exports) {
var torque = exports.torque = exports.torque || {};
torque.net = torque.net || {};
2013-10-29 01:41:27 +08:00
var lastCall = null;
2013-09-11 19:21:33 +08:00
2013-11-11 23:46:42 +08:00
function jsonp(url, callback, options) {
options = options || { timeout: 10000 };
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
// function name
var fnName = 'torque_' + Date.now();
function clean() {
head.removeChild(script);
clearTimeout(timeoutTimer);
delete window[fnName];
}
window[fnName] = function() {
clean();
callback.apply(window, arguments);
};
// timeout for errors
var timeoutTimer = setTimeout(function() {
clean();
callback.call(window, null);
}, options.timeout);
// setup url
url = url.replace('callback=\?', 'callback=' + fnName);
script.type = 'text/javascript';
script.src = url;
script.async = true;
// defer the loading because IE9 loads in the same frame the script
// so Loader._script is null
setTimeout(function() { head.appendChild(script); }, 0);
}
2013-11-09 00:09:23 +08:00
function get(url, callback, options) {
options = options || {
method: 'GET',
data: null
};
2013-10-29 01:41:27 +08:00
lastCall = { url: url, callback: callback };
2013-09-11 19:21:33 +08:00
var request = XMLHttpRequest;
// from d3.js
if (window.XDomainRequest
&& !("withCredentials" in request)
&& /^(http(s)?:)?\/\//.test(url)) request = XDomainRequest;
2013-11-23 01:31:04 +08:00
2013-09-11 19:21:33 +08:00
var req = new request();
2013-11-23 01:31:04 +08:00
req.open(options.method, url, true);
2013-09-11 19:21:33 +08:00
function respond() {
var status = req.status, result;
if (!status && req.responseText || status >= 200 && status < 300 || status === 304) {
callback(req);
} else {
callback(null);
2013-07-30 16:45:35 +08:00
}
2013-09-11 19:21:33 +08:00
}
"onload" in req
? req.onload = req.onerror = respond
: req.onreadystatechange = function() { req.readyState > 3 && respond(); };
2013-07-30 16:45:35 +08:00
2013-11-23 01:31:04 +08:00
req.onprogress = function() {};
2013-07-30 16:45:35 +08:00
//req.responseType = 'arraybuffer';
2013-11-09 00:09:23 +08:00
if (options.data) {
req.setRequestHeader("Content-type", "application/json");
//req.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
req.setRequestHeader("Accept", "*");
}
req.send(options.data);
2013-07-30 16:45:35 +08:00
return req;
}
2013-11-09 00:09:23 +08:00
function post(url, data, callback) {
return get(url, callback, {
data: data,
method: "POST"
});
}
2013-07-30 16:45:35 +08:00
torque.net = {
2013-10-29 01:41:27 +08:00
get: get,
2013-11-09 00:09:23 +08:00
post: post,
2013-11-11 23:46:42 +08:00
jsonp: jsonp,
2013-10-29 01:41:27 +08:00
lastCall: function() { return lastCall; }
2013-07-30 16:45:35 +08:00
};
})(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;
2013-10-11 17:09:30 +08:00
var pixel_size = st['point-radius'];
2013-07-30 16:45:35 +08:00
// render a circle
2013-10-11 17:09:30 +08:00
// fill
2013-07-30 16:45:35 +08:00
ctx.beginPath();
ctx.arc(0, 0, pixel_size, 0, TAU, true, true);
ctx.closePath();
2013-10-11 17:09:30 +08:00
if (st.fillStyle) {
if (st.fillOpacity) {
2013-07-30 16:45:35 +08:00
ctx.globalAlpha = st.fillOpacity;
}
ctx.fill();
}
2013-10-11 17:09:30 +08:00
// stroke
2013-07-30 16:45:35 +08:00
ctx.globalAlpha = 1.0;
2013-10-11 17:09:30 +08:00
if (st.strokeStyle && st.lineWidth) {
if (st.strokeOpacity) {
2013-07-30 16:45:35 +08:00
ctx.globalAlpha = st.strokeOpacity;
}
2013-10-11 17:09:30 +08:00
if (st.lineWidth) {
2013-07-30 16:45:35 +08:00
ctx.lineWidth = st.lineWidth;
}
ctx.strokeStyle = st.strokeStyle;
2013-10-11 17:09:30 +08:00
// do not render for alpha = 0
if (ctx.globalAlpha > 0) {
ctx.stroke();
}
2013-07-30 16:45:35 +08:00
}
}
2013-11-23 01:31:04 +08:00
function renderRectangle(ctx, st) {
ctx.fillStyle = st.fillStyle;
ctx.strokStyle = st.strokStyle;
var pixel_size = st['point-radius'];
// fill
if (st.fillStyle && st.fillOpacity) {
ctx.globalAlpha = st.fillOpacity;
}
ctx.fillRect(0, 0, pixel_size, pixel_size)
// stroke
ctx.globalAlpha = 1.0;
if (st.strokeStyle && st.lineWidth) {
if (st.strokeOpacity) {
ctx.globalAlpha = st.strokeOpacity;
}
if (st.lineWidth) {
ctx.lineWidth = st.lineWidth;
}
ctx.strokeStyle = st.strokeStyle;
// do not render for alpha = 0
if (ctx.globalAlpha > 0) {
ctx.strokeRect(0, 0, pixel_size, pixel_size);
}
}
}
2013-08-01 18:04:39 +08:00
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);
}
2013-10-11 17:09:30 +08:00
exports.torque.cartocss = exports.torque.cartocss || {};
2013-08-01 18:04:39 +08:00
exports.torque.cartocss = {
renderPoint: renderPoint,
2013-11-23 01:31:04 +08:00
renderSprite: renderSprite,
renderRectangle: renderRectangle
2013-08-01 18:04:39 +08:00
};
2013-07-30 16:45:35 +08:00
})(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;',
2013-08-29 21:22:57 +08:00
' marker-width: 4;',
2013-07-30 16:45:35 +08:00
' [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');
2013-09-11 19:21:33 +08:00
this._sprites = []; // sprites per layer
2013-08-29 21:22:57 +08:00
this._shader = null;
2013-07-30 16:45:35 +08:00
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) {
2013-11-23 01:31:04 +08:00
// clean sprites
this.setShader(new carto.RendererJS().render(cartocss));
},
setShader: function(shader) {
2013-07-30 16:45:35 +08:00
// clean sprites
2013-09-11 19:21:33 +08:00
this._sprites = [];
2013-11-23 01:31:04 +08:00
this._shader = shader;
2013-07-30 16:45:35 +08:00
},
2014-01-09 23:56:06 +08:00
clearSpriteCache: function() {
this._sprites = [];
},
2013-07-30 16:45:35 +08:00
//
// generate sprite based on cartocss style
//
2013-08-29 21:22:57 +08:00
generateSprite: function(shader, value, shaderVars) {
var prof = Profiler.metric('PointRenderer:generateSprite').start();
var st = shader.getStyle('canvas-2d', {
2013-07-30 16:45:35 +08:00
value: value
2013-08-29 21:22:57 +08:00
}, shaderVars);
2013-07-30 16:45:35 +08:00
var pointSize = st['point-radius'];
2013-10-11 17:09:30 +08:00
if (!pointSize) {
2013-07-30 16:45:35 +08:00
throw new Error("marker-width property should be set");
}
2013-10-11 17:09:30 +08:00
// take into account the exterior ring to calculate the size
var canvasSize = (st.lineWidth || 0) + pointSize*2;
2013-07-30 16:45:35 +08:00
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
2013-10-11 17:09:30 +08:00
ctx.width = canvas.width = Math.ceil(canvasSize);
ctx.height = canvas.height = Math.ceil(canvasSize);
ctx.translate(canvasSize/2, canvasSize/2);
if(st['point-file'] || st['marker-file']) {
2013-08-01 18:04:39 +08:00
torque.cartocss.renderSprite(ctx, st);
} else {
2013-11-23 01:31:04 +08:00
var mt = st['marker-type'];
if (mt && mt === 'rectangle') {
torque.cartocss.renderRectangle(ctx, st);
} else {
torque.cartocss.renderPoint(ctx, st);
}
2013-08-01 18:04:39 +08:00
}
2013-08-29 21:22:57 +08:00
prof.end();
2013-07-30 16:45:35 +08:00
return canvas;
},
2013-08-29 21:22:57 +08:00
//
// renders all the layers (and frames for each layer) from cartocss
//
renderTile: function(tile, key) {
2013-11-23 01:31:04 +08:00
var layers = this._shader.getLayers();
2013-08-29 21:22:57 +08:00
for(var i = 0, n = layers.length; i < n; ++i ) {
var layer = layers[i];
2013-11-23 01:31:04 +08:00
if (layer.name() !== "Map") {
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);
}
2013-08-29 21:22:57 +08:00
}
}
},
2013-07-30 16:45:35 +08:00
//
// renders a tile in the canvas for key defined in
// the torque tile
//
2013-08-29 21:22:57 +08:00
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
2013-07-30 16:45:35 +08:00
if(!this._canvas) return;
2013-11-23 01:31:04 +08:00
2013-08-29 21:22:57 +08:00
var prof = Profiler.metric('PointRenderer:renderTile').start();
2013-07-30 16:45:35 +08:00
var ctx = this._ctx;
2013-11-23 01:31:04 +08:00
var blendMode = shader.eval('comp-op') || this.options.blendmode;
if(blendMode) {
ctx.globalCompositeOperation = blendMode;
}
if (this.options.cumulative && key > tile.maxDate) {
//TODO: precache because this tile is not going to change
key = tile.maxDate;
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
var tileMax = this.options.resolution * (256/this.options.resolution - 1)
var activePixels = tile.timeCount[key];
2013-07-30 16:45:35 +08:00
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) {
2013-11-25 23:36:58 +08:00
sp = sprites[c] = this.generateSprite(shader, c, _.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars));
2013-07-30 16:45:35 +08:00
}
2013-09-28 00:56:03 +08:00
//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);
2013-11-23 01:31:04 +08:00
var y = tileMax - tile.y[posIdx]; // flip mercator
ctx.drawImage(sp, x, y - (sp.height >> 1));
2013-07-30 16:45:35 +08:00
}
}
}
2013-08-29 21:22:57 +08:00
prof.end();
2013-10-11 17:09:30 +08:00
},
setBlendMode: function(b) {
this.options.blendmode = b;
2013-07-30 16:45:35 +08:00
}
2013-10-11 17:09:30 +08:00
2013-07-30 16:45:35 +08:00
};
// 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 || {};
2013-08-01 18:04:39 +08:00
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');
2013-07-30 16:45:35 +08:00
var TAU = Math.PI * 2;
//
// this renderer just render points depending of the value
//
function RectanbleRenderer(canvas, options) {
this.options = options;
2013-08-01 18:04:39 +08:00
carto.tree.Reference.set(torque['torque-reference']);
2013-07-30 16:45:35 +08:00
this.setCanvas(canvas);
2013-08-01 18:04:39 +08:00
this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS);
2013-07-30 16:45:35 +08:00
}
RectanbleRenderer.prototype = {
2013-08-01 18:04:39 +08:00
//
// 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;
},
2013-07-30 16:45:35 +08:00
setCanvas: function(canvas) {
if(!canvas) return;
this._canvas = canvas;
this._ctx = canvas.getContext('2d');
},
accumulate: function(tile, keys) {
2013-08-29 21:22:57 +08:00
var prof = Profiler.metric('RectangleRender:accumulate').start();
2013-07-30 16:45:35 +08:00
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];
}
}
}
2013-08-29 21:22:57 +08:00
prof.end();
2013-07-30 16:45:35 +08:00
return accum;
},
renderTileAccum: function(accum, px, py) {
2013-08-29 21:22:57 +08:00
var prof = Profiler.metric('RectangleRender:renderTileAccum').start();
var color, x, y, alpha;
2013-07-30 16:45:35 +08:00
var res = this.options.resolution;
var ctx = this._ctx;
var s = (256/res) | 0;
var s2 = s*s;
var colors = this._colors;
2013-08-29 21:22:57 +08:00
if(this.options.blendmode) {
ctx.globalCompositeOperation = this.options.blendmode;
}
var polygon_alpha = this._shader['polygon-opacity'] || function() { return 1.0; };
2013-07-30 16:45:35 +08:00
for(var i = 0; i < s2; ++i) {
var xy = i;
var value = accum[i];
if(value) {
2013-08-01 18:04:39 +08:00
x = (xy/s) | 0;
y = xy % s;
// by-pass the style generation for improving performance
color = this._shader['polygon-fill']({ value: value }, { zoom: 0 });
2013-07-30 16:45:35 +08:00
ctx.fillStyle = color;
2013-08-29 21:22:57 +08:00
//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;
2013-07-30 16:45:35 +08:00
ctx.fillRect(x * res, 256 - res - y * res, res, res);
}
}
2013-08-29 21:22:57 +08:00
prof.end();
2013-07-30 16:45:35 +08:00
},
//
// 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
*/
2013-10-29 01:41:27 +08:00
CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer';
2013-07-30 16:45:35 +08:00
/**
* 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);
}
2013-08-29 21:22:57 +08:00
if(options.readyHandler) {
this.readyHandler = options.readyHandler;
2013-07-30 16:45:35 +08:00
}
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
};
/**
* 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_();
2013-08-29 21:22:57 +08:00
this.readyHandler && this.readyHandler();
2013-07-30 16:45:35 +08:00
};
/**
* @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_);
2013-09-30 19:31:27 +08:00
// 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;
}
2013-07-30 16:45:35 +08:00
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_);
}
};
}
2013-08-29 21:22:57 +08:00
/*
====================
canvas setup for drawing tiles
====================
2013-07-30 16:45:35 +08:00
*/
2013-08-29 21:22:57 +08:00
if(typeof(google) !== 'undefined' && typeof(google.maps) !== 'undefined') {
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
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;
}
}
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
// 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;
2013-09-28 00:56:03 +08:00
this._tiles = {};
2013-08-29 21:22:57 +08:00
this._tilesToLoad = 0;
this._updateTiles = this._updateTiles.bind(this);
2013-10-29 01:41:27 +08:00
this._listeners = [];
this._listeners.push(
google.maps.event.addListener(this._map, 'dragend', this._updateTiles),
google.maps.event.addListener(this._map, 'zoom_changed', this._updateTiles)
);
2013-08-29 21:22:57 +08:00
this.tileSize = 256;
this._updateTiles();
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
_removeTileLoader: function() {
2013-10-29 01:41:27 +08:00
for(var i in this._listeners) {
google.maps.event.removeListener(this._listeners[i]);
}
this._removeTiles();
2013-07-30 16:45:35 +08:00
},
2013-09-28 00:56:03 +08:00
_removeTiles: function () {
for (var key in this._tiles) {
this._removeTile(key);
}
},
2013-10-11 17:09:30 +08:00
_reloadTiles: function() {
this._removeTiles();
this._updateTiles();
},
2013-08-29 21:22:57 +08:00
_updateTiles: function () {
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
if (!this._map) { return; }
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
var bounds = this._map.getBounds();
var zoom = this._map.getZoom();
var tileSize = this.tileSize;
var mzoom = (1 << zoom);
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
var topLeft = new google.maps.LatLng(
bounds.getNorthEast().lat(),
bounds.getSouthWest().lng()
);
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
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) {
2013-09-30 19:31:27 +08:00
var limit = (1 << this._map.getZoom());
// wrap tile
tilePoint = {
x: ((tilePoint.x % limit) + limit) % limit,
y: tilePoint.y
};
2013-08-29 21:22:57 +08:00
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;
2013-11-05 15:57:40 +08:00
if (!torque.isBrowserSupported()) {
throw new Error("browser is not supported by torque");
}
2013-08-29 21:22:57 +08:00
this.key = 0;
2013-11-23 01:31:04 +08:00
this.shader = null;
2013-09-11 19:21:33 +08:00
this.ready = false;
2013-08-29 21:22:57 +08:00
this.options = _.extend({}, options);
_.defaults(this.options, {
provider: 'sql_api',
renderer: 'point',
resolution: 2,
2013-10-31 21:40:01 +08:00
steps: 100,
visible: true
2013-08-29 21:22:57 +08:00
});
2013-11-23 01:31:04 +08:00
if (options.cartocss) {
_.extend(this.options,
torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss));
}
2013-08-29 21:22:57 +08:00
2013-12-12 21:03:35 +08:00
this.hidden = !this.options.visible;
2013-08-29 21:22:57 +08:00
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);
2013-10-11 17:09:30 +08:00
this.setDuration = this.animator.duration.bind(this.animator);
2013-10-29 01:41:27 +08:00
this.isRunning = this.animator.isRunning.bind(this.animator);
2013-10-11 17:09:30 +08:00
2013-08-29 21:22:57 +08:00
CanvasLayer.call(this, {
map: this.options.map,
//resizeHandler: this.redraw,
animate: false,
updateHandler: this.render,
readyHandler: this.initialize
});
}
/**
* torque layer
*/
GMapsTorqueLayer.prototype = _.extend({},
2013-09-11 19:21:33 +08:00
CanvasLayer.prototype,
2013-08-29 21:22:57 +08:00
torque.GMapsTileLoader.prototype,
2013-09-11 19:21:33 +08:00
torque.Event,
2013-08-29 21:22:57 +08:00
{
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);
2013-09-30 19:31:27 +08:00
this.options.ready = function() {
self.fire("change:bounds", {
bounds: self.provider.getBounds()
});
2013-11-11 23:46:42 +08:00
self.animator.steps(self.provider.getSteps());
2013-10-29 01:41:27 +08:00
self.animator.rescale();
2013-11-07 22:54:10 +08:00
self.fire('change:steps', {
steps: self.provider.getSteps()
});
2013-10-29 01:41:27 +08:00
self.setKey(self.key);
2013-09-30 19:31:27 +08:00
};
2013-08-29 21:22:57 +08:00
this.provider = new this.providers[this.options.provider](this.options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options);
2014-01-09 23:56:06 +08:00
// this listener should be before tile loader
this._cacheListener = google.maps.event.addListener(this.options.map, 'zoom_changed', function() {
self.renderer && self.renderer.clearSpriteCache();
});
2013-08-29 21:22:57 +08:00
this._initTileLoader(this.options.map, this.getProjection());
2013-11-23 01:31:04 +08:00
if (this.shader) {
this.renderer.setShader(this.shader);
2013-07-30 16:45:35 +08:00
}
},
2013-10-31 21:40:01 +08:00
hide: function() {
if(this.hidden) return this;
this.pause();
this.clear();
this.hidden = true;
return this;
},
show: function() {
if(!this.hidden) return this;
this.hidden = false;
this.play();
return this;
},
2013-10-29 01:41:27 +08:00
setSQL: function(sql) {
if (!this.provider || !this.provider.setSQL) {
throw new Error("this provider does not support SQL");
}
this.provider.setSQL(sql);
this._reloadTiles();
return this;
},
setBlendMode: function(_) {
this.renderer && this.renderer.setBlendMode(_);
this.redraw();
},
2013-10-11 17:09:30 +08:00
setSteps: function(steps) {
2013-10-29 01:41:27 +08:00
this.provider && this.provider.setSteps(steps);
this.animator && this.animator.steps(steps);
this._reloadTiles();
},
setColumn: function(column, isTime) {
this.provider && this.provider.setColumn(column, isTime);
2013-10-11 17:09:30 +08:00
this._reloadTiles();
},
2013-10-29 01:41:27 +08:00
getTimeBounds: function() {
return this.provider && this.provider.getKeySpan();
},
2013-07-30 16:45:35 +08:00
getCanvas: function() {
2013-08-29 21:22:57 +08:00
return this.canvas;
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
// for each tile shown on the map request the data
onTileAdded: function(t) {
var self = this;
this.provider.getTileData(t, t.zoom, function(tileData) {
2014-01-09 23:56:06 +08:00
// don't load tiles that are not being shown
if (t.zoom !== self.options.map.getZoom()) return;
2013-08-29 21:22:57 +08:00
self._tileLoaded(t, tileData);
2013-11-11 23:46:42 +08:00
if (tileData) {
self.redraw();
}
2013-08-29 21:22:57 +08:00
});
2013-07-30 16:45:35 +08:00
},
2013-10-31 21:40:01 +08:00
clear: function() {
var canvas = this.canvas;
canvas.width = canvas.width;
},
2013-08-29 21:22:57 +08:00
/**
* render the selectef key
* don't call this function directly, it's called by
* requestAnimationFrame. Use redraw to refresh it
*/
render: function() {
2013-10-31 21:40:01 +08:00
if(this.hidden) return;
2013-08-29 21:22:57 +08:00
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];
2013-11-11 23:46:42 +08:00
if (tile) {
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);
}
2013-08-29 21:22:57 +08:00
}
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
/**
* 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;
2013-09-11 19:21:33 +08:00
this.animator.step(key);
2013-08-29 21:22:57 +08:00
this.redraw();
2013-09-11 19:21:33 +08:00
this.fire('change:time', { time: this.getTime(), step: this.key });
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
/**
* helper function, does the same than ``setKey`` but only
* accepts scalars.
*/
2013-09-11 19:21:33 +08:00
setStep: function(time) {
2013-08-29 21:22:57 +08:00
if(time === undefined || time.length !== undefined) {
throw new Error("setTime only accept scalars");
}
this.setKey(time);
2013-07-30 16:45:35 +08:00
},
2013-09-11 19:21:33 +08:00
/**
* 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();
2013-11-07 22:54:10 +08:00
var time = times.start + (times.end - times.start)*(step/this.provider.getSteps());
2013-10-29 01:41:27 +08:00
return new Date(time);
2013-09-11 19:21:33 +08:00
},
2013-12-12 21:03:35 +08:00
getStep: function() {
return this.key;
},
2013-09-11 19:21:33 +08:00
/**
* returns the animation time defined by the data
* in the defined column. Date object
*/
getTime: function() {
return this.stepToTime(this.key);
},
2013-08-29 21:22:57 +08:00
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
2013-11-23 01:31:04 +08:00
var shader = new carto.RendererJS().render(cartocss);
this.shader = shader;
if (this.renderer) {
this.renderer.setShader(shader);
}
// provider options
var options = torque.common.TorqueLayer.optionsFromLayer(shader.findLayer({ name: 'Map' }));
if(this.provider && this.provider.setOptions(options)) {
this._reloadTiles();
}
_.extend(this.options, options);
// animator options
if (options.animationDuration) {
this.animator.duration(options.animationDuration);
2013-08-29 21:22:57 +08:00
}
2013-11-23 01:31:04 +08:00
2013-09-11 19:21:33 +08:00
this.redraw();
2013-07-30 16:45:35 +08:00
return this;
},
2013-08-29 21:22:57 +08:00
redraw: function() {
this.scheduleUpdate();
2013-10-11 17:09:30 +08:00
},
onRemove: function() {
CanvasLayer.prototype.onRemove.call(this);
this.animator.stop();
2013-10-29 01:41:27 +08:00
this._removeTileLoader();
2014-01-09 23:56:06 +08:00
google.maps.event.removeListener(this._cacheListener);
2013-08-29 21:22:57 +08:00
}
});
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
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
renderers: {
'point': torque.renderer.Point,
'pixel': torque.renderer.Rectangle
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
initialize: function(options) {
var self = this;
this.key = 0;
this.options.renderer = this.options.renderer || 'pixel';
2013-09-11 19:21:33 +08:00
this.options.provider = this.options.provider || 'sql_api';
2013-08-29 21:22:57 +08:00
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](null, options);
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
_tileLoaded: function(tile, tileData) {
tile.data = tileData;
this.drawTile(tile);
},
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
_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);
});
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
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);
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
setKey: function(key) {
this.key = key;
this.redraw();
2013-07-30 16:45:35 +08:00
},
2013-08-29 21:22:57 +08:00
/**
* 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);
2013-07-30 16:45:35 +08:00
}
});
2013-08-29 21:22:57 +08:00
exports.torque.GMapsTiledTorqueLayer = GMapsTiledTorqueLayer;
exports.torque.GMapsTorqueLayer = GMapsTorqueLayer;
})(typeof exports === "undefined" ? this : exports);
2013-09-30 22:56:29 +08:00
if(typeof(L) !== 'undefined') {
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
L.Mixin.TileLoader = {
_initTileLoader: function() {
this._tiles = {}
this._tilesToLoad = 0;
this._map.on({
'moveend': this._updateTiles
}, this);
this._updateTiles();
},
_removeTileLoader: function() {
2013-10-29 01:41:27 +08:00
this._map.off({
2013-07-30 16:45:35 +08:00
'moveend': this._updateTiles
}, this);
2013-09-28 00:56:03 +08:00
this._removeTiles();
2013-07-30 16:45:35 +08:00
},
_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);
},
2013-09-28 00:56:03 +08:00
_removeTiles: function (bounds) {
for (var key in this._tiles) {
this._removeTile(key);
}
},
2013-10-11 17:09:30 +08:00
_reloadTiles: function() {
this._removeTiles();
this._updateTiles();
},
2013-07-30 16:45:35 +08:00
_removeOtherTiles: function (bounds) {
2013-09-11 19:21:33 +08:00
var kArr, x, y, z, key;
var zoom = this._map.getZoom();
2013-07-30 16:45:35 +08:00
for (key in this._tiles) {
if (this._tiles.hasOwnProperty(key)) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
2013-09-11 19:21:33 +08:00
z = parseInt(kArr[2], 10);
2013-07-30 16:45:35 +08:00
// remove tile if it's out of bounds
2013-09-11 19:21:33 +08:00
if (zoom !== z || x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
2013-07-30 16:45:35 +08:00
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]);
}
}
}
2013-09-30 22:56:29 +08:00
} //L defined
if(typeof(L) !== 'undefined') {
2013-08-29 21:22:57 +08:00
/**
* 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
},
2014-01-09 23:56:06 +08:00
initialize: function (options) {
2013-08-29 21:22:57 +08:00
var self = this;
2014-01-09 23:56:06 +08:00
options = options || {};
2013-08-29 21:22:57 +08:00
//this.project = this._project.bind(this);
this.render = this.render.bind(this);
L.Util.setOptions(this, options);
this._canvas = document.createElement('canvas');
2014-01-09 23:56:06 +08:00
this._canvas.style.position = 'absolute';
this._canvas.style.top = 0;
this._canvas.style.left = 0;
this._canvas.style.zIndex = options.zIndex || 0;
2013-08-29 21:22:57 +08:00
this._ctx = this._canvas.getContext('2d');
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
2013-11-23 01:31:04 +08:00
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
2013-08-29 21:22:57 +08:00
this.requestAnimationFrame = requestAnimationFrame;
},
onAdd: function (map) {
this._map = map;
2014-01-09 23:56:06 +08:00
//this._staticPane = map._createPane('leaflet-tile-pane', map._container);
if (!map._panes.staticPane) {
map._panes.staticPane = map._createPane('leaflet-tile-pane', map._container);
}
this._staticPane = map._panes.staticPane
2013-08-29 21:22:57 +08:00
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);
2013-11-11 23:46:42 +08:00
map.on('resize', this._reset, this);
2013-08-29 21:22:57 +08:00
if(this.options.tileLoader) {
this._initTileLoader();
}
this._reset();
},
getCanvas: function() {
return this._canvas;
},
2013-12-18 19:06:14 +08:00
getAttribution: function() {
return this.options.attribution;
},
2013-08-29 21:22:57 +08:00
draw: function() {
return this._reset();
},
onRemove: function (map) {
map._container.removeChild(this._staticPane);
map.off({
'viewreset': this._reset,
2013-11-11 23:46:42 +08:00
'move': this._render,
'resize': this._reset
2013-08-29 21:22:57 +08:00
}, this);
},
addTo: function (map) {
map.addLayer(this);
return this;
},
setOpacity: function (opacity) {
this.options.opacity = opacity;
this._updateOpacity();
return this;
},
2014-01-09 23:56:06 +08:00
setZIndex: function(zIndex) {
this._canvas.style.zIndex = zIndex;
},
2013-08-29 21:22:57 +08:00
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');
}
});
2013-09-30 22:56:29 +08:00
} //L defined
if(typeof(L) !== 'undefined') {
2013-07-30 16:45:35 +08:00
/**
* 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;
2013-11-05 15:57:40 +08:00
if (!torque.isBrowserSupported()) {
throw new Error("browser is not supported by torque");
}
2013-07-30 16:45:35 +08:00
options.tileLoader = true;
this.key = 0;
2013-11-23 01:31:04 +08:00
if (options.cartocss) {
_.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss));
}
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
options.resolution = options.resolution || 2;
options.steps = options.steps || 100;
2013-10-31 21:40:01 +08:00
options.visible = options.visible === undefined ? true: options.visible;
this.hidden = !options.visible;
2013-08-29 21:22:57 +08:00
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);
2013-10-11 17:09:30 +08:00
this.setDuration = this.animator.duration.bind(this.animator);
2013-10-29 01:41:27 +08:00
this.isRunning = this.animator.isRunning.bind(this.animator);
2013-10-11 17:09:30 +08:00
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
L.CanvasLayer.prototype.initialize.call(this, options);
this.options.renderer = this.options.renderer || 'point';
2013-09-11 19:21:33 +08:00
this.options.provider = this.options.provider || 'sql_api';
2013-07-30 16:45:35 +08:00
2013-11-07 22:54:10 +08:00
options.ready = function() {
self.fire("change:bounds", {
bounds: self.provider.getBounds()
});
self.animator.steps(self.provider.getSteps());
self.animator.rescale();
self.fire('change:steps', {
steps: self.provider.getSteps()
});
self.setKey(self.key);
};
2013-07-30 16:45:35 +08:00
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options);
2013-10-11 17:09:30 +08:00
2013-07-30 16:45:35 +08:00
// 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) {
2014-01-09 23:56:06 +08:00
// don't load tiles that are not being shown
if (t.zoom !== self._map.getZoom()) return;
2013-07-30 16:45:35 +08:00
self._tileLoaded(t, tileData);
2013-11-11 23:46:42 +08:00
if (tileData) {
self.redraw();
}
2013-07-30 16:45:35 +08:00
});
}, this);
2013-10-29 01:41:27 +08:00
},
2014-01-09 23:56:06 +08:00
_clearCaches: function() {
this.renderer && this.renderer.clearSpriteCache();
},
onAdd: function (map) {
map.on({
'zoomend': this._clearCaches
}, this);
L.CanvasLayer.prototype.onAdd.call(this, map);
},
2013-11-23 01:31:04 +08:00
2013-11-11 23:46:42 +08:00
onRemove: function(map) {
2013-10-29 01:41:27 +08:00
this._removeTileLoader();
2014-01-09 23:56:06 +08:00
map.off({
'zoomend': this._clearCaches
}, this);
2013-11-11 23:46:42 +08:00
L.CanvasLayer.prototype.onRemove.call(this, map);
2013-10-29 01:41:27 +08:00
},
2013-10-31 21:40:01 +08:00
hide: function() {
if(this.hidden) return this;
this.pause();
this.clear();
this.hidden = true;
return this;
},
show: function() {
if(!this.hidden) return this;
this.hidden = false;
this.play();
return this;
},
2013-10-29 01:41:27 +08:00
setSQL: function(sql) {
if (!this.provider || !this.provider.setSQL) {
throw new Error("this provider does not support SQL");
}
this.provider.setSQL(sql);
this._reloadTiles();
return this;
},
setBlendMode: function(_) {
this.renderer.setBlendMode(_);
this.redraw();
2013-07-30 16:45:35 +08:00
},
2013-10-11 17:09:30 +08:00
setSteps: function(steps) {
this.provider.setSteps(steps);
this._reloadTiles();
},
2013-10-29 01:41:27 +08:00
setColumn: function(column, isTime) {
this.provider.setColumn(column, isTime);
this._reloadTiles();
},
getTimeBounds: function() {
return this.provider && this.provider.getKeySpan();
},
2013-10-31 21:40:01 +08:00
clear: function() {
var canvas = this.getCanvas();
canvas.width = canvas.width;
},
2013-10-29 01:41:27 +08:00
2013-07-30 16:45:35 +08:00
/**
* render the selectef key
* don't call this function directly, it's called by
* requestAnimationFrame. Use redraw to refresh it
*/
render: function() {
2013-10-31 21:40:01 +08:00
if(this.hidden) return;
2013-07-30 16:45:35 +08:00
var t, tile, pos;
var canvas = this.getCanvas();
canvas.width = canvas.width;
var ctx = canvas.getContext('2d');
2013-09-11 19:21:33 +08:00
for(t in this._tiles) {
tile = this._tiles[t];
2013-11-11 23:46:42 +08:00
if (tile) {
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);
}
2013-07-30 16:45:35 +08:00
}
},
/**
* 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;
2013-09-11 19:21:33 +08:00
this.animator.step(key);
2013-07-30 16:45:35 +08:00
this.redraw();
2013-09-11 19:21:33 +08:00
this.fire('change:time', { time: this.getTime(), step: this.key });
2013-08-29 21:22:57 +08:00
},
/**
* helper function, does the same than ``setKey`` but only
* accepts scalars.
*/
2013-09-11 19:21:33 +08:00
setStep: function(time) {
2013-08-29 21:22:57 +08:00
if(time === undefined || time.length !== undefined) {
throw new Error("setTime only accept scalars");
}
this.setKey(time);
},
2013-09-11 19:21:33 +08:00
/**
* 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();
2013-11-07 22:54:10 +08:00
var time = times.start + (times.end - times.start)*(step/this.provider.getSteps());
2013-10-29 01:41:27 +08:00
return new Date(time);
2013-09-11 19:21:33 +08:00
},
2013-12-12 21:03:35 +08:00
getStep: function() {
return this.key;
},
2013-09-11 19:21:33 +08:00
/**
* 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();
},
2013-08-29 21:22:57 +08:00
/**
* set the cartocss for the current renderer
*/
setCartoCSS: function(cartocss) {
if (!this.renderer) throw new Error('renderer is not valid');
2013-11-23 01:31:04 +08:00
var shader = new carto.RendererJS().render(cartocss);
this.renderer.setShader(shader);
// provider options
var options = torque.common.TorqueLayer.optionsFromLayer(shader.findLayer({ name: 'Map' }));
if(this.provider.setOptions(options)) {
this._reloadTiles();
}
_.extend(this.options, options);
// animator options
if (options.animationDuration) {
this.animator.duration(options.animationDuration);
}
2013-08-29 21:22:57 +08:00
this.redraw();
return this;
2013-07-30 16:45:35 +08:00
}
});
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';
2013-09-11 19:21:33 +08:00
this.options.provider = this.options.provider || 'sql_api';
2013-07-30 16:45:35 +08:00
this.provider = new this.providers[this.options.provider](options);
this.renderer = new this.renderers[this.options.renderer](null, options);
},
_tileLoaded: function(tile, tilePoint, tileData) {
2013-08-01 18:04:39 +08:00
if(this._tiles[tilePoint.x + ':' + tilePoint.y] !== undefined) {
this._tiles[tilePoint.x + ':' + tilePoint.y].data = tileData;
this.drawTile(tile);
}
2013-08-07 00:18:55 +08:00
L.TileLayer.Canvas.prototype._tileLoaded.call(this);
2013-07-30 16:45:35 +08:00
},
2013-08-06 23:48:25 +08:00
redraw: function() {
for (var i in this._tiles) {
this._redrawTile(this._tiles[i]);
}
},
2013-07-30 16:45:35 +08:00
_loadTile: function(tile, tilePoint) {
var self = this;
L.TileLayer.Canvas.prototype._loadTile.apply(this, arguments);
2013-08-29 21:22:57 +08:00
// 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) {
2013-07-30 16:45:35 +08:00
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();
2013-08-29 21:22:57 +08:00
},
/**
* 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);
2013-07-30 16:45:35 +08:00
}
});
2013-09-30 22:56:29 +08:00
} //L defined