/** Torque 2.16.3 Temporal mapping for CARTO https://github.com/cartodb/torque **/ !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.torque=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= end) throw new Error('start must be smaller than end'); this.start = start; this.end = end; }; AnimatorStepsRange.prototype = { diff: function() { return this.end - this.start; }, isLast: function(step) { // round step into an integer, to be able to compare number as expected (also converts bad input to 0) return (step | 0) === this.end; } }; module.exports = AnimatorStepsRange; },{}],2:[function(require,module,exports){ (function (global){ var torque = require('./'); var AnimatorStepsRange = require('./animator-steps-range'); var requestAnimationFrame = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || global.msRequestAnimationFrame || function(callback) { return global.setTimeout(callback, 1000 / 60); }; var cancelAnimationFrame = global.cancelAnimationFrame || global.mozCancelAnimationFrame || global.webkitCancelAnimationFrame || global.msCancelAnimationFrame || function(id) { clearTimeout(id); }; /** * 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; this.itemsReady = false; this.options = torque.extend({ animationDelay: 0, maxDelta: 0.2, loop: options.loop === undefined ? true : options.loop }, this.options); this.steps(options.steps); } Animator.prototype = { start: function() { this.running = true; requestAnimationFrame(this._tick); this.options.onStart && this.options.onStart(); if (this.stepsRange().diff() === 1) { this.running = false; } }, isRunning: function() { return this.running; }, stop: function() { this.pause(); this.time(this.stepsRange().start); this.options.onStop && this.options.onStop(); }, // real animation time time: function(_) { if (!arguments.length) return this._time; this._time = _; var t = this.range(this.domain(this._time)); this.callback(t); }, toggle: function() { if (this.running) { this.pause() } else { this.start() } }, 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._defaultStepsRange.end); this.rangeInv = this.range.invert(); this.time(this._time); this.running? this.start(): this.pause(); return this; }, duration: function(_) { if (!arguments.length) return this.options.animationDuration; this.options.animationDuration = _; if (this.time() > _) { this.time(0); } this.rescale(); return this; }, steps: function(_) { this.options.steps = _; this._defaultStepsRange = new AnimatorStepsRange(0, _); return this.rescale(); }, // Returns or sets a (custom) steps range // Setting a steps range must be within the full range stepsRange: function(start, end) { if (arguments.length === 2) { if (start < this._defaultStepsRange.start) throw new Error('start must be within default steps range'); if (end > this._defaultStepsRange.end) throw new Error('end must be within default steps range'); this._customStepsRange = new AnimatorStepsRange(start, end); this.options.onStepsRange && this.options.onStepsRange(); // Change current step if it's outside the new custom range var step = this.step() | 0; // round to an integer if (step < start || step > end) { this.step(start); } } return this._customStepsRange || this._defaultStepsRange; }, removeCustomStepsRange: function() { this._customStepsRange = undefined; this.options.onStepsRange && this.options.onStepsRange(); }, step: function(s) { if(arguments.length === 0) return this.range(this.domain(this._time)); this._time = this.domainInv(this.rangeInv(s)); }, pause: function() { this.running = false; cancelAnimationFrame(this._tick); this.options.onPause && this.options.onPause(); }, _tick: function() { var t1 = +new Date(); var delta = (t1 - this._t0)*0.001; // if delta is really big means the tab lost the focus // at some point, so limit delta change delta = Math.min(this.options.maxDelta, delta); this._t0 = t1; this._time += delta; var stepsRange = this.stepsRange(); if (stepsRange.isLast(this.step())) { if(!this.options.loop){ // set time to max time this.time(this.options.animationDuration); this.pause(); } else { this.step(stepsRange.start); } } if(this.running) { this.time(this._time); requestAnimationFrame(this._tick); } } }; module.exports = Animator; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./":11,"./animator-steps-range":1}],3:[function(require,module,exports){ 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" // ] } }, "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" }, "-torque-clear-color": { "css": "-torque-clear-color", "type": "color", "default-value": "rgba(255, 255, 255, 0)", "default-meaning": "full clear", "doc": "color used to clear canvas on each frame" }, "-torque-frame-count": { "css": "-torque-frame-count", "default-value": "128", "type":"number", "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." }, "-torque-resolution": { "css": "-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": { "css": "-torque-animation-duration", "default-value": "30", "type":"number", "default-meaning": "the animation lasts 30 seconds", "doc": "Animation duration in seconds" }, "-torque-aggregation-function": { "css": "-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": { "css": "-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": { "css": "-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" } }, "symbolizers" : { "*": { "comp-op": { "css": "comp-op", "default-value": "src-over", "default-meaning": "add the current layer on top of other layers", "doc": "Composite operation. This defines how this layer should behave relative to layers atop or below it.", "type": [ "src", // "src-over", // "dst-over", // "src-in", // "dst-in", // "src-out", // "dst-out", // "src-atop", // "dst-atop", // "xor", // "darken", // "lighten" // ] }, "opacity": { "css": "opacity", "type": "float", "doc": "An alpha value for the style (which means an alpha applied to all features in separate buffer and then composited back to main buffer)", "default-value": 1, "default-meaning": "no separate buffer will be used and no alpha will be applied to the style after rendering" } }, "trail": { "steps": { "css": "trail-steps", "type": "float", "default-value": 1, "default-meaning": "no trail steps", "doc": "How many steps of trails are going to be rendered" } }, "polygon": { "fill": { "css": "polygon-fill", "type": "color", "default-value": "rgba(128,128,128,1)", "default-meaning": "gray and fully opaque (alpha = 1), same as rgb(128,128,128)", "doc": "Fill color to assign to a polygon" }, "fill-opacity": { "css": "polygon-opacity", "type": "float", "doc": "The opacity of the polygon", "default-value": 1, "default-meaning": "opaque" } }, "line": { "stroke": { "css": "line-color", "default-value": "rgba(0,0,0,1)", "type": "color", "default-meaning": "black and fully opaque (alpha = 1), same as rgb(0,0,0)", "doc": "The color of a drawn line" }, "stroke-width": { "css": "line-width", "default-value": 1, "type": "float", "doc": "The width of a line in pixels" }, "stroke-opacity": { "css": "line-opacity", "default-value": 1, "type": "float", "default-meaning": "opaque", "doc": "The opacity of a line" }, "stroke-linejoin": { "css": "line-join", "default-value": "miter", "type": [ "miter", "round", "bevel" ], "doc": "The behavior of lines when joining" }, "stroke-linecap": { "css": "line-cap", "default-value": "butt", "type": [ "butt", "round", "square" ], "doc": "The display of line endings" } }, "markers": { "file": { "css": "marker-file", "doc": "An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse.", "default-value": "", "default-meaning": "An ellipse or circle, if width equals height", "type": "uri" }, "opacity": { "css": "marker-opacity", "doc": "The overall opacity of the marker, if set, overrides both the opacity of both the fill and stroke", "default-value": 1, "default-meaning": "The stroke-opacity and fill-opacity will be used", "type": "float" }, "fill-opacity": { "css": "marker-fill-opacity", "doc": "The fill opacity of the marker", "default-value": 1, "default-meaning": "opaque", "type": "float" }, "stroke": { "css": "marker-line-color", "doc": "The color of the stroke around a marker shape.", "default-value": "black", "type": "color" }, "stroke-width": { "css": "marker-line-width", "doc": "The width of the stroke around a marker shape, in pixels. This is positioned on the boundary, so high values can cover the area itself.", "type": "float" }, "stroke-opacity": { "css": "marker-line-opacity", "default-value": 1, "default-meaning": "opaque", "doc": "The opacity of a line", "type": "float" }, "fill": { "css": "marker-fill", "default-value": "blue", "doc": "The color of the area of the marker.", "type": "color" }, "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)" }, "width": { "css": "marker-width", "default-value": 10, "doc": "The width of the marker, if using one of the default types.", "type": "float" } }, "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] } }; module.exports = { version: { latest: _torque_reference_latest, '1.0.0': _torque_reference_latest } }; },{}],4:[function(require,module,exports){ (function (global){ // // common functionallity for torque layers // var carto = global.carto || require('carto'); function TorqueLayer() {} TorqueLayer.prototype = { }; TorqueLayer.optionsFromLayer = function(mapConfig) { var opts = {}; if (!mapConfig) return opts; var attrs = { 'buffer-size': 'buffer-size', '-torque-frame-count': 'steps', '-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); }; module.exports.TorqueLayer = TorqueLayer; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"carto":undefined}],5:[function(require,module,exports){ (function (global){ var Event = {}; Event.on = function(evt, callback) { var cb = this._evt_callbacks = this._evt_callbacks || {}; var l = cb[evt] || (cb[evt] = []); l.push(callback); return this; }; 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)); } return this; }; Event.fire = Event.trigger; Event.off = function (evt, callback) { var c = this._evt_callbacks && this._evt_callbacks[evt]; if (c && !callback) { delete this._evt_callbacks[evt]; return this; } var remove = []; for(var i = 0; c && i < c.length; ++i) { if(c[i] === callback) remove.push(i); } while((i = remove.pop()) !== undefined) c.splice(i, 1); return this; }; Event.callbacks = function(evt) { return (this._evt_callbacks && this._evt_callbacks[evt]) || []; }; function extend() { var objs = arguments; var a = objs[0]; for (var i = 1; i < objs.length; ++i) { var b = objs[i]; for (var k in b) { a[k] = b[k]; } } return a; } function clone(a) { return extend({}, a); } function isFunction(f) { return typeof f == 'function' || false; } function isArray(value) { return value && typeof value == 'object' && Object.prototype.toString.call(value) == '[object Array]'; } // types var types = { Uint8Array: typeof(global['Uint8Array']) !== 'undefined' ? global.Uint8Array : Array, Uint8ClampedArray: typeof(global['Uint8ClampedArray']) !== 'undefined' ? global.Uint8ClampedArray: Array, Uint32Array: typeof(global['Uint32Array']) !== 'undefined' ? global.Uint32Array : Array, Int16Array: typeof(global['Int16Array']) !== 'undefined' ? global.Int16Array : Array, Int32Array: typeof(global['Int32Array']) !== 'undefined' ? global.Int32Array: Array }; function isBrowserSupported() { return !!document.createElement('canvas'); } function userAgent() { return typeof navigator !== 'undefined' ? navigator.userAgent : ''; } var flags = { sprites_to_images: userAgent().indexOf('Safari') === -1 && userAgent().indexOf('Firefox') === -1 }; module.exports = { Event: Event, extend: extend, clone: clone, isFunction: isFunction, isArray: isArray, types: types, isBrowserSupported: isBrowserSupported, flags: flags }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],6:[function(require,module,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. */ function CanvasLayer(opt_options) { /** * If true, canvas is in a map pane and the OverlayView is fully functional. * See google.maps.OverlayView.onAdd for more information. * @type {boolean} * @private */ this.isAdded_ = false; /** * If true, each update will immediately schedule the next. * @type {boolean} * @private */ this.isAnimated_ = false; /** * The name of the MapPane in which this layer will be displayed. * @type {string} * @private */ this.paneName_ = CanvasLayer.DEFAULT_PANE_NAME_; /** * A user-supplied function called whenever an update is required. Null or * undefined if a callback is not provided. * @type {?function=} * @private */ this.updateHandler_ = null; /** * A user-supplied function called whenever an update is required and the * map has been resized since the last update. Null or undefined if a * callback is not provided. * @type {?function} * @private */ this.resizeHandler_ = null; /** * The LatLng coordinate of the top left of the current view of the map. Will * be null when this.isAdded_ is false. * @type {google.maps.LatLng} * @private */ this.topLeft_ = null; /** * The map-pan event listener. Will be null when this.isAdded_ is false. Will * be null when this.isAdded_ is false. * @type {?function} * @private */ this.centerListener_ = null; /** * The map-resize event listener. Will be null when this.isAdded_ is false. * @type {?function} * @private */ this.resizeListener_ = null; /** * If true, the map size has changed and this.resizeHandler_ must be called * on the next update. * @type {boolean} * @private */ this.needsResize_ = true; /** * A browser-defined id for the currently requested callback. Null when no * callback is queued. * @type {?number} * @private */ this.requestAnimationFrameId_ = null; var canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = 0; canvas.style.left = 0; canvas.style.pointerEvents = 'none'; /** * The canvas element. * @type {!HTMLCanvasElement} */ this.canvas = canvas; /** * Simple bind for functions with no args for bind-less browsers (Safari). * @param {Object} thisArg The this value used for the target function. * @param {function} func The function to be bound. */ function simpleBindShim(thisArg, func) { return function() { func.apply(thisArg); }; } /** * A reference to this.repositionCanvas_ with this bound as its this value. * @type {function} * @private */ this.repositionFunction_ = simpleBindShim(this, this.repositionCanvas_); /** * A reference to this.resize_ with this bound as its this value. * @type {function} * @private */ this.resizeFunction_ = simpleBindShim(this, this.resize_); /** * A reference to this.update_ with this bound as its this value. * @type {function} * @private */ this.requestUpdateFunction_ = simpleBindShim(this, this.update_); // set provided options, if any if (opt_options) { this.setOptions(opt_options); } } CanvasLayer.prototype = new google.maps.OverlayView(); /** * The default MapPane to contain the canvas. * @type {string} * @const * @private */ CanvasLayer.DEFAULT_PANE_NAME_ = 'overlayLayer'; /** * Transform CSS property name, with vendor prefix if required. If browser * does not support transforms, property will be ignored. * @type {string} * @const * @private */ CanvasLayer.CSS_TRANSFORM_ = (function() { var div = document.createElement('div'); var transformProps = [ 'transform', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ]; for (var i = 0; i < transformProps.length; i++) { var prop = transformProps[i]; if (div.style[prop] !== undefined) { return prop; } } // return unprefixed version by default return transformProps[0]; })(); /** * The requestAnimationFrame function, with vendor-prefixed or setTimeout-based * fallbacks. MUST be called with window as thisArg. * @type {function} * @param {function} callback The function to add to the frame request queue. * @return {number} The browser-defined id for the requested callback. * @private */ CanvasLayer.prototype.requestAnimFrame_ = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return window.setTimeout(callback, 1000 / 60); }; /** * The cancelAnimationFrame function, with vendor-prefixed fallback. Does not * fall back to clearTimeout as some platforms implement requestAnimationFrame * but not cancelAnimationFrame, and the cost is an extra frame on onRemove. * MUST be called with window as thisArg. * @type {function} * @param {number=} requestId The id of the frame request to cancel. * @private */ CanvasLayer.prototype.cancelAnimFrame_ = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function(requestId) {}; /** * Sets any options provided. See CanvasLayerOptions for more information. * @param {CanvasLayerOptions} options The options to set. */ CanvasLayer.prototype.setOptions = function(options) { if (options.animate !== undefined) { this.setAnimate(options.animate); } if (options.paneName !== undefined) { this.setPane(options.paneName); } if (options.updateHandler !== undefined) { this.setUpdateHandler(options.updateHandler); } if (options.resizeHandler !== undefined) { this.setResizeHandler(options.resizeHandler); } if(options.readyHandler) { this.readyHandler = options.readyHandler; } }; /** * Set the animated state of the layer. If true, updateHandler will be called * repeatedly, once per frame. If false, updateHandler will only be called when * a map property changes that could require the canvas content to be redrawn. * @param {boolean} animate Whether the canvas is animated. */ CanvasLayer.prototype.setAnimate = function(animate) { this.isAnimated_ = !!animate; if (this.isAnimated_) { this.scheduleUpdate(); } }; /** * @return {boolean} Whether the canvas is animated. */ CanvasLayer.prototype.isAnimated = function() { return this.isAnimated_; }; /** * Set the MapPane in which this layer will be displayed, by name. See * {@code google.maps.MapPanes} for the panes available. * @param {string} paneName The name of the desired MapPane. */ CanvasLayer.prototype.setPaneName = function(paneName) { this.paneName_ = paneName; this.setPane_(); }; /** * Set the opacity for the canvas. * * @param {number} opacity The opacity of the canvas */ CanvasLayer.prototype.setOpacity = function (opacity) { this.canvas.style.opacity = opacity; }; /** * Get the canvases opacity. * * @return {number} The opacity of the canvas */ CanvasLayer.prototype.getOpacity = function () { return this.canvas.style.opacity; }; /** * @return {string} The name of the current container pane. */ CanvasLayer.prototype.getPaneName = function() { return this.paneName_; }; /** * Adds the canvas to the specified container pane. Since this is guaranteed to * execute only after onAdd is called, this is when paneName's existence is * checked (and an error is thrown if it doesn't exist). * @private */ CanvasLayer.prototype.setPane_ = function() { if (!this.isAdded_) { return; } // onAdd has been called, so panes can be used var panes = this.getPanes(); if (!panes[this.paneName_]) { throw new Error('"' + this.paneName_ + '" is not a valid MapPane name.'); } panes[this.paneName_].appendChild(this.canvas); }; /** * Set a function that will be called whenever the parent map and the overlay's * canvas have been resized. If opt_resizeHandler is null or unspecified, any * existing callback is removed. * @param {?function=} opt_resizeHandler The resize callback function. */ CanvasLayer.prototype.setResizeHandler = function(opt_resizeHandler) { this.resizeHandler_ = opt_resizeHandler; }; /** * Set a function that will be called when a repaint of the canvas is required. * If opt_updateHandler is null or unspecified, any existing callback is * removed. * @param {?function=} opt_updateHandler The update callback function. */ CanvasLayer.prototype.setUpdateHandler = function(opt_updateHandler) { this.updateHandler_ = opt_updateHandler; }; /** * @inheritDoc */ CanvasLayer.prototype.onAdd = function() { if (this.isAdded_) { return; } this.isAdded_ = true; this.setPane_(); this.resizeListener_ = google.maps.event.addListener(this.getMap(), 'resize', this.resizeFunction_); this.centerListener_ = google.maps.event.addListener(this.getMap(), 'center_changed', this.repositionFunction_); this.resize_(); this.repositionCanvas_(); this.readyHandler && this.readyHandler(); }; /** * @inheritDoc */ CanvasLayer.prototype.onRemove = function() { if (!this.isAdded_) { return; } this.isAdded_ = false; this.topLeft_ = null; // remove canvas and listeners for pan and resize from map this.canvas.parentElement.removeChild(this.canvas); if (this.centerListener_) { google.maps.event.removeListener(this.centerListener_); this.centerListener_ = null; } if (this.resizeListener_) { google.maps.event.removeListener(this.resizeListener_); this.resizeListener_ = null; } // cease canvas update callbacks if (this.requestAnimationFrameId_) { this.cancelAnimFrame_.call(window, this.requestAnimationFrameId_); this.requestAnimationFrameId_ = null; } }; /** * The internal callback for resize events that resizes the canvas to keep the * map properly covered. * @private */ CanvasLayer.prototype.resize_ = function() { // TODO(bckenny): it's common to use a smaller canvas but use CSS to scale // what is drawn by the browser to save on fill rate. Add an option to do // this. if (!this.isAdded_) { return; } var map = this.getMap(); var width = map.getDiv().offsetWidth; var height = map.getDiv().offsetHeight; var oldWidth = this.canvas.width; var oldHeight = this.canvas.height; // resizing may allocate a new back buffer, so do so conservatively if (oldWidth !== width || oldHeight !== height) { this.canvas.width = width; this.canvas.height = height; this.canvas.style.width = width + 'px'; this.canvas.style.height = height + 'px'; this.needsResize_ = true; this.scheduleUpdate(); } }; /** * @inheritDoc */ CanvasLayer.prototype.draw = function() { this.repositionCanvas_(); }; /** * Internal callback for map view changes. Since the Maps API moves the overlay * along with the map, this function calculates the opposite translation to * keep the canvas in place. * @private */ CanvasLayer.prototype.repositionCanvas_ = function() { // TODO(bckenny): *should* only be executed on RAF, but in current browsers // this causes noticeable hitches in map and overlay relative // positioning. var bounds = this.getMap().getBounds(); this.topLeft_ = new google.maps.LatLng(bounds.getNorthEast().lat(), bounds.getSouthWest().lng()); // canvas position relative to draggable map's conatainer depends on // overlayView's projection, not the map's var projection = this.getProjection(); var divTopLeft = projection.fromLatLngToDivPixel(this.topLeft_); // when the zoom level is low, more than one map can be shown in the screen // so the canvas should be attach to the map with more are in the screen var mapSize = (1 << this.getMap().getZoom())*256; if (Math.abs(divTopLeft.x) > mapSize) { divTopLeft.x -= mapSize; } this.canvas.style[CanvasLayer.CSS_TRANSFORM_] = 'translate(' + Math.round(divTopLeft.x) + 'px,' + Math.round(divTopLeft.y) + 'px)'; this.scheduleUpdate(); }; /** * Internal callback that serves as main animation scheduler via * requestAnimationFrame. Calls resize and update callbacks if set, and * schedules the next frame if overlay is animated. * @private */ CanvasLayer.prototype.update_ = function() { this.requestAnimationFrameId_ = null; if (!this.isAdded_) { return; } if (this.isAnimated_) { this.scheduleUpdate(); } if (this.needsResize_ && this.resizeHandler_) { this.needsResize_ = false; this.resizeHandler_(); } if (this.updateHandler_) { this.updateHandler_(); } }; /** * A convenience method to get the current LatLng coordinate of the top left of * the current view of the map. * @return {google.maps.LatLng} The top left coordinate. */ CanvasLayer.prototype.getTopLeft = function() { return this.topLeft_; }; /** * Schedule a requestAnimationFrame callback to updateHandler. If one is * already scheduled, there is no effect. */ CanvasLayer.prototype.scheduleUpdate = function() { if (this.isAdded_ && !this.requestAnimationFrameId_) { this.requestAnimationFrameId_ = this.requestAnimFrame_.call(window, this.requestUpdateFunction_); } }; module.exports = CanvasLayer; },{}],7:[function(require,module,exports){ /* ==================== canvas setup for drawing tiles ==================== */ function CanvasTileLayer(canvas_setup, render) { this.tileSize = new google.maps.Size(256, 256); this.maxZoom = 19; this.name = "Tile #s"; this.alt = "Canvas tile layer"; this.tiles = {}; this.canvas_setup = canvas_setup; this.render = render; if (!render) { this.render = canvas_setup; } } // create a tile with a canvas element CanvasTileLayer.prototype.create_tile_canvas = function (coord, zoom, ownerDocument) { // create canvas and reset style var canvas = ownerDocument.createElement('canvas'); var hit_canvas = ownerDocument.createElement('canvas'); canvas.style.border = hit_canvas.style.border = "none"; canvas.style.margin = hit_canvas.style.margin = "0"; canvas.style.padding = hit_canvas.style.padding = "0"; // prepare canvas and context sizes var ctx = canvas.getContext('2d'); ctx.width = canvas.width = this.tileSize.width; ctx.height = canvas.height = this.tileSize.height; var hit_ctx = hit_canvas.getContext('2d'); hit_canvas.width = hit_ctx.width = this.tileSize.width; hit_canvas.height = hit_ctx.height = this.tileSize.height; //set unique id var tile_id = coord.x + '_' + coord.y + '_' + zoom; canvas.setAttribute('id', tile_id); hit_canvas.setAttribute('id', tile_id); if (tile_id in this.tiles) delete this.tiles[tile_id]; this.tiles[tile_id] = {canvas:canvas, ctx:ctx, hit_canvas:hit_canvas, hit_ctx:hit_ctx, coord:coord, zoom:zoom, primitives:null}; // custom setup //if (tile_id == '19295_24654_16'){ if (this.canvas_setup) this.canvas_setup(this.tiles[tile_id], coord, zoom); //} return canvas; } CanvasTileLayer.prototype.each = function (callback) { for (var t in this.tiles) { var tile = this.tiles[t]; callback(tile); } } CanvasTileLayer.prototype.recreate = function () { for (var t in this.tiles) { var tile = this.tiles[t]; this.canvas_setup(tile, tile.coord, tile.zoom); } }; CanvasTileLayer.prototype.redraw_tile = function (tile) { this.render(tile, tile.coord, tile.zoom); }; CanvasTileLayer.prototype.redraw = function () { for (var t in this.tiles) { var tile = this.tiles[t]; this.render(tile, tile.coord, tile.zoom); } }; // could be called directly... CanvasTileLayer.prototype.getTile = function (coord, zoom, ownerDocument) { return this.create_tile_canvas(coord, zoom, ownerDocument); }; CanvasTileLayer.prototype.releaseTile = function (tile) { var id = tile.getAttribute('id'); delete this.tiles[id]; }; module.exports = CanvasTileLayer; },{}],8:[function(require,module,exports){ function GMapsTileLoader() { } GMapsTileLoader.prototype = { _initTileLoader: function(map, projection) { this._map = map; this._projection = projection; this._tiles = {}; this._tilesLoading = {}; this._tilesToLoad = 0; this._updateTiles = this._updateTiles.bind(this); this._listeners = []; this._listeners.push( google.maps.event.addListener(this._map, 'dragend', this._updateTiles), google.maps.event.addListener(this._map, 'zoom_changed', this._updateTiles) ); this.tileSize = 256; this._updateTiles(); }, _removeTileLoader: function() { this._listeners.forEach(function (listener) { google.maps.event.removeListener(listener); }); this._removeTiles(); }, _removeTiles: function () { for (var key in this._tiles) { this._removeTile(key); } }, _reloadTiles: function() { this._removeTiles(); this._updateTiles(); }, _updateTiles: function () { if (!this._map) { return; } var bounds = this._map.getBounds(); var zoom = this._map.getZoom(); var tileSize = this.tileSize; var mzoom = (1 << zoom); var topLeft = new google.maps.LatLng( bounds.getNorthEast().lat(), bounds.getSouthWest().lng() ); var bottomRigth = new google.maps.LatLng( bounds.getSouthWest().lat(), bounds.getNorthEast().lng() ); this._projection = this._map.getProjection(); var divTopLeft = this._projection.fromLatLngToPoint(topLeft); var divBottomRight = this._projection.fromLatLngToPoint(bottomRigth); var nwTilePoint = new google.maps.Point( Math.floor(divTopLeft.x*mzoom / tileSize), Math.floor(divTopLeft.y*mzoom / tileSize)), seTilePoint = new google.maps.Point( Math.floor(divBottomRight.x*mzoom / tileSize), Math.floor(divBottomRight.y*mzoom / tileSize)); this._addTilesFromCenterOut(nwTilePoint, seTilePoint); this._removeOtherTiles(nwTilePoint, seTilePoint); }, _removeOtherTiles: function (nwTilePoint, seTilePoint) { var kArr, x, y, key; var zoom = this._map.getZoom(); for (key in this._tiles) { if (this._tiles.hasOwnProperty(key)) { kArr = key.split(':'); x = parseInt(kArr[0], 10); y = parseInt(kArr[1], 10); z = parseInt(kArr[2], 10); // remove tile if it's out of bounds if (z !== zoom || x < nwTilePoint.x || x > seTilePoint.x || y < nwTilePoint.y || y > seTilePoint.y) { this._removeTile(key); } } } }, _removeTile: function (key) { this.onTileRemoved && this.onTileRemoved(this._tiles[key]); delete this._tiles[key]; delete this._tilesLoading[key]; }, _tileKey: function(tilePoint) { return tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom; }, _tileShouldBeLoaded: function (tilePoint) { var k = this._tileKey(tilePoint); return !(k in this._tiles) && !(k in this._tilesLoading); }, _tileLoaded: function(tilePoint, tileData) { this._tilesToLoad--; var k = tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom this._tiles[k] = tileData; delete this._tilesLoading[k]; if(this._tilesToLoad === 0) { this.onTilesLoaded && this.onTilesLoaded(); } }, getTilePos: function (tilePoint) { var limit = (1 << this._map.getZoom()); // wrap tile tilePoint = { x: ((tilePoint.x % limit) + limit) % limit, y: tilePoint.y }; tilePoint = new google.maps.Point( tilePoint.x * this.tileSize, tilePoint.y * this.tileSize ); var bounds = this._map.getBounds(); var topLeft = new google.maps.LatLng( bounds.getNorthEast().lat(), bounds.getSouthWest().lng() ); var divTopLeft = this._map.getProjection().fromLatLngToPoint(topLeft); zoom = (1 << this._map.getZoom()); divTopLeft.x = divTopLeft.x * zoom; divTopLeft.y = divTopLeft.y * zoom; return new google.maps.Point( tilePoint.x - divTopLeft.x, tilePoint.y - divTopLeft.y ); }, _addTilesFromCenterOut: function (nwTilePoint, seTilePoint) { var queue = [], center = new google.maps.Point( (nwTilePoint.x + seTilePoint.x) * 0.5, (nwTilePoint.y + seTilePoint.y) * 0.5 ), zoom = this._map.getZoom(); var j, i, point; for (j = nwTilePoint.y; j <= seTilePoint.y; j++) { for (i = nwTilePoint.x; i <= seTilePoint.x; i++) { point = new google.maps.Point (i, j); point.zoom = zoom; if (this._tileShouldBeLoaded(point)) { queue.push(point); } } } var tilesToLoad = queue.length; if (tilesToLoad === 0) { return; } function distanceToCenterSq(point) { var dx = point.x - center.x; var dy = point.y - center.y; return dx * dx + dy * dy; } // load tiles in order of their distance to center queue.sort(function (a, b) { return distanceToCenterSq(a) - distanceToCenterSq(b); }); this._tilesToLoad += tilesToLoad; for (i = 0; i < tilesToLoad; i++) { var t = queue[i]; var k = this._tileKey(t); this._tilesLoading[k] = t; // events if (this.onTileAdded) { this.onTileAdded(t); } } this.onTilesLoading && this.onTilesLoading(); } } module.exports = GMapsTileLoader; },{}],9:[function(require,module,exports){ var gmaps = {}; if (typeof google !== 'undefined' && typeof google.maps !== 'undefined') { gmaps = require('./torque'); gmaps.GMapsTileLoader = require('./gmaps_tileloader_mixin'); } module.exports = gmaps; },{"./gmaps_tileloader_mixin":8,"./torque":10}],10:[function(require,module,exports){ (function (global){ var carto = global.carto || require('carto'); var torque = require('../'); var CanvasLayer = require('./CanvasLayer'); var CanvasTileLayer = require('./canvas_tile_layer'); var GMapsTileLoader = require('./gmaps_tileloader_mixin'); function GMapsTorqueLayer(options) { var self = this; if (!torque.isBrowserSupported()) { throw new Error("browser is not supported by torque"); } this.keys = [0]; Object.defineProperty(this, 'key', { get: function() { return this.getKey(); } }); this.shader = null; this.ready = false; this.options = torque.extend({}, options); this.options = torque.extend({ provider: 'windshaft', renderer: 'point', resolution: 2, steps: 100, visible: true }, this.options); if (options.cartocss) { torque.extend(this.options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss)); } if(options.tileJSON) this.options.provider = "tileJSON"; this.hidden = !this.options.visible; this.animator = new torque.Animator(function(time) { var k = time | 0; if(self.getKey() !== k) { self.setKey(k); } }, torque.extend(torque.clone(this.options), { onPause: function() { self.fire('pause'); }, onStop: function() { self.fire('stop'); }, onStart: function() { self.fire('play'); }, onStepsRange: function() { self.fire('change:stepsRange', self.animator.stepsRange()); } })); 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); this.setDuration = this.animator.duration.bind(this.animator); this.isRunning = this.animator.isRunning.bind(this.animator); CanvasLayer.call(this, { animate: false, updateHandler: this.render, readyHandler: this.initialize }); } /** * torque layer */ GMapsTorqueLayer.prototype = torque.extend({}, CanvasLayer.prototype, GMapsTileLoader.prototype, torque.Event, { providers: { 'sql_api': torque.providers.json, 'url_template': torque.providers.JsonArray, 'windshaft': torque.providers.windshaft, 'tileJSON': torque.providers.tileJSON }, renderers: { 'point': torque.renderer.Point, 'pixel': torque.renderer.Rectangle }, initialize: function() { var self = this; this.onTileAdded = this.onTileAdded.bind(this); this.options.ready = function() { self.fire("change:bounds", { bounds: self.provider.getBounds() }); self.animator.steps(self.provider.getSteps()); self.animator.rescale(); self.fire('change:steps', { steps: self.provider.getSteps() }); self.setKeys(self.getKeys()); }; this.provider = new this.providers[this.options.provider](this.options); this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options); this.renderer.options.errorCallback = this.options.errorCallback; // this listener should be before tile loader this._cacheListener = google.maps.event.addListener(this.map, 'zoom_changed', function() { self.renderer && self.renderer.clearSpriteCache(); }); this._initTileLoader(this.map, this.getProjection()); if (this.shader) { this.renderer.setShader(this.shader); } }, 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(); if (this.options.steps === 1){ this.redraw(); } return this; }, setSQL: function(sql) { if (this.provider.options.named_map) throw new Error("SQL queries on named maps are read-only"); 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(); }, setSteps: function(steps) { 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); this._reloadTiles(); }, getTimeBounds: function() { return this.provider && this.provider.getKeySpan(); }, getCanvas: function() { return this.canvas; }, // for each tile shown on the map request the data onTileAdded: function(t) { var self = this; this.provider.getTileData(t, t.zoom, function(tileData) { // don't load tiles that are not being shown if (t.zoom !== self.map.getZoom()) return; self._tileLoaded(t, tileData); self.fire('tileLoaded'); if (tileData) { self.redraw(); } }); }, clear: function() { var canvas = this.canvas; canvas.width = canvas.width; }, /** * render the selectef key * don't call this function directly, it's called by * requestAnimationFrame. Use redraw to refresh it */ render: function() { if(this.hidden) return; var t, tile, pos; var canvas = this.canvas; this.renderer.clearCanvas(); var ctx = canvas.getContext('2d'); // renders only a "frame" for(t in this._tiles) { tile = this._tiles[t]; if (tile) { pos = this.getTilePos(tile.coord); ctx.setTransform(1, 0, 0, 1, pos.x, pos.y); this.renderer.renderTile(tile, this.keys); } } this.renderer.applyFilters(); }, getActivePointsBBox: function(step) { var positions = []; var tileMax = this.options.resolution * (256/this.options.resolution - 1); for(var t in this._tiles) { var tile = this._tiles[t]; positions = positions.concat(this.renderer.getActivePointsBBox(tile, step)); } return positions; }, /** * 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.setKeys([key]); }, /** * returns the array of keys being rendered */ getKeys: function() { return this.keys; }, setKeys: function(keys) { this.keys = keys; this.animator.step(this.getKey()); this.redraw(); this.fire('change:time', { time: this.getTime(), step: this.getKey() }); }, getKey: function() { return this.keys[0]; }, /** * helper function, does the same than ``setKey`` but only * accepts scalars. */ setStep: function(time) { if(time === undefined || time.length !== undefined) { throw new Error("setTime only accept scalars"); } this.setKey(time); }, renderRange: function(start, end) { this.pause(); var keys = []; for (var i = start; i <= end; i++) { keys.push(i); } this.setKeys(keys); }, resetRenderRange: function() { this.stop(); this.play(); }, /** * transform from animation step to Date object * that contains the animation time * * ``step`` should be between 0 and ``steps - 1`` */ stepToTime: function(step) { if (!this.provider) return 0; var times = this.provider.getKeySpan(); var time = times.start + (times.end - times.start)*(step/this.provider.getSteps()); return new Date(time); }, timeToStep: function(timestamp) { if (typeof timestamp === "Date") timestamp = timestamp.getTime(); if (!this.provider) return 0; var times = this.provider.getKeySpan(); var step = (this.provider.getSteps() * (timestamp - times.start)) / (times.end - times.start); return step; }, getStep: function() { return this.getKey(); }, /** * returns the animation time defined by the data * in the defined column. Date object */ getTime: function() { return this.stepToTime(this.getKey()); }, /** * set the cartocss for the current renderer */ setCartoCSS: function(cartocss) { if (!this.renderer) throw new Error('renderer is not valid'); if (this.provider && this.provider.options.named_map) { console.log('Torque layer: CartoCSS style on named maps is read-only'); return false; } 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' })); this.provider && this.provider.setCartoCSS && this.provider.setCartoCSS(cartocss); if(this.provider && this.provider.setOptions(options)) { this._reloadTiles(); } torque.extend(this.options, options); // animator options if (options.animationDuration) { this.animator.duration(options.animationDuration); } this.redraw(); return this; }, redraw: function() { this.scheduleUpdate(); }, onRemove: function() { this.fire('remove'); CanvasLayer.prototype.onRemove.call(this); this.animator.stop(); this._removeTileLoader(); google.maps.event.removeListener(this._cacheListener); }, /** * return an array with the values for all the pixels active for the step */ getValues: function(step) { var values = []; step = step === undefined ? this.getKey(): step; var t, tile; for(t in this._tiles) { tile = this._tiles[t]; this.renderer.getValues(tile, step, values); } return values; }, getValueForPos: function(x, y, step) { step = step === undefined ? this.getKey(): step; var t, tile, pos, value = null, xx, yy; for(t in this._tiles) { tile = this._tiles[t]; pos = this.getTilePos(tile.coord); xx = x - pos.x; yy = y - pos.y; if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) { value = this.renderer.getValueFor(tile, step, xx, yy); } if (value !== null) { return value; } } return null; }, /** return the number of points for a step */ pointCount: function(step) { var t, tile; step = step === undefined ? this.key: step; var c = 0; for(t in this._tiles) { tile = this._tiles[t]; if (tile) { c += tile.timeCount[step]; } } return c; }, getValueForBBox: function(x, y, w, h) { var xf = x + w, yf = y + h; var sum = 0; for(_y = y; y= 0) { this.cancelAnimationFrame.call(window, this.currentAnimationFrame); } this.currentAnimationFrame = this.requestAnimationFrame.call(window, this.render); }, // use direct: true if you are inside an animation frame call redraw: function(direct) { var domPosition = L.DomUtil.getPosition(this._map.getPanes().mapPane); if (domPosition) { L.DomUtil.setPosition(this._canvas, { x: -domPosition.x, y: -domPosition.y }); } if (direct) { this.render(); } else { this._render(); } }, onResize: function() { }, render: function() { throw new Error('render function should be implemented'); } }); },{"./leaflet_tileloader_mixin":14}],13:[function(require,module,exports){ if (typeof L !== 'undefined') { require('./torque'); } },{"./torque":15}],14:[function(require,module,exports){ L.Mixin.TileLoader = { _initTileLoader: function() { this._tilesLoading = {}; this._tilesToLoad = 0; this._map.on({ 'moveend': this._updateTiles }, this); this._updateTiles(); }, _removeTileLoader: function() { this._map.off({ 'moveend': this._updateTiles }, this); this._removeTiles(); }, _updateTiles: function () { if (!this._map) { return; } var bounds = this._map.getPixelBounds(), zoom = this._map.getZoom(), tileSize = this.options.tileSize; if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { return; } var nwTilePoint = new L.Point( Math.floor(bounds.min.x / tileSize), Math.floor(bounds.min.y / tileSize)), seTilePoint = new L.Point( Math.floor(bounds.max.x / tileSize), Math.floor(bounds.max.y / tileSize)), tileBounds = new L.Bounds(nwTilePoint, seTilePoint); this._addTilesFromCenterOut(tileBounds); this._removeOtherTiles(tileBounds); }, _removeTiles: function (bounds) { for (var key in this._tiles) { this._removeTile(key); } }, _reloadTiles: function() { this._removeTiles(); this._updateTiles(); }, _removeOtherTiles: function (bounds) { var kArr, x, y, z, key; var zoom = this._map.getZoom(); for (key in this._tiles) { if (this._tiles.hasOwnProperty(key)) { kArr = key.split(':'); x = parseInt(kArr[0], 10); y = parseInt(kArr[1], 10); z = parseInt(kArr[2], 10); // remove tile if it's out of bounds if (zoom !== z || x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { this._removeTile(key); } } } }, _removeTile: function (key) { this.fire('tileRemoved', this._tiles[key]); delete this._tiles[key]; delete this._tilesLoading[key]; }, _tileKey: function(tilePoint) { return tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom; }, _tileShouldBeLoaded: function (tilePoint) { var k = this._tileKey(tilePoint); return !(k in this._tiles) && !(k in this._tilesLoading); }, _tileLoaded: function(tilePoint, tileData) { this._tilesToLoad--; var k = tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom this._tiles[k] = tileData; delete this._tilesLoading[k]; if(this._tilesToLoad === 0) { this.fire("tilesLoaded"); } }, getTilePos: function (tilePoint) { tilePoint = new L.Point(tilePoint.x, tilePoint.y); var origin = this._map._getTopLeftPoint(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++) { var t = queue[i]; var k = this._tileKey(t); this._tilesLoading[k] = t; this.fire('tileAdded', t); } this.fire("tilesLoading"); } } },{}],15:[function(require,module,exports){ (function (global){ var carto = global.carto || require('carto'); var torque = require('../'); require('./canvas_layer'); /** * torque layer */ L.TorqueLayer = L.CanvasLayer.extend({ providers: { 'sql_api': torque.providers.json, 'url_template': torque.providers.JsonArray, 'windshaft': torque.providers.windshaft, 'tileJSON': torque.providers.tileJSON }, renderers: { 'point': torque.renderer.Point, 'pixel': torque.renderer.Rectangle }, initialize: function(options) { var self = this; if (!torque.isBrowserSupported()) { throw new Error("browser is not supported by torque"); } options.tileLoader = true; this.keys = [0]; this._tiles = {}; Object.defineProperty(this, 'key', { get: function() { return this.getKey(); } }); this.prevRenderedKey = 0; if (options.cartocss) { // We're only passing the Map header to the global options because the parser won't like turbocarto expressions var headerCartoCSS = options.cartocss.replace(/\n/g,'').match(/Map\s*?\{.*?}/g)[0]; torque.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(headerCartoCSS)); } options.resolution = options.resolution || 2; options.steps = options.steps || 100; options.visible = options.visible === undefined ? true: options.visible; this.hidden = !options.visible; this.animator = new torque.Animator(function(time) { var k = time | 0; if(self.getKey() !== k) { self.setKey(k, { direct: true }); } }, torque.extend(torque.clone(options), { onPause: function() { self.fire('pause'); }, onStop: function() { self.fire('stop'); }, onStart: function() { self.fire('play'); }, onStepsRange: function() { self.fire('change:stepsRange', self.animator.stepsRange()); } })); 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); this.setDuration = this.animator.duration.bind(this.animator); this.isRunning = this.animator.isRunning.bind(this.animator); L.CanvasLayer.prototype.initialize.call(this, options); this.options.renderer = this.options.renderer || 'point'; this.options.provider = this.options.provider || 'windshaft'; if (this.options.tileJSON) this.options.provider = 'tileJSON'; this.provider = new this.providers[this.options.provider](options); options.layer = this; this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options); 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.setKeys(self.getKeys()); }; this.on('tileLoaded', function () { self.renderer.setCartoCSS(self.renderer.style); }) this.renderer.on("allIconsLoaded", this.render.bind(this)); // 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) { // don't load tiles that are not being shown if (t.zoom !== self._map.getZoom()) return; self._tileLoaded(t, tileData); self._clearTileCaches(); if (tileData) { self.redraw(); } self.fire('tileLoaded'); }); }, this); }, _clearTileCaches: function() { var t, tile; for(t in this._tiles) { tile = this._tiles[t]; if (tile && tile._tileCache) { tile._tileCache = null; } } }, _clearCaches: function() { this.renderer && this.renderer.clearSpriteCache(); this._clearTileCaches(); }, onAdd: function (map) { map.on({ 'zoomend': this._clearCaches, 'zoomstart': this._pauseOnZoom }, this); map.on({ 'zoomend': this._resumeOnZoom }, this); L.CanvasLayer.prototype.onAdd.call(this, map); }, onRemove: function(map) { this.fire('remove'); this._removeTileLoader(); map.off({ 'zoomend': this._clearCaches, 'zoomstart': this._pauseOnZoom }, this); map.off({ 'zoomend': this._resumeOnZoom }, this); L.CanvasLayer.prototype.onRemove.call(this, map); }, _pauseOnZoom: function() { this.wasRunning = this.isRunning(); if (this.wasRunning) { this.pause(); } }, _resumeOnZoom: function() { if (this.wasRunning) { this.play(); } }, 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(); if (this.options.steps === 1){ this.redraw(); } return this; }, setSQL: function(sql) { if (this.provider.options.named_map) throw new Error("SQL queries on named maps are read-only"); 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(); }, setSteps: function(steps) { this.provider.setSteps(steps); this._reloadTiles(); }, setColumn: function(column, isTime) { this.provider.setColumn(column, isTime); this._reloadTiles(); }, getTimeBounds: function() { return this.provider && this.provider.getKeySpan(); }, clear: function() { var canvas = this.getCanvas(); canvas.width = canvas.width; }, /** * render the selectef key * don't call this function directly, it's called by * requestAnimationFrame. Use redraw to refresh it */ render: function() { if(this.hidden) return; var t, tile, pos; var canvas = this.getCanvas(); this.renderer.clearCanvas(); var ctx = canvas.getContext('2d'); for(t in this._tiles) { tile = this._tiles[t]; if (tile) { // clear cache if (this.animator.isRunning()) { tile._tileCache = null; } pos = this.getTilePos(tile.coord); ctx.setTransform(1, 0, 0, 1, pos.x, pos.y); if (tile._tileCache) { // when the tile has a cached image just render it and avoid to render // all the points this.renderer._ctx.drawImage(tile._tileCache, 0, 0); } else { this.renderer.renderTile(tile, this.keys); } } } this.renderer.applyFilters(); // prepare caches if the animation is not running // don't cache if the key has just changed, this avoids to cache // when the user is dragging, it only cache when the map is still if (!this.animator.isRunning() && this.getKey() === this.prevRenderedKey) { var tile_size = this.renderer.TILE_SIZE; for(t in this._tiles) { tile = this._tiles[t]; if (tile && !tile._tileCache) { var c = tile._tileCache = document.createElement('canvas'); c.width = c.height = tile_size; pos = this.getTilePos(tile.coord); // clip bounds, firefox raise an exception when try to get data from outside canvas var x = Math.max(0, pos.x) var y = Math.max(0, pos.y) var w = Math.min(tile_size, this.getCanvas().width - x); var h = Math.min(tile_size, this.getCanvas().height - y); if (w > 0 && h > 0) { c.getContext('2d').drawImage(this.getCanvas(), x, y, w, h, x - pos.x, y - pos.y, w, h); } } } } this.prevRenderedKey = this.getKey(); }, /** * 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, options) { this.setKeys([key], options); }, /** * returns the array of keys being rendered */ getKeys: function() { return this.keys; }, setKeys: function(keys, options) { this.keys = keys; this.animator.step(this.getKey()); this._clearTileCaches(); this.redraw(options && options.direct); this.fire('change:time', { time: this.getTime(), step: this.getKey(), start: this.getKey(), end: this.getLastKey() }); }, getKey: function() { return this.keys[0]; }, getLastKey: function() { return this.keys[this.keys.length - 1]; }, /** * helper function, does the same than ``setKey`` but only * accepts scalars. */ setStep: function(time) { if(time === undefined || time.length !== undefined) { throw new Error("setTime only accept scalars"); } this.setKey(time); }, renderRange: function(start, end) { this.pause(); var keys = []; for (var i = start; i <= end; i++) { keys.push(i); } this.setKeys(keys); }, resetRenderRange: function() { this.stop(); this.play(); }, /** * transform from animation step to Date object * that contains the animation time * * ``step`` should be between 0 and ``steps - 1`` */ stepToTime: function(step) { var times = this.provider.getKeySpan(); var time = times.start + (times.end - times.start)*(step/this.provider.getSteps()); return new Date(time); }, timeToStep: function(timestamp) { if (typeof timestamp === "Date") timestamp = timestamp.getTime(); if (!this.provider) return 0; var times = this.provider.getKeySpan(); var step = (this.provider.getSteps() * (timestamp - times.start)) / (times.end - times.start); return step; }, getStep: function() { return this.getKey(); }, /** * returns the animation time defined by the data * in the defined column. Date object */ getTime: function() { return this.stepToTime(this.getKey()); }, /** * returns an object with the start and end times */ getTimeSpan: function() { return this.provider.getKeySpan(); }, /** * set the cartocss for the current renderer */ setCartoCSS: function(cartocss) { if (!this.renderer) throw new Error('renderer is not valid'); if (this.provider && this.provider.options.named_map) { console.log('Torque layer: CartoCSS style on named maps is read-only'); return false; } this.renderer.setCartoCSS(cartocss, function () { // provider options var options = torque.common.TorqueLayer.optionsFromLayer(this.renderer._shader.findLayer({ name: 'Map' })); this.provider.setCartoCSS && this.provider.setCartoCSS(cartocss); if(this.provider.setOptions(options)) { this._reloadTiles(); } torque.extend(this.options, options); // animator options if (options.animationDuration) { this.animator.duration(options.animationDuration); } this._clearCaches(); this.redraw(); return this; }.bind(this)); }, /** * get active points for a step in active zoom * returns a list of bounding boxes [[] , [], []] * empty list if there is no active pixels */ getActivePointsBBox: function(step) { var positions = []; for(var t in this._tiles) { var tile = this._tiles[t]; positions = positions.concat(this.renderer.getActivePointsBBox(tile, step)); } return positions; }, /** * return an array with the values for all the pixels active for the step */ getValues: function(step) { var values = []; step = step === undefined ? this.getKey(): step; var t, tile; for(t in this._tiles) { tile = this._tiles[t]; this.renderer.getValues(tile, step, values); } return values; }, /** * return the value for position relative to map coordinates. null for no value */ getValueForPos: function(x, y, step) { step = step === undefined ? this.getKey(): step; var t, tile, pos, value = null, xx, yy; for(t in this._tiles) { tile = this._tiles[t]; pos = this.getTilePos(tile.coord); xx = x - pos.x; yy = y - pos.y; if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) { value = this.renderer.getValueFor(tile, step, xx, yy); } if (value !== null) { return value; } } return null; }, getValueForBBox: function(x, y, w, h) { var xf = x + w, yf = y + h, _x=x; var sum = 0; for(_y = y; _y= 0) { this.cancelAnimationFrame.call(window, this.currentAnimationFrame); } this.currentAnimationFrame = this.requestAnimationFrame.call(window, this.render); }; ol.CanvasLayer.prototype.getCanvas = function() { return this._canvas; }; ol.CanvasLayer.prototype.getAttribution = function() { return this.options.attribution; }; ol.CanvasLayer.prototype.draw = function() { return this._render(); }; ol.CanvasLayer.prototype.redraw = function(direct) { if (direct) { this.render(); } else { this._render(); } }; module.exports = ol.CanvasLayer; },{"./ol_tileloader_mixin":20}],19:[function(require,module,exports){ if (typeof ol !== 'undefined') { require('./torque'); } },{"./torque":21}],20:[function(require,module,exports){ ol.TileLoader = function(tileSize, maxZoom){ this._tileSize = tileSize; this._tiles = {}; this._tilesLoading = {}; this._tilesToLoad = 0; this._updateTiles = this._updateTiles.bind(this); this._tileGrid = ol.tilegrid.createXYZ({ maxZoom: maxZoom, tileSize: tileSize }); }; ol.TileLoader.prototype._initTileLoader = function(map) { this._map = map; this._view = map.getView(); this._centerChangedId = this._view.on("change:center", function(e){ this._updateTiles(); }, this); this._postcomposeKey = undefined; this._resolutionChangedId = this._view.on("change:resolution", function(evt){ this._currentResolution = this._view.getResolution(); if(this._postcomposeKey) return; this.fire("mapZoomStart"); this._postcomposeKey = this._map.on("postcompose", function(evt) { if(evt.frameState.viewState.resolution === this._currentResolution){ this._updateTiles(); this._map.unByKey(this._postcomposeKey); this._postcomposeKey = undefined; this.fire("mapZoomEnd"); } }, this); }, this); this._updateTiles(); }; ol.TileLoader.prototype._removeTileLoader = function() { this._view.unByKey(this._centerChangedId); this._view.unByKey(this._resolutionChangedId ); this._removeTiles(); }; ol.TileLoader.prototype._removeTiles = function () { for (var key in this._tiles) { this._removeTile(key); } }; ol.TileLoader.prototype._reloadTiles = function() { this._removeTiles(); this._updateTiles(); }; ol.TileLoader.prototype._updateTiles = function () { if (!this._map) { return; } var zoom = this._tileGrid.getZForResolution(this._view.getResolution()); var extent = this._view.calculateExtent(this._map.getSize()); var tileRange = this._requestTilesForExtentAndZ(extent, zoom); this._removeOtherTiles(tileRange); }; ol.TileLoader.prototype._removeOtherTiles = function(tileRange) { var kArr, x, y, z, key; var zoom = this._tileGrid.getZForResolution(this._view.getResolution()); 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 < tileRange.minX || x > tileRange.maxX || ((-y-1) < tileRange.minY) || (-y-1) > tileRange.maxY) { this._removeTile(key); } } } }; ol.TileLoader.prototype._removeTile = function (key) { this.fire('tileRemoved', this._tiles[key]); delete this._tiles[key]; delete this._tilesLoading[key]; }; ol.TileLoader.prototype._tileKey = function(tilePoint) { return tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom; }; ol.TileLoader.prototype._tileShouldBeLoaded = function (tilePoint) { var k = this._tileKey(tilePoint); return !(k in this._tiles) && !(k in this._tilesLoading); }; ol.TileLoader.prototype._removeFromTilesLoading = function(tilePoint){ this._tilesToLoad--; var k = this._tileKey(tilePoint); delete this._tilesLoading[k]; if(this._tilesToLoad === 0) { this.fire("tilesLoaded"); } }; ol.TileLoader.prototype._tileLoaded = function(tilePoint, tileData) { var k = this._tileKey(tilePoint); this._tiles[k] = tileData; }; ol.TileLoader.prototype.getTilePos = function (tilePoint) { var zoom = this._tileGrid.getZForResolution(this._view.getResolution()); var extent = this._tileGrid.getTileCoordExtent([zoom, tilePoint.x, -tilePoint.y-1]); var topLeft = this._map.getPixelFromCoordinate([extent[0], extent[3]]); return { x: topLeft[0], y: topLeft[1] }; }; ol.TileLoader.prototype._requestTilesForExtentAndZ = function (extent, zoom) { var queue = []; var tileCoords = []; this._tileGrid.forEachTileCoord(extent, zoom, function(coord){ tileCoords.push(coord); var point = { x: coord[1], y: -coord[2] - 1, zoom: coord[0] }; if (this._tileShouldBeLoaded(point)) { queue.push(point); } }.bind(this)); var tilesToLoad = queue.length; if (tilesToLoad > 0) { this._tilesToLoad += tilesToLoad; for (var i = 0; i < tilesToLoad; i++) { var t = queue[i]; var k = this._tileKey(t); this._tilesLoading[k] = t; // events this.fire('tileAdded', t); } this.fire("tilesLoading"); } var tileRange = { minX : tileCoords[0][1], maxX : tileCoords [tileCoords.length - 1][1], minY : tileCoords[0][2], maxY : tileCoords [tileCoords.length - 1] [2] }; return tileRange; }; module.exports = ol.TileLoader; },{}],21:[function(require,module,exports){ (function (global){ var carto = global.carto || require('carto'); var torque = require('../'); require('./canvas_layer'); ol.TorqueLayer = function(options){ var self = this; if (!torque.isBrowserSupported()) { throw new Error("browser is not supported by torque"); } options.tileLoader = true; this.keys = [0]; Object.defineProperty(this, 'key', { get: function() { return this.getKey(); } }); this.prevRenderedKey = 0; if (options.cartocss) { torque.extend(options, torque.common.TorqueLayer.optionsFromCartoCSS(options.cartocss)); } options.resolution = options.resolution || 2; options.steps = options.steps || 100; options.visible = options.visible === undefined ? true: options.visible; this.hidden = !options.visible; this.animator = new torque.Animator(function(time) { var k = time | 0; if(self.getKey() !== k) { self.setKey(k, { direct: true }); } }, torque.extend(torque.clone(options), { onPause: function() { self.fire('pause'); }, onStop: function() { self.fire('stop'); }, onStart: function() { self.fire('play'); }, onStepsRange: function() { self.fire('change:stepsRange', self.animator.stepsRange()); } })); 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); this.setDuration = this.animator.duration.bind(this.animator); this.isRunning = this.animator.isRunning.bind(this.animator); ol.CanvasLayer.call(this, options); this.options.renderer = this.options.renderer || 'point'; this.options.provider = this.options.provider || 'windshaft'; if (this.options.tileJSON) this.options.provider = 'tileJSON'; this.provider = new this.providers[this.options.provider](options); this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options); 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.setKeys(self.getKeys()); }; this.renderer.on("allIconsLoaded", this.render.bind(this)); // for each tile shown on the map request the data this.on('tileAdded', function(t) { var tileData = this.provider.getTileData(t, t.zoom, function(tileData) { self._removeFromTilesLoading(t); if (t.zoom !== self._tileGrid.getZForResolution(self._view.getResolution())) return; self._tileLoaded(t, tileData); self.fire('tileLoaded'); if (tileData) { self.redraw(); } }); }, this); this.on('mapZoomStart', function(){ this.getCanvas().style.display = "none"; this._pauseOnZoom(); }, this); this.on('mapZoomEnd', function() { this.getCanvas().style.display = "block"; this._resumeOnZoom(); }, this); }; ol.TorqueLayer.prototype = torque.extend({}, ol.CanvasLayer.prototype, torque.Event, { providers: { 'sql_api': torque.providers.json, 'url_template': torque.providers.JsonArray, 'windshaft': torque.providers.windshaft, 'tileJSON': torque.providers.tileJSON }, renderers: { 'point': torque.renderer.Point, 'pixel': torque.renderer.Rectangle }, onAdd: function(map){ ol.CanvasLayer.prototype.setMap.call(this, map); }, onRemove: function(map) { this.fire('remove'); this._removeTileLoader(); }, _pauseOnZoom: function() { this.wasRunning = this.isRunning(); if (this.wasRunning) { this.pause(); } }, _resumeOnZoom: function() { if (this.wasRunning) { this.play(); } }, 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(); if (this.options.steps === 1){ this.redraw(); } return this; }, setSQL: function(sql) { if (this.provider.options.named_map) throw new Error("SQL queries on named maps are read-only"); 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(); }, setSteps: function(steps) { this.provider.setSteps(steps); this._reloadTiles(); }, setColumn: function(column, isTime) { this.provider.setColumn(column, isTime); this._reloadTiles(); }, getTimeBounds: function() { return this.provider && this.provider.getKeySpan(); }, clear: function() { var canvas = this.getCanvas(); canvas.width = canvas.width; }, /** * render the selectef key * don't call this function directly, it's called by * requestAnimationFrame. Use redraw to refresh it */ render: function() { if(this.hidden) return; var t, tile, pos; var canvas = this.getCanvas(); this.renderer.clearCanvas(); var ctx = canvas.getContext('2d'); // renders only a "frame" for(t in this._tiles) { tile = this._tiles[t]; if (tile) { pos = this.getTilePos(tile.coord); ctx.setTransform(1, 0, 0, 1, pos.x, pos.y); this.renderer.renderTile(tile, this.keys); } } this.renderer.applyFilters(); }, /** * 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, options) { this.setKeys([key], options); }, /** * returns the array of keys being rendered */ getKeys: function() { return this.keys; }, setKeys: function(keys, options) { this.keys = keys; this.animator.step(this.getKey()); this.redraw(options && options.direct); this.fire('change:time', { time: this.getTime(), step: this.getKey(), start: this.getKey(), end: this.getLastKey() }); }, getKey: function() { return this.keys[0]; }, getLastKey: function() { return this.keys[this.keys.length - 1]; }, /** * helper function, does the same than ``setKey`` but only * accepts scalars. */ setStep: function(time) { if(time === undefined || time.length !== undefined) { throw new Error("setTime only accept scalars"); } this.setKey(time); }, renderRange: function(start, end) { this.pause(); var keys = []; for (var i = start; i <= end; i++) { keys.push(i); } this.setKeys(keys); }, resetRenderRange: function() { this.stop(); this.play(); }, /** * transform from animation step to Date object * that contains the animation time * * ``step`` should be between 0 and ``steps - 1`` */ stepToTime: function(step) { var times = this.provider.getKeySpan(); var time = times.start + (times.end - times.start)*(step/this.provider.getSteps()); return new Date(time); }, timeToStep: function(timestamp) { if (typeof timestamp === "Date") timestamp = timestamp.getTime(); if (!this.provider) return 0; var times = this.provider.getKeySpan(); var step = (this.provider.getSteps() * (timestamp - times.start)) / (times.end - times.start); return step; }, getStep: function() { return this.getKey(); }, /** * returns the animation time defined by the data * in the defined column. Date object */ getTime: function() { return this.stepToTime(this.getKey()); }, /** * returns an object with the start and end times */ getTimeSpan: function() { return this.provider.getKeySpan(); }, /** * set the cartocss for the current renderer */ setCartoCSS: function(cartocss) { if (this.provider.options.named_map) throw new Error("CartoCSS style on named maps is read-only"); if (!this.renderer) throw new Error('renderer is not valid'); var shader = new carto.RendererJS().render(cartocss); this.renderer.setShader(shader); // provider options var options = torque.common.TorqueLayer.optionsFromLayer(shader.findLayer({ name: 'Map' })); this.provider.setCartoCSS && this.provider.setCartoCSS(cartocss); if(this.provider.setOptions(options)) { this._reloadTiles(); } torque.extend(this.options, options); // animator options if (options.animationDuration) { this.animator.duration(options.animationDuration); } this.redraw(); return this; }, /** * get active points for a step in active zoom * returns a list of bounding boxes [[] , [], []] * empty list if there is no active pixels */ getActivePointsBBox: function(step) { var positions = []; for(var t in this._tiles) { var tile = this._tiles[t]; positions = positions.concat(this.renderer.getActivePointsBBox(tile, step)); } return positions; }, /** * return an array with the values for all the pixels active for the step */ getValues: function(step) { var values = []; step = step === undefined ? this.getKey(): step; var t, tile; for(t in this._tiles) { tile = this._tiles[t]; this.renderer.getValues(tile, step, values); } return values; }, /** * return the value for position relative to map coordinates. null for no value */ getValueForPos: function(x, y, step) { step = step === undefined ? this.getKey(): step; var t, tile, pos, value = null, xx, yy; for(t in this._tiles) { tile = this._tiles[t]; pos = this.getTilePos(tile.coord); xx = x - pos.x; yy = y - pos.y; if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) { value = this.renderer.getValueFor(tile, step, xx, yy); } if (value !== null) { return value; } } return null; }, getValueForBBox: function(x, y, w, h) { var xf = x + w, yf = y + h, _x=x; var sum = 0; for(_y = y; _y 1) { Profiler.new_value(this.name, this.count); this.count = 0; this.start(); } } }; Profiler.metric = function(name) { return new Metric(name); }; module.exports = Profiler; },{}],23:[function(require,module,exports){ module.exports = { json: require('./json'), JsonArray: require('./jsonarray'), windshaft: require('./windshaft'), tileJSON: require('./tilejson') }; },{"./json":24,"./jsonarray":25,"./tilejson":26,"./windshaft":27}],24:[function(require,module,exports){ var torque = require('../'); var Profiler = require('../profiler'); var Uint8Array = torque.types.Uint8Array; var Int32Array = torque.types.Int32Array; var Uint32Array = torque.types.Uint32Array; // format('hello, {0}', 'rambo') -> "hello, rambo" function format(str) { for(var i = 1; i < arguments.length; ++i) { var attrs = arguments[i]; for(var attr in attrs) { str = str.replace(RegExp('\\{' + attr + '\\}', 'g'), attrs[attr]); } } return str; } var json = function (options) { this._ready = false; this._tileQueue = []; this.options = options; this.options.is_time = this.options.is_time === undefined ? true: this.options.is_time; this.options.tiler_protocol = options.tiler_protocol || 'http'; this.options.tiler_domain = options.tiler_domain || 'cartodb.com'; this.options.tiler_port = options.tiler_port || 80; if (this.options.data_aggregation) { this.options.cumulative = this.options.data_aggregation === 'cumulative'; } // check options if (options.resolution === undefined ) throw new Error("resolution should be provided"); if (options.steps === undefined ) throw new Error("steps should be provided"); if(options.start === undefined) { this._fetchKeySpan(); } else { this._setReady(true); } }; json.prototype = { /** * return the torque tile encoded in an efficient javascript * structure: * { * x:Uint8Array x coordinates in tile reference system, normally from 0-255 * y:Uint8Array y coordinates in tile reference system * Index: Array index to the properties * } */ proccessTile: function(rows, coord, zoom) { var r; var x = new Uint8Array(rows.length); var y = new Uint8Array(rows.length); var prof_mem = Profiler.metric('ProviderJSON:mem'); var prof_point_count = Profiler.metric('ProviderJSON:point_count'); var prof_process_time = Profiler.metric('ProviderJSON:process_time').start() // count number of dates var dates = 0; var maxDateSlots = -1; for (r = 0; r < rows.length; ++r) { var row = rows[r]; dates += row.dates__uint16.length; for(var d = 0; d < row.dates__uint16.length; ++d) { maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]); } } if(this.options.cumulative) { dates = (1 + maxDateSlots) * rows.length; } var type = this.options.cumulative ? Uint32Array: Uint8Array; // reserve memory for all the dates var timeIndex = new Int32Array(maxDateSlots + 1); //index-size var timeCount = new Int32Array(maxDateSlots + 1); var renderData = new (this.options.valueDataType || type)(dates); var renderDataPos = new Uint32Array(dates); prof_mem.inc( 4 * maxDateSlots + // timeIndex 4 * maxDateSlots + // timeCount dates + //renderData dates * 4 ); //renderDataPos prof_point_count.inc(rows.length); var rowsPerSlot = {}; // precache pixel positions for (var r = 0; r < rows.length; ++r) { var row = rows[r]; x[r] = row.x__uint8 * this.options.resolution; // fix value when it's in the tile EDGE // TODO: this should be fixed in SQL query if (row.y__uint8 === -1) { y[r] = 0; } else { y[r] = row.y__uint8 * this.options.resolution; } var dates = row.dates__uint16; var vals = row.vals__uint8; if (!this.options.cumulative) { for (var j = 0, len = dates.length; j < len; ++j) { var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []); if(this.options.cumulative) { vals[j] += prev_val; } prev_val = vals[j]; rr.push([r, vals[j]]); } } else { var valByDate = {} for (var j = 0, len = dates.length; j < len; ++j) { valByDate[dates[j]] = vals[j]; } var accum = 0; // extend the latest to the end for (var j = dates[0]; j <= maxDateSlots; ++j) { var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []); var v = valByDate[j]; if (v) { accum += v; } rr.push([r, accum]); } /*var lastDateSlot = dates[dates.length - 1]; for (var j = lastDateSlot + 1; j <= maxDateSlots; ++j) { var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []); rr.push([r, prev_val]); } */ } } // for each timeslot search active buckets var renderDataIndex = 0; var timeSlotIndex = 0; var i = 0; for(var i = 0; i <= maxDateSlots; ++i) { var c = 0; var slotRows = rowsPerSlot[i] if(slotRows) { for (var r = 0; r < slotRows.length; ++r) { var rr = slotRows[r]; ++c; renderDataPos[renderDataIndex] = rr[0] renderData[renderDataIndex] = rr[1]; ++renderDataIndex; } } timeIndex[i] = timeSlotIndex; timeCount[i] = c; timeSlotIndex += c; } prof_process_time.end(); return { x: x, y: y, z: zoom, coord: { x: coord.x, y: coord.y, z: zoom }, timeCount: timeCount, timeIndex: timeIndex, renderDataPos: renderDataPos, renderData: renderData, maxDate: maxDateSlots }; }, _host: function() { var opts = this.options; var port = opts.sql_api_port; var domain = ((opts.user_name || opts.user) + '.' + (opts.sql_api_domain || 'cartodb.com')) + (port ? ':' + port: ''); var protocol = opts.sql_api_protocol || 'http'; return this.options.url || protocol + '://' + domain + '/api/v2/sql'; }, url: function(subhost) { var opts = this.options; var protocol = opts.sql_api_protocol || 'http'; if (!this.options.cdn_url) { return this._host(); } var h = protocol+ "://"; if (subhost) { h += subhost + "."; } var cdn_host = opts.cdn_url; if(!cdn_host.http && !cdn_host.https) { throw new Error("cdn_host should contain http and/or https entries"); } h += cdn_host[protocol] + "/" + (opts.user_name || opts.user) + '/api/v2/sql'; return h; }, _hash: function(str) { var hash = 0; if (!str || str.length == 0) return hash; for (var i = 0, l = str.length; i < l; ++i) { hash = (( (hash << 5 ) - hash ) + str.charCodeAt(i)) | 0; } return hash; }, _extraParams: function() { if (this.options.extra_params) { var p = []; for(var k in this.options.extra_params) { var v = this.options.extra_params[k]; if (v) { p.push(k + "=" + encodeURIComponent(v)); } } return p.join('&'); } return null; }, isHttps: function() { return this.options.sql_api_protocol && this.options.sql_api_protocol === 'https'; }, // execute actual query sql: function(sql, callback, options) { options = options || {}; var subdomains = this.options.subdomains || '0123'; if(this.isHttps()) { subdomains = [null]; // no subdomain } var url; if (options.no_cdn) { url = this._host(); } else { url = this.url(subdomains[Math.abs(this._hash(sql))%subdomains.length]); } var extra = this._extraParams(); torque.net.get( url + "?q=" + encodeURIComponent(sql) + (extra ? "&" + extra: ''), function (data) { if(options.parseJSON) { data = JSON.parse(data && data.responseText); } callback && callback(data); }); }, getTileData: function(coord, zoom, callback) { if(!this._ready) { this._tileQueue.push([coord, zoom, callback]); } else { this._getTileData(coord, zoom, callback); } }, _setReady: function(ready) { this._ready = true; this._processQueue(); this.options.ready && this.options.ready(); }, _processQueue: function() { var item; while (item = this._tileQueue.pop()) { this._getTileData.apply(this, item); } }, /** * `coord` object like {x : tilex, y: tiley } * `zoom` quadtree zoom level */ _getTileData: function(coord, zoom, callback) { var prof_fetch_time = Profiler.metric('ProviderJSON:tile_fetch_time').start() this.table = this.options.table; var numTiles = 1 << zoom; var column_conv = this.options.column; if(this.options.is_time) { column_conv = format("date_part('epoch', {column})", this.options); } var sql = "" + "WITH " + "par AS (" + " SELECT CDB_XYZ_Resolution({zoom})*{resolution} as res" + ", 256/{resolution} as tile_size" + ", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " + ")," + "cte AS ( "+ " SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" + ", {countby} c" + ", floor(({column_conv} - {start})/{step}) d" + " FROM ({_sql}) i, par p " + " WHERE i.the_geom_webmercator && p.ext " + " GROUP BY g, d" + ") " + "" + "SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " + " (st_y(g)-st_ymin(p.ext))/p.res y__uint8," + " array_agg(c) vals__uint8," + " array_agg(d) dates__uint16" + // the tile_size where are needed because the overlaps query in cte subquery includes the points // in the left and bottom borders of the tile " FROM cte, par p where (st_y(g)-st_ymin(p.ext))/p.res < tile_size and (st_x(g)-st_xmin(p.ext))/p.res < tile_size GROUP BY x__uint8, y__uint8"; var query = format(sql, this.options, { zoom: zoom, x: coord.x, y: coord.y, column_conv: column_conv, _sql: this.getSQL() }); var self = this; this.sql(query, function (data) { if (data) { var rows = JSON.parse(data.responseText).rows; callback(self.proccessTile(rows, coord, zoom)); } else { callback(null); } prof_fetch_time.end(); }); }, getKeySpan: function() { return { start: this.options.start * 1000, end: this.options.end * 1000, step: this.options.step, steps: this.options.steps, columnType: this.options.is_time ? 'date': 'number' }; }, setColumn: function(column, isTime) { this.options.column = column; this.options.is_time = isTime === undefined ? true: false; this.reload(); }, setResolution: function(res) { this.options.resolution = res; }, // return true if tiles has been changed setOptions: function(opt) { var refresh = false; if(opt.resolution !== undefined && opt.resolution !== this.options.resolution) { this.options.resolution = opt.resolution; refresh = true; } if(opt.steps !== undefined && opt.steps !== this.options.steps) { this.setSteps(opt.steps, { silent: true }); refresh = true; } if(opt.column !== undefined && opt.column !== this.options.column) { this.options.column = opt.column; refresh = true; } if(opt.countby !== undefined && opt.countby !== this.options.countby) { this.options.countby = opt.countby; refresh = true; } if(opt.data_aggregation !== undefined) { var c = opt.data_aggregation === 'cumulative'; if (this.options.cumulative !== c) { this.options.cumulative = c; refresh = true; } } if (refresh) this.reload(); return refresh; }, reload: function() { this._ready = false; this._fetchKeySpan(); }, setSQL: function(sql) { if (this.options.sql != sql) { this.options.sql = sql; this.reload(); } }, getSteps: function() { return Math.min(this.options.steps, this.options.data_steps); }, setSteps: function(steps, opt) { opt = opt || {}; if (this.options.steps !== steps) { this.options.steps = steps; this.options.step = (this.options.end - this.options.start)/this.getSteps(); this.options.step = this.options.step || 1; if (!opt.silent) this.reload(); } }, getBounds: function() { return this.options.bounds; }, getSQL: function() { return this.options.sql || "select * from " + this.options.table; }, _tilerHost: function() { var opts = this.options; var user = (opts.user_name || opts.user); return opts.tiler_protocol + "://" + (user ? user + "." : "") + opts.tiler_domain + ((opts.tiler_port != "") ? (":" + opts.tiler_port) : ""); }, _fetchUpdateAt: function(callback) { var self = this; var layergroup = { "version": "1.0.1", "stat_tag": this.options.stat_tag || 'torque', "layers": [{ "type": "cartodb", "options": { "cartocss_version": "2.1.1", "cartocss": "#layer {}", "sql": this.getSQL() } }] }; var url = this._tilerHost() + "/tiles/layergroup"; var extra = this._extraParams(); // tiler needs map_key instead of api_key // so replace it if (extra) { extra = extra.replace('api_key=', 'map_key='); } url = url + "?config=" + encodeURIComponent(JSON.stringify(layergroup)) + "&callback=?" + (extra ? "&" + extra: ''); torque.net.jsonp(url, function (data) { var query = format("select * from ({sql}) __torque_wrap_sql limit 0", { sql: self.getSQL() }); self.sql(query, function (queryData) { if (data && queryData) { callback({ updated_at: data.last_updated, fields: queryData.fields }); } }, { parseJSON: true }); }); }, // // the data range could be set by the user though ``start`` // option. It can be fecthed from the table when the start // is not specified. // _fetchKeySpan: function() { var self = this; var max_col, min_col, max_tmpl, min_tmpl; this._fetchUpdateAt(function(data) { if (!data) return; self.options.extra_params = self.options.extra_params || {}; self.options.extra_params.last_updated = data.updated_at || 0; self.options.extra_params.cache_policy = 'persist'; self.options.is_time = data.fields[self.options.column].type === 'date'; var column_conv = self.options.column; if (self.options.is_time){ max_tmpl = "date_part('epoch', max({column}))"; min_tmpl = "date_part('epoch', min({column}))"; column_conv = format("date_part('epoch', {column})", self.options); } else { max_tmpl = "max({column})"; min_tmpl = "min({column})"; } max_col = format(max_tmpl, { column: self.options.column }); min_col = format(min_tmpl, { column: self.options.column }); /*var sql_stats = "" + "WITH summary_groups as ( " + "WITH summary as ( " + "select (row_number() over (order by __time_col asc nulls last)+1)/2 as rownum, __time_col " + "from (select *, {column} as __time_col from ({sql}) __s) __torque_wrap_sql " + "order by __time_col asc " + ") " + "SELECT " + "max(__time_col) OVER(PARTITION BY rownum) - " + "min(__time_col) OVER(PARTITION BY rownum) diff " + "FROM summary " + "), subq as ( " + " SELECT " + "st_xmax(st_envelope(st_collect(the_geom))) xmax, " + "st_ymax(st_envelope(st_collect(the_geom))) ymax, " + "st_xmin(st_envelope(st_collect(the_geom))) xmin, " + "st_ymin(st_envelope(st_collect(the_geom))) ymin, " + "{max_col} max, " + "{min_col} min FROM ({sql}) __torque_wrap_sql " + ")" + "SELECT " + "xmax, xmin, ymax, ymin, a.max as max_date, a.min as min_date, " + "avg(diff) as diffavg," + "(a.max - a.min)/avg(diff) as num_steps " + "FROM summary_groups, subq a " + "WHERE diff > 0 group by xmax, xmin, ymax, ymin, max_date, min_date"; */ var sql_stats = " SELECT " + "st_xmax(st_envelope(st_collect(the_geom))) xmax, " + "st_ymax(st_envelope(st_collect(the_geom))) ymax, " + "st_xmin(st_envelope(st_collect(the_geom))) xmin, " + "st_ymin(st_envelope(st_collect(the_geom))) ymin, " + "count(*) as num_steps, " + "{max_col} max_date, " + "{min_col} min_date FROM ({sql}) __torque_wrap_sql "; var sql = format(sql_stats, { max_col: max_col, min_col: min_col, column: column_conv, sql: self.getSQL() }); self.sql(sql, function(data) { //TODO: manage bounds data = data.rows[0]; self.options.start = data.min_date; self.options.end = data.max_date; self.options.step = (data.max_date - data.min_date)/Math.min(self.options.steps, data.num_steps>>0); self.options.data_steps = data.num_steps >> 0; // step can't be 0 self.options.step = self.options.step || 1; self.options.bounds = [ [data.ymin, data.xmin], [data.ymax, data.xmax] ]; self._setReady(true); }, { parseJSON: true, no_cdn: true }); }, { parseJSON: true, no_cdn: true}) } }; module.exports = json; },{"../":11,"../profiler":22}],25:[function(require,module,exports){ var torque = require('../'); var Profiler = require('../profiler'); var Uint8Array = torque.types.Uint8Array; var Int32Array = torque.types.Int32Array; var Uint32Array = torque.types.Uint32Array; // format('hello, {0}', 'rambo') -> "hello, rambo" function format(str, attrs) { for(var i = 1; i < arguments.length; ++i) { var attrs = arguments[i]; for(var attr in attrs) { str = str.replace(RegExp('\\{' + attr + '\\}', 'g'), attrs[attr]); } } return str; } var json = function (options) { // check options this.options = options; }; json.prototype = { // // return the data aggregated by key: // { // key0: 12, // key1: 32 // key2: 25 // } // aggregateByKey: function(rows) { function getKeys(row) { var HEADER_SIZE = 3; var valuesCount = row.data[2]; var keys = {}; for (var s = 0; s < valuesCount; ++s) { keys[row.data[HEADER_SIZE + s]] = row.data[HEADER_SIZE + valuesCount + s]; } return keys; } var keys = {}; for (r = 0; r < rows.length; ++r) { var rowKeys = getKeys(rows[r]); for(var k in rowKeys) { keys[k] = keys[k] || 0; keys[k] += rowKeys[k]; } } return keys; }, /** * */ proccessTile: function(rows, coord, zoom) { var r; var x = new Uint8Array(rows.length); var y = new Uint8Array(rows.length); var self = this; // decode into a javascript strcuture the array function decode_row(row) { var HEADER_SIZE = 3; var o = { x: row.data[0] * self.options.resolution, y: row.data[1] * self.options.resolution, valuesCount: row.data[2], times: [], values: [] }; for (var s = 0; s < o.valuesCount; ++s) { o.times.push(row.data[HEADER_SIZE + s]); o.values.push(row.data[HEADER_SIZE + o.valuesCount + s]); } if(self.options.cumulative) { for (var s = 1; s < o.valuesCount; ++s) { o.values[s] += o.values[s - 1]; } } return o } // decode all the rows for (r = 0; r < rows.length; ++r) { rows[r] = decode_row(rows[r]); } // count number of dates var dates = 0; var maxDateSlots = 0; for (r = 0; r < rows.length; ++r) { var row = rows[r]; dates += row.times.length; for(var d = 0; d < row.times.length; ++d) { maxDateSlots = Math.max(maxDateSlots, row.times[d]); } } // reserve memory for all the dates var timeIndex = new Int32Array(maxDateSlots + 1); //index-size var timeCount = new Int32Array(maxDateSlots + 1); var renderData = new (this.options.valueDataType || Uint8Array)(dates); var renderDataPos = new Uint32Array(dates); var rowsPerSlot = {}; // precache pixel positions for (var r = 0; r < rows.length; ++r) { var row = rows[r]; x[r] = row.x; y[r] = row.y; var dates = row.times; var vals = row.values; for (var j = 0, len = dates.length; j < len; ++j) { var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []); rr.push([r, vals[j]]); } } // for each timeslot search active buckets var renderDataIndex = 0; var timeSlotIndex = 0; var i = 0; for(var i = 0; i <= maxDateSlots; ++i) { var c = 0; var slotRows = rowsPerSlot[i] if(slotRows) { for (var r = 0; r < slotRows.length; ++r) { var rr = slotRows[r]; ++c; renderDataPos[renderDataIndex] = rr[0] renderData[renderDataIndex] = rr[1]; ++renderDataIndex; } } timeIndex[i] = timeSlotIndex; timeCount[i] = c; timeSlotIndex += c; } return { x: x, y: y, coord: { x: coord.x, y: coord.y, z: zoom }, timeCount: timeCount, timeIndex: timeIndex, renderDataPos: renderDataPos, renderData: renderData }; }, url: function() { return this.options.url; }, tileUrl: function(coord, zoom) { var template = this.url(); var s = (this.options.subdomains || 'abcd')[(coord.x + coord.y + zoom) % 4]; return template .replace('{x}', coord.x) .replace('{y}', coord.y) .replace('{z}', zoom) .replace('{s}', s); }, getTile: function(coord, zoom, callback) { var template = this.tileUrl(coord, zoom); var self = this; var fetchTime = Profiler.metric('jsonarray:fetch time'); fetchTime.start(); torque.net.get(template, function (data) { fetchTime.end(); if(data) { data = JSON.parse(data.responseText); } callback(data); }); }, /** * `coord` object like {x : tilex, y: tiley } * `zoom` quadtree zoom level */ getTileData: function(coord, zoom, callback) { var template = this.tileUrl(coord, zoom); var self = this; var fetchTime = Profiler.metric('jsonarray:fetch time'); fetchTime.start(); torque.net.get(template, function (data) { fetchTime.end(); var processed = null; var processingTime = Profiler.metric('jsonarray:processing time'); var parsingTime = Profiler.metric('jsonarray:parsing time'); try { processingTime.start(); parsingTime.start(); var rows = JSON.parse(data.responseText || data.response).rows; parsingTime.end(); processed = self.proccessTile(rows, coord, zoom); processingTime.end(); } catch(e) { console.error("problem parsing JSON on ", coord, zoom); } callback(processed); }); } }; module.exports = json; },{"../":11,"../profiler":22}],26:[function(require,module,exports){ var torque = require('../'); var Uint8Array = torque.types.Uint8Array; var Int32Array = torque.types.Int32Array; var Uint32Array = torque.types.Uint32Array; var Uint8ClampedArray = torque.types.Uint8ClampedArray; // 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 tileJSON = function (options) { this._ready = false; this._tileQueue = []; this.options = options; this.options.coordinates_data_type = this.options.coordinates_data_type || Uint8Array; if (this.options.data_aggregation) { this.options.cumulative = this.options.data_aggregation === 'cumulative'; } if (this.options.auth_token) { var e = this.options.extra_params || (this.options.extra_params = {}); e.auth_token = this.options.auth_token; } if (!this.options.no_fetch_map) { this._fetchMap(); } }; tileJSON.prototype = { NAME: "tileJSON", /** * return the torque tile encoded in an efficient javascript * structure: * { * x:Uint8Array x coordinates in tile reference system, normally from 0-255 * y:Uint8Array y coordinates in tile reference system * Index: Array index to the properties * } */ proccessTile: function(rows, coord, zoom) { var r; var x = new this.options.coordinates_data_type(rows.length); var y = new this.options.coordinates_data_type(rows.length); // count number of dates var dates = 0; var maxDateSlots = -1; for (r = 0; r < rows.length; ++r) { var row = rows[r]; dates += row.dates__uint16.length; for(var d = 0; d < row.dates__uint16.length; ++d) { maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]); } } if(this.options.cumulative) { dates = (1 + maxDateSlots) * rows.length; } var type = this.options.cumulative ? Uint32Array: Uint8ClampedArray; // reserve memory for all the dates var timeIndex = new Int32Array(maxDateSlots + 1); //index-size var timeCount = new Int32Array(maxDateSlots + 1); var renderData = new (this.options.valueDataType || type)(dates); var renderDataPos = new Uint32Array(dates); var rowsPerSlot = {}; // precache pixel positions for (var r = 0; r < rows.length; ++r) { var row = rows[r]; x[r] = row.x__uint8 * this.options.resolution; y[r] = row.y__uint8 * this.options.resolution; var dates = row.dates__uint16; var vals = row.vals__uint8; 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]); } } } // 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, z: zoom, coord: { x: coord.x, y: coord.y, z: zoom }, timeCount: timeCount, timeIndex: timeIndex, renderDataPos: renderDataPos, renderData: renderData, maxDate: maxDateSlots }; }, setSteps: function(steps, opt) { opt = opt || {}; if (this.options.steps !== steps) { this.options.steps = steps; this.options.step = (this.options.end - this.options.start)/this.getSteps(); this.options.step = this.options.step || 1; if (!opt.silent) this.reload(); } }, setOptions: function(opt) { var refresh = false; if(opt.resolution !== undefined && opt.resolution !== this.options.resolution) { this.options.resolution = opt.resolution; refresh = true; } if(opt.steps !== undefined && opt.steps !== this.options.steps) { this.setSteps(opt.steps, { silent: true }); refresh = true; } if(opt.column !== undefined && opt.column !== this.options.column) { this.options.column = opt.column; refresh = true; } if(opt.countby !== undefined && opt.countby !== this.options.countby) { this.options.countby = opt.countby; refresh = true; } if(opt.data_aggregation !== undefined) { var c = opt.data_aggregation === 'cumulative'; if (this.options.cumulative !== c) { this.options.cumulative = c; refresh = true; } } if (refresh) this.reload(); return refresh; }, _extraParams: function(e) { e = torque.extend(torque.extend({}, e), this.options.extra_params); if (e) { var p = []; for(var k in e) { var v = e[k]; if (v) { if (torque.isArray(v)) { for (var i = 0, len = v.length; i < len; i++) { p.push(k + "[]=" + encodeURIComponent(v[i])); } } else { p.push(k + "=" + encodeURIComponent(v)); } } } return p.join('&'); } return null; }, getTileData: function(coord, zoom, callback) { if(!this._ready) { this._tileQueue.push([coord, zoom, callback]); } else { this._getTileData(coord, zoom, callback); } }, _setReady: function(ready) { this._ready = true; this._processQueue(); this.options.ready && this.options.ready(); }, _processQueue: function() { var item; while (item = this._tileQueue.pop()) { this._getTileData.apply(this, item); } }, /** * `coord` object like {x : tilex, y: tiley } * `zoom` quadtree zoom level */ _getTileData: function(coord, zoom, callback) { var self = this; var subdomains = this.options.subdomains || '0123'; var limit_x = Math.pow(2, zoom); var corrected_x = ((coord.x % limit_x) + limit_x) % limit_x; var index = Math.abs(corrected_x + coord.y) % subdomains.length; var extra = this._extraParams(); var url = this.templateUrl .replace('{x}', corrected_x) .replace('{y}', coord.y) .replace('{z}', zoom) .replace('{s}', subdomains[index]) url += extra; torque.net.get( url , function (data) { if (data && data.responseText) { var rows = JSON.parse(data.responseText); callback(self.proccessTile(rows, coord, zoom)); } else { callback(null); } }); }, getKeySpan: function() { return { start: this.options.start, end: this.options.end, step: this.options.step, steps: this.options.steps, columnType: this.options.column_type }; }, setColumn: function(column, isTime) { this.options.column = column; this.options.is_time = isTime === undefined ? true: false; this.reload(); }, reload: function() { this._ready = false; this._fetchMap(); }, getSteps: function() { return Math.min(this.options.steps, this.options.data_steps); }, getBounds: function() { return this.options.bounds; }, getSQL: function() { return this.options.sql || "select * from " + this.options.table; }, setSQL: function(sql) { if (this.options.sql != sql) { this.options.sql = sql; this.reload(); } }, _isUserTemplateUrl: function(t) { return t && t.indexOf('{user}') !== -1; }, isHttps: function() { return this.options.maps_api_template.indexOf('https') === 0; }, _fetchMap: function(callback) { var self = this; torque.net.get(this.options.tileJSON, function (data) { data = JSON.parse(data.response); if (data) { if (data.errors){ self.options.errorCallback && self.options.errorCallback(data.errors); return; } for(var k in data) { self.options[k] = data[k]; } self.templateUrl = data.tiles[0]; if (self.templateUrl.indexOf("http") !== 0){ self.templateUrl = self.options.tileJSON.substring(0, self.options.tileJSON.lastIndexOf("/") + 1) + self.templateUrl; } self._setReady(true); } }); } }; module.exports = tileJSON; },{"../":11}],27:[function(require,module,exports){ var torque = require('../'); var Profiler = require('../profiler'); var Uint8Array = torque.types.Uint8Array; var Int32Array = torque.types.Int32Array; var Uint32Array = torque.types.Uint32Array; var Uint8ClampedArray = torque.types.Uint8ClampedArray; // 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 windshaft = function (options) { this._ready = false; this._tileQueue = []; this.options = options; this.options.is_time = this.options.is_time === undefined ? true: this.options.is_time; this.options.tiler_protocol = options.tiler_protocol || 'http'; this.options.tiler_domain = options.tiler_domain || 'cartodb.com'; this.options.tiler_port = options.tiler_port || 80; // backwards compatible if (!options.maps_api_template) { this._buildMapsApiTemplate(this.options); } else { this.options.maps_api_template = options.maps_api_template; } this.options.coordinates_data_type = this.options.coordinates_data_type || Uint8Array; if (this.options.data_aggregation) { this.options.cumulative = this.options.data_aggregation === 'cumulative'; } if (this.options.auth_token) { var e = this.options.extra_params || (this.options.extra_params = {}); e.auth_token = this.options.auth_token; } if (!this.options.no_fetch_map) { this._fetchMap(); } }; windshaft.prototype = { /** * return the torque tile encoded in an efficient javascript * structure: * { * x:Uint8Array x coordinates in tile reference system, normally from 0-255 * y:Uint8Array y coordinates in tile reference system * Index: Array index to the properties * } */ proccessTile: function(rows, coord, zoom) { var r; var x = new this.options.coordinates_data_type(rows.length); var y = new this.options.coordinates_data_type(rows.length); var prof_mem = Profiler.metric('torque.provider.windshaft.mem'); var prof_point_count = Profiler.metric('torque.provider.windshaft.points'); var prof_process_time = Profiler.metric('torque.provider.windshaft.process_time').start(); // count number of dates var dates = 0; var maxDateSlots = -1; for (r = 0; r < rows.length; ++r) { var row = rows[r]; dates += row.dates__uint16.length; for(var d = 0; d < row.dates__uint16.length; ++d) { maxDateSlots = Math.max(maxDateSlots, row.dates__uint16[d]); } } if(this.options.cumulative) { dates = (1 + maxDateSlots) * rows.length; } var type = this.options.cumulative ? Uint32Array: Uint8ClampedArray; // reserve memory for all the dates var timeIndex = new Int32Array(maxDateSlots + 1); //index-size var timeCount = new Int32Array(maxDateSlots + 1); var renderData = new (this.options.valueDataType || type)(dates); var renderDataPos = new Uint32Array(dates); prof_mem.inc( 4 * maxDateSlots + // timeIndex 4 * maxDateSlots + // timeCount dates + //renderData dates * 4 ); //renderDataPos prof_point_count.inc(rows.length); var rowsPerSlot = {}; // precache pixel positions for (var r = 0; r < rows.length; ++r) { var row = rows[r]; x[r] = row.x__uint8 * this.options.resolution; y[r] = row.y__uint8 * this.options.resolution; var dates = row.dates__uint16; var vals = row.vals__uint8; if (!this.options.cumulative) { for (var j = 0, len = dates.length; j < len; ++j) { var rr = rowsPerSlot[dates[j]] || (rowsPerSlot[dates[j]] = []); if(this.options.cumulative) { vals[j] += prev_val; } prev_val = vals[j]; rr.push([r, vals[j]]); } } else { var valByDate = {} for (var j = 0, len = dates.length; j < len; ++j) { valByDate[dates[j]] = vals[j]; } var accum = 0; // extend the latest to the end for (var j = dates[0]; j <= maxDateSlots; ++j) { var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []); var v = valByDate[j]; if (v) { accum += v; } rr.push([r, accum]); } /*var lastDateSlot = dates[dates.length - 1]; for (var j = lastDateSlot + 1; j <= maxDateSlots; ++j) { var rr = rowsPerSlot[j] || (rowsPerSlot[j] = []); rr.push([r, prev_val]); } */ } } // for each timeslot search active buckets var renderDataIndex = 0; var timeSlotIndex = 0; var i = 0; for(var i = 0; i <= maxDateSlots; ++i) { var c = 0; var slotRows = rowsPerSlot[i] if(slotRows) { for (var r = 0; r < slotRows.length; ++r) { var rr = slotRows[r]; ++c; renderDataPos[renderDataIndex] = rr[0] renderData[renderDataIndex] = rr[1]; ++renderDataIndex; } } timeIndex[i] = timeSlotIndex; timeCount[i] = c; timeSlotIndex += c; } prof_process_time.end(); return { x: x, y: y, z: zoom, coord: { x: coord.x, y: coord.y, z: zoom }, timeCount: timeCount, timeIndex: timeIndex, renderDataPos: renderDataPos, renderData: renderData, maxDate: maxDateSlots }; }, /*setCartoCSS: function(c) { this.options.cartocss = c; },*/ setSteps: function(steps, opt) { opt = opt || {}; if (this.options.steps !== steps) { this.options.steps = steps; this.options.step = (this.options.end - this.options.start)/this.getSteps(); this.options.step = this.options.step || 1; if (!opt.silent) this.reload(); } }, setOptions: function(opt) { var refresh = false; if(opt.resolution !== undefined && opt.resolution !== this.options.resolution) { this.options.resolution = opt.resolution; refresh = true; } if(opt.steps !== undefined && opt.steps !== this.options.steps) { this.setSteps(opt.steps, { silent: true }); refresh = true; } if(opt.column !== undefined && opt.column !== this.options.column) { this.options.column = opt.column; refresh = true; } if(opt.countby !== undefined && opt.countby !== this.options.countby) { this.options.countby = opt.countby; refresh = true; } if(opt.data_aggregation !== undefined) { var c = opt.data_aggregation === 'cumulative'; if (this.options.cumulative !== c) { this.options.cumulative = c; refresh = true; } } if (refresh) this.reload(); return refresh; }, _extraParams: function(e) { e = torque.extend(torque.extend({}, e), this.options.extra_params); if (e) { var p = []; for(var k in e) { var v = e[k]; if (v) { if (torque.isArray(v)) { for (var i = 0, len = v.length; i < len; i++) { p.push(k + "[]=" + encodeURIComponent(v[i])); } } else { p.push(k + "=" + encodeURIComponent(v)); } } } return p.join('&'); } return null; }, getTileData: function(coord, zoom, callback) { if(!this._ready) { this._tileQueue.push([coord, zoom, callback]); } else { this._getTileData(coord, zoom, callback); } }, _setReady: function(ready) { this._ready = true; this._processQueue(); this.options.ready && this.options.ready(); }, _processQueue: function() { var item; while (item = this._tileQueue.pop()) { this._getTileData.apply(this, item); } }, /** * `coord` object like {x : tilex, y: tiley } * `zoom` quadtree zoom level */ _getTileData: function(coord, zoom, callback) { var self = this; var prof_fetch_time = Profiler.metric('torque.provider.windshaft.tile.fetch').start(); var subdomains = this.options.subdomains || '0123'; var limit_x = Math.pow(2, zoom); var corrected_x = ((coord.x % limit_x) + limit_x) % limit_x; var index = Math.abs(corrected_x + coord.y) % subdomains.length; var url = this.templateUrl .replace('{x}', corrected_x) .replace('{y}', coord.y) .replace('{z}', zoom) .replace('{s}', subdomains[index]) var extra = this._extraParams(); torque.net.get( url + (extra ? "?" + extra: ''), function (data) { prof_fetch_time.end(); if (data && data.responseText) { var rows = JSON.parse(data.responseText); callback(self.proccessTile(rows, coord, zoom)); } else { Profiler.metric('torque.provider.windshaft.tile.error').inc(); callback(null); } }); }, getKeySpan: function() { return { start: this.options.start, end: this.options.end, step: this.options.step, steps: this.options.steps, columnType: this.options.column_type }; }, setColumn: function(column, isTime) { this.options.column = column; this.options.is_time = isTime === undefined ? true: false; this.reload(); }, reload: function() { this._ready = false; this._fetchMap(); }, getSteps: function() { return Math.min(this.options.steps, this.options.data_steps); }, getBounds: function() { return this.options.bounds; }, getSQL: function() { return this.options.sql || "select * from " + this.options.table; }, setSQL: function(sql) { if (this.options.sql != sql) { this.options.sql = sql; this.reload(); } }, _buildMapsApiTemplate: function(opts) { var user = opts.user_name || opts.user; opts.maps_api_template = opts.tiler_protocol + "://" + ((user) ? "{user}.":"") + opts.tiler_domain + ((opts.tiler_port != "") ? (":" + opts.tiler_port) : ""); }, _tilerHost: function() { var opts = this.options; var user = opts.user_name || opts.user; return opts.maps_api_template.replace('{user}', user); }, url: function () { var opts = this.options; var cdn_host = opts.cdn_url; var has_empty_cdn = !cdn_host || (cdn_host && (!cdn_host.http && !cdn_host.https)); if (opts.no_cdn || has_empty_cdn) { return this._tilerHost(); } else { var protocol = this.isHttps() ? 'https': 'http'; var h = protocol + "://"; if (!this.isHttps()) { h += "{s}."; } var cdn_url = cdn_host[protocol]; // build default template url if the cdn url is not templatized // this is for backwards compatiblity, ideally we should use the url // that tiler sends to us right away if (!this._isUserTemplateUrl(cdn_url)) { cdn_url = cdn_url + "/{user}"; } var user = opts.user_name || opts.user; h += cdn_url.replace('{user}', user) return h; } }, _isUserTemplateUrl: function(t) { return t && t.indexOf('{user}') !== -1; }, isHttps: function() { return this.options.maps_api_template.indexOf('https') === 0; }, _generateCartoCSS: function() { var attr = { '-torque-frame-count': this.options.steps, '-torque-resolution': this.options.resolution, '-torque-aggregation-function': "'" + this.options.countby + "'", '-torque-time-attribute': "'" + this.options.column + "'", '-torque-data-aggregation': this.options.cumulative ? 'cumulative': 'linear', }; var st = 'Map{'; for (var k in attr) { st += k + ":" + attr[k] + ";"; } return st + "}"; }, _fetchMap: function(callback) { var self = this; var layergroup = {}; var host = this.options.dynamic_cdn ? this.url().replace('{s}', '0'): this._tilerHost(); var url = host + "/api/v1/map"; var named = this.options.named_map; var allParams = {}; if(named) { //tiles/template url = host + "/api/v1/map/named/" + named.name + "/jsonp"; if(typeof named.params !== "undefined"){ layergroup = named.params; } } else { layergroup = { "version": "1.0.1", "stat_tag": this.options.stat_tag || 'torque', "layers": [{ "type": "torque", "options": { "cartocss_version": "1.0.0", "cartocss": this._generateCartoCSS(), "sql": this.getSQL() } }] }; } if(this.options.stat_tag){ allParams["stat_tag"] = this.options.stat_tag; } extra = this._extraParams(allParams); // 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: ''); var map_instance_time = Profiler.metric('torque.provider.windshaft.layergroup.time').start(); torque.net.jsonp(url, function (data) { map_instance_time.end(); if (data) { if (data.errors){ self.options.errorCallback && self.options.errorCallback(data.errors); return; } var torque_key = Object.keys(data.metadata.torque)[0] var opt = data.metadata.torque[torque_key]; for(var k in opt) { self.options[k] = opt[k]; } // use cdn_url if present if (data.cdn_url) { var c = self.options.cdn_url = self.options.cdn_url || {}; c.http = data.cdn_url.http || c.http; c.https = data.cdn_url.https || c.https; } self.templateUrl = self.url() + "/api/v1/map/" + data.layergroupid + "/" + torque_key + "/{z}/{x}/{y}.json.torque"; self._setReady(true); } else { Profiler.metric('torque.provider.windshaft.layergroup.error').inc(); } }, { callbackName: self.options.instanciateCallback }); } }; module.exports = windshaft; },{"../":11,"../profiler":22}],28:[function(require,module,exports){ var TAU = Math.PI*2; // min value to render a line. // it does not make sense to render a line of a width is not even visible var LINEWIDTH_MIN_VALUE = 0.05; var MAX_SPRITE_RADIUS = 255; function renderPoint(ctx, st) { ctx.fillStyle = st['marker-fill']; var pixel_size = st['marker-width']; // render a circle // TODO: fill and stroke order should depend on the order of the properties // in the cartocss. // fill ctx.beginPath(); ctx.arc(0, 0, pixel_size, 0, TAU, true, true); ctx.closePath(); if (st['marker-opacity'] !== undefined ) st['marker-fill-opacity'] = st['marker-line-opacity'] = st['marker-opacity']; if (st['marker-fill']) { ctx.globalAlpha = st['marker-fill-opacity'] >= 0? st['marker-fill-opacity']: 1; if (ctx.globalAlpha > 0) { ctx.fill(); } } // stroke if (st['marker-line-color'] && st['marker-line-width'] && st['marker-line-width'] > LINEWIDTH_MIN_VALUE) { ctx.globalAlpha = st['marker-line-opacity'] >= 0? st['marker-line-opacity']: 1; if (st['marker-line-width'] !== undefined) { ctx.lineWidth = st['marker-line-width']; } ctx.strokeStyle = st['marker-line-color']; // do not render for alpha = 0 if (ctx.globalAlpha > 0) { ctx.stroke(); } } } function renderRectangle(ctx, st) { ctx.fillStyle = st['marker-fill']; var pixel_size = st['marker-width']; var w = pixel_size * 2; // fill if (st['marker-fill']) { if (st['marker-fill-opacity'] !== undefined || st['marker-opacity'] !== undefined) { ctx.globalAlpha = st['marker-fill-opacity'] || st['marker-opacity']; } ctx.fillRect(-pixel_size, -pixel_size, w, w) } // stroke ctx.globalAlpha = 1.0; if (st['marker-line-color'] && st['marker-line-width']) { if (st['marker-line-opacity']) { ctx.globalAlpha = st['marker-line-opacity']; } if (st['marker-line-width']) { ctx.lineWidth = st['marker-line-width']; } ctx.strokeStyle = st['marker-line-color']; // do not render for alpha = 0 if (ctx.globalAlpha > 0) { ctx.strokeRect(-pixel_size, -pixel_size, w, w) } } } function renderSprite(ctx, img, st) { if(img.complete){ if (st['marker-fill-opacity'] !== undefined || st['marker-opacity'] !== undefined) { ctx.globalAlpha = st['marker-fill-opacity'] || st['marker-opacity']; } ctx.drawImage(img, 0, 0, Math.min(img.width, MAX_SPRITE_RADIUS), Math.min(img.height, MAX_SPRITE_RADIUS)); } } module.exports = { renderPoint: renderPoint, renderSprite: renderSprite, renderRectangle: renderRectangle, MAX_SPRITE_RADIUS: MAX_SPRITE_RADIUS }; },{}],29:[function(require,module,exports){ var d3 = require('d3'); var jenks = require('turf-jenks'); function TorqueDataSource (tiles) { this.tiles = tiles } module.exports = TorqueDataSource TorqueDataSource.prototype.getName = function () { return 'TorqueDataSource' } TorqueDataSource.prototype.getRamp = function (column, bins, method, callback) { var ramp = [] var error = null var values = Object.keys(this.tiles).map(function (t) { return this.tiles[t].renderData; }.bind(this)).reduce(function (p,c,i) { for(var i = 0; i ramp[length - 1]; }))); } } } else { error = new Error('Quantification method ' + method + ' is not supported') } callback(error, ramp) } },{"d3":undefined,"turf-jenks":undefined}],30:[function(require,module,exports){ module.exports = { cartocss: require('./cartocss_render'), Point: require('./point'), Rectangle: require('./rectangle') }; },{"./cartocss_render":28,"./point":31,"./rectangle":32}],31:[function(require,module,exports){ (function (global){ var torque = require('../'); var cartocss = require('./cartocss_render'); var Profiler = require('../profiler'); var carto = global.carto || require('carto'); var Filters = require('./torque_filters'); var turbocarto = require('turbo-carto'); var CartoDatasource = require('./datasource'); var TAU = Math.PI * 2; var DEFAULT_CARTOCSS = [ '#layer {', ' marker-fill: #662506;', ' marker-width: 4;', ' [value > 1] { marker-fill: #FEE391; }', ' [value > 2] { marker-fill: #FEC44F; }', ' [value > 3] { marker-fill: #FE9929; }', ' [value > 4] { marker-fill: #EC7014; }', ' [value > 5] { marker-fill: #CC4C02; }', ' [value > 6] { marker-fill: #993404; }', ' [value > 7] { marker-fill: #662506; }', '}' ].join('\n'); var COMP_OP_TO_CANVAS = { "difference": "difference", "src": 'source-over', "exclusion": "exclusion", "dst": "destination-in", "multiply": "multiply", "contrast": "contrast", "src-over": 'source-over', "screen": "screen", "invert": "invert", "dst-over": 'destination-over', "overlay": "overlay", "invert-rgb": "invert", "src-in": 'source-in', "darken": 'darken', "dst-in": 'destination-in', "lighten": 'lighten', "src-out": 'source-out', "color-dodge": "color-dodge", "hue":"hue", "dst-out": 'destination-out', "color-burn":"color-burn", "saturation":"saturation", "src-atop": 'source-atop', "hard-light":"hard-light", "color":"color", "dst-atop": 'destination-atop', "soft-light":"soft-light", "xor": 'xor' } function compop2canvas(compop) { return COMP_OP_TO_CANVAS[compop] || compop; } // // 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.layer = options.layer; this._canvas = canvas; this._ctx = canvas.getContext('2d'); this._sprites = []; // sprites per layer this._shader = null; this._icons = {}; this._iconsToLoad = 0; this._filters = new Filters(this._canvas, {canvasClass: options.canvasClass}); this.style = this.options.cartocss || DEFAULT_CARTOCSS; this.setCartoCSS(this.style); this.TILE_SIZE = 256; this._style = null; this._gradients = {}; this._forcePoints = false; } torque.extend(PointRenderer.prototype, torque.Event, { clearCanvas: function() { if (this._Map) { var canvas = this._canvas; var color = this._Map['-torque-clear-color'] // shortcut for the default value var ctx = this._ctx; if (color === "rgba(255, 255, 255, 0)" || !color) { ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.restore(); } else { ctx.setTransform(1, 0, 0, 1, 0, 0); var compop = this._Map['comp-op'] ctx.globalCompositeOperation = compop2canvas(compop) || compop; ctx.fillStyle = color; ctx.fillRect(0, 0, canvas.width, canvas.height); } } }, setCanvas: function(canvas) { this._canvas = canvas; this._ctx = canvas.getContext('2d'); }, // // sets the cartocss style to render stuff // setCartoCSS: function(cartocss, callback) { var self = this; this.style = cartocss; if (PointRenderer.isTurboCarto(cartocss)) { var datasource = new CartoDatasource(self.layer._tiles); turbocarto(cartocss, datasource, function (err, parsedCartoCSS) { self.setShader(new carto.RendererJS().render(parsedCartoCSS)); self.layer.redraw(); self.layer.animator.start(); callback && callback(); }); } else { self.setShader(new carto.RendererJS().render(cartocss)); callback && callback(); } }, setShader: function(shader) { // clean sprites this._sprites = []; this._shader = shader; this._Map = this._shader.getDefault().getStyle({}, { zoom: 0 }); var img_names = this._shader.getImageURLs(); this._preloadIcons(img_names); }, clearSpriteCache: function() { this._sprites = []; }, // // generate sprite based on cartocss style // generateSprite: function(shader, value, shaderVars) { var self = this; var prof = Profiler.metric('torque.renderer.point.generateSprite').start(); var st = shader.getStyle({ value: value }, shaderVars); if(this._style === null || this._style !== st){ this._style = st; } var pointSize = st['marker-width']; if (!pointSize) { return null; } if (st['marker-opacity'] === 0 && !st['marker-line-opacity']) { return null; } var canvas = this._createCanvas(); var ctx = canvas.getContext('2d'); var markerFile = st["marker-file"] || st["point-file"]; var qualifiedUrl = markerFile && this._qualifyURL(markerFile); if (qualifiedUrl && this._iconsToLoad <= 0 && this._icons[qualifiedUrl]) { var img = this._icons[qualifiedUrl]; var dWidth = Math.min(st['marker-width'] * 2 || img.width, cartocss.MAX_SPRITE_RADIUS * 2); var dHeight = Math.min((st['marker-height'] || dWidth) * (img.width / img.height), cartocss.MAX_SPRITE_RADIUS * 2); canvas.width = ctx.width = dWidth; canvas.height = ctx.height = dHeight; ctx.scale(dWidth/img.width, dHeight/img.height); cartocss.renderSprite(ctx, img, st); } else { // take into account the exterior ring to calculate the size var canvasSize = (st['marker-line-width'] || 0) + pointSize*2; var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize); ctx.translate(w/2, w/2); var mt = st['marker-type']; if (mt && mt === 'rectangle') { cartocss.renderRectangle(ctx, st); } else { cartocss.renderPoint(ctx, st); } } prof.end(true); if (torque.flags.sprites_to_images) { var i = this._createImage(); i.src = canvas.toDataURL(); return i; } return canvas; }, // // renders all the layers (and frames for each layer) from cartocss // renderTile: function(tile, keys, callback) { if (this._iconsToLoad > 0) { this.on('allIconsLoaded', function() { this.renderTile.apply(this, [tile, keys, callback]); }); return false; } // convert scalar key to keys array if (typeof keys.length === 'undefined') { keys = [keys]; } var prof = Profiler.metric('torque.renderer.point.renderLayers').start(); var layers = this._shader.getLayers(); for(var i = 0, n = layers.length; i < n; ++i ) { var layer = layers[i]; 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] = []); for (var k = 0, len = keys.length; k < len; k++) { this._renderTile(tile, keys[k] - frame, frame, fr_sprites, layer); } } } } prof.end(true); return callback && callback(null); }, _createCanvas: function() { return this.options.canvasClass ? new this.options.canvasClass() : document.createElement('canvas'); }, _createImage: function() { return this.options.imageClass ? new this.options.imageClass() : new Image(); }, _setImageSrc: function(img, url, callback) { if (this.options.setImageSrc) { this.options.setImageSrc(img, url, callback); } else { img.onload = function(){ callback(null); }; img.onerror = function(){ callback(new Error('Could not load image')); }; img.src = url; } }, _qualifyURL: function(url) { if (typeof this.options.qualifyURL !== "undefined"){ return this.options.qualifyURL(url); } else{ var a = document.createElement('a'); a.href = url; return a.href; } }, // // renders a tile in the canvas for key defined in // the torque tile // _renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) { if (!this._canvas) return; var prof = Profiler.metric('torque.renderer.point.renderTile').start(); var ctx = this._ctx; var blendMode = compop2canvas(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; } var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1) var activePixels = tile.timeCount[key]; var anchor = this.options.resolution/2; 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 === undefined) { sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars)); } if (sp) { var x = tile.x[posIdx]- (sp.width >> 1) + anchor; var y = tileMax - tile.y[posIdx] + anchor; // flip mercator ctx.drawImage(sp, x, y - (sp.height >> 1)); } } } } prof.end(true); }, setBlendMode: function(b) { this.options.blendmode = b; }, /** * get active points for a step in active zoom * returns a list of bounding boxes [[sw, ne] , [], []] where ne is a {lat: .., lon: ...} obj * empty list if there is no active pixels */ getActivePointsBBox: function(tile, step) { var positions = []; var mercator = new torque.Mercator(); var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1); //this.renderer.renderTile(tile, this.key, pos.x, pos.y); var activePixels = tile.timeCount[step]; var pixelIndex = tile.timeIndex[step]; for(var p = 0; p < activePixels; ++p) { var posIdx = tile.renderDataPos[pixelIndex + p]; var c = tile.renderData[pixelIndex + p]; if (c) { var x = tile.x[posIdx]; var y = tileMax - tile.y[posIdx]; // flip mercator positions.push(mercator.tilePixelBBox( tile.coord.x, tile.coord.y, tile.coord.z, x, y )); } } return positions; }, /** * returns an array with all the values for the active pixels * @tile tile object * @step integer with the step * @values (optional) an array where the values will be placed */ getValues: function(tile, step, values) { values = values || []; var activePixels = tile.timeCount[step]; var pixelIndex = tile.timeIndex[step]; for(var p = 0; p < activePixels; ++p) { var posIdx = tile.renderDataPos[pixelIndex + p]; values.push(tile.renderData[pixelIndex + p]); } return values; }, // return the value for x, y (tile coordinates) // null for no value getValueFor: function(tile, step, px, py) { var mercator = new torque.Mercator(); var res = this.options.resolution; var res2 = res >> 1; var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1); //this.renderer.renderTile(tile, this.key, pos.x, pos.y); var activePixels = tile.timeCount[step]; var pixelIndex = tile.timeIndex[step]; for(var p = 0; p < activePixels; ++p) { var posIdx = tile.renderDataPos[pixelIndex + p]; var c = tile.renderData[pixelIndex + p]; if (c) { var x = tile.x[posIdx]; var y = tileMax - tile.y[posIdx]; var dx = px + res2 - x; var dy = py + res2 - y; if (dx >= 0 && dx < res && dy >= 0 && dy < res) { return { value: c, bbox: mercator.tilePixelBBox( tile.coord.x, tile.coord.y, tile.coord.z, x - res2, y - res2, res ) } } } } return null; }, _preloadIcons: function(img_names) { var self = this; if (img_names.length > 0 && !this._forcePoints) { var qualifiedImageUrlSet = Object.keys(img_names.reduce(function(imgNamesMap, imgName) { var qualifiedUrl = self._qualifyURL(imgName); if (!self._icons[qualifiedUrl]) { imgNamesMap[qualifiedUrl] = true; } return imgNamesMap; }, {})); var filtered = self._shader.getLayers().some(function(layer) { return typeof layer.shader["image-filters"] !== "undefined"; }); this._iconsToLoad += qualifiedImageUrlSet.length; qualifiedImageUrlSet.forEach(function(qualifiedImageUrl) { self._icons[qualifiedImageUrl] = null; var img = self._createImage(); if (filtered) { img.crossOrigin = 'Anonymous'; } self._setImageSrc(img, qualifiedImageUrl, function(err) { if (err) { self._forcePoints = true; self.clearSpriteCache(); self._iconsToLoad = 0; self.fire("allIconsLoaded"); if(filtered) { console.info("Only CORS-enabled, or same domain image-files can be used in combination with image-filters"); } console.error("Couldn't get marker-file " + qualifiedImageUrl); } else { self._icons[qualifiedImageUrl] = img; self._iconsToLoad--; if (self._iconsToLoad <= 0){ self.clearSpriteCache(); self.fire("allIconsLoaded"); } } }); }); } else { this.fire("allIconsLoaded"); } }, applyFilters: function(){ if(this._style){ if(this._style['image-filters']){ function gradientKey(imf){ var hash = "" for(var i = 0; i < imf.args.length; i++){ var rgb = imf.args[i].rgb; hash += rgb[0] + ":" + rgb[1] + ":" + rgb[2]; } return hash; } var gradient = this._gradients[gradientKey(this._style['image-filters'])]; if(!gradient){ function componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } function rgbToHex(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); } gradient = {}; var colorize = this._style['image-filters'].args; var increment = 1/colorize.length; for (var i = 0; i < colorize.length; i++){ var key = increment * i + increment; var rgb = colorize[i].rgb; var formattedColor = rgbToHex(rgb[0], rgb[1], rgb[2]); gradient[key] = formattedColor; } this._gradients[gradientKey(this._style['image-filters'])] = gradient; } this._filters.gradient(gradient); this._filters.draw(); } } } }); PointRenderer.isTurboCarto = function (cartocss) { var reservedWords = ['ramp', 'colorbrewer', 'buckets'] var isTurbo = reservedWords .map(function (w) { return w + '(' }) .map(String.prototype.indexOf.bind(cartocss)) .every(function (f) { return f === -1 }) return !isTurbo } // exports public api module.exports = PointRenderer; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../":11,"../profiler":22,"./cartocss_render":28,"./datasource":29,"./torque_filters":33,"carto":undefined,"turbo-carto":undefined}],32:[function(require,module,exports){ (function (global){ var carto = global.carto || require('carto'); var DEFAULT_CARTOCSS = [ '#layer {', ' polygon-fill: #FFFF00;', ' [value > 10] { polygon-fill: #FFFF00; }', ' [value > 100] { polygon-fill: #FFCC00; }', ' [value > 1000] { polygon-fill: #FE9929; }', ' [value > 10000] { polygon-fill: #FF6600; }', ' [value > 100000] { polygon-fill: #FF3300; }', '}' ].join('\n'); var TAU = Math.PI * 2; // // this renderer just render points depending of the value // function RectanbleRenderer(canvas, options) { this.options = options; carto.tree.Reference.set(torque['torque-reference']); this.setCanvas(canvas); this.setCartoCSS(this.options.cartocss || DEFAULT_CARTOCSS); } RectanbleRenderer.prototype = { // // sets the cartocss style to render stuff // setCartoCSS: function(cartocss) { this._cartoCssStyle = new carto.RendererJS().render(cartocss); if(this._cartoCssStyle.getLayers().length > 1) { throw new Error("only one CartoCSS layer is supported"); } this._shader = this._cartoCssStyle.getLayers()[0].shader; }, setCanvas: function(canvas) { if(!canvas) return; this._canvas = canvas; this._ctx = canvas.getContext('2d'); }, accumulate: function(tile, keys) { var prof = Profiler.metric('RectangleRender:accumulate').start(); var x, y, posIdx, p, k, key, activePixels, pixelIndex; var res = this.options.resolution; var s = 256/res; var accum = new Float32Array(s*s); if(typeof(keys) !== 'object') { keys = [keys]; } for(k = 0; k < keys.length; ++k) { key = keys[k]; activePixels = tile.timeCount[key]; if(activePixels) { pixelIndex = tile.timeIndex[key]; for(p = 0; p < activePixels; ++p) { posIdx = tile.renderDataPos[pixelIndex + p]; x = tile.x[posIdx]/res; y = tile.y[posIdx]/res; accum[x*s + y] += tile.renderData[pixelIndex + p]; } } } prof.end(); return accum; }, renderTileAccum: function(accum, px, py) { var prof = Profiler.metric('RectangleRender:renderTileAccum').start(); var color, x, y, alpha; var res = this.options.resolution; var ctx = this._ctx; var s = (256/res) | 0; var s2 = s*s; var colors = this._colors; if(this.options.blendmode) { ctx.globalCompositeOperation = this.options.blendmode; } var polygon_alpha = this._shader['polygon-opacity'] || function() { return 1.0; }; for(var i = 0; i < s2; ++i) { var xy = i; var value = accum[i]; if(value) { x = (xy/s) | 0; y = xy % s; // by-pass the style generation for improving performance color = this._shader['polygon-fill']({ value: value }, { zoom: 0 }); ctx.fillStyle = color; //TODO: each function should have a default value for each //property defined in the cartocss alpha = polygon_alpha({ value: value }, { zoom: 0 }); if(alpha === null) { alpha = 1.0; } ctx.globalAlpha = alpha; ctx.fillRect(x * res, 256 - res - y * res, res, res); } } prof.end(); }, // // renders a tile in the canvas for key defined in // the torque tile // renderTile: function(tile, key, callback) { 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(); return callback && callback(null); } }; // exports public api module.exports = RectanbleRenderer; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"carto":undefined}],33:[function(require,module,exports){ /* Based on simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas, by Vladimir Agafonkin https://github.com/mourner/simpleheat */ 'use strict'; function torque_filters(canvas, options) { // jshint newcap: false, validthis: true if (!(this instanceof torque_filters)) { return new torque_filters(canvas, options); } options = options || {}; this._canvas = canvas = typeof canvas === 'string' ? document.getElementById(canvas) : canvas; this._ctx = canvas.getContext('2d'); this._width = canvas.width; this._height = canvas.height; this._max = 1; this._data = []; this.canvasClass = options.canvasClass; } torque_filters.prototype = { defaultGradient: { 0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red' }, gradient: function (grad) { // create a 256x1 gradient that we'll use to turn a grayscale heatmap into a colored one var canvas = this._createCanvas(), ctx = canvas.getContext('2d'), gradient = ctx.createLinearGradient(0, 0, 0, 256); canvas.width = 1; canvas.height = 256; for (var i in grad) { gradient.addColorStop(+i, grad[i]); } ctx.fillStyle = gradient; ctx.fillRect(0, 0, 1, 256); this._grad = ctx.getImageData(0, 0, 1, 256).data; return this; }, draw: function () { if (!this._grad) { this.gradient(this.defaultGradient); } var ctx = this._ctx; var colored = ctx.getImageData(0, 0, this._canvas.width, this._canvas.height); this._colorize(colored.data, this._grad); ctx.putImageData(colored, 0, 0); return this; }, _colorize: function (pixels, gradient) { for (var i = 3, len = pixels.length, j; i < len; i += 4) { j = pixels[i] * 4; // get gradient color from opacity value if (j) { pixels[i - 3] = gradient[j]; pixels[i - 2] = gradient[j + 1]; pixels[i - 1] = gradient[j + 2]; } } }, _createCanvas: function() { return this.canvasClass ? new this.canvasClass() : document.createElement('canvas'); } }; module.exports = torque_filters; },{}],34:[function(require,module,exports){ (function (global){ var torque = require('./core'); var lastCall = null; function jsonp(url, callback, options) { options = options || {}; options.timeout = options.timeout === undefined ? 10000: options.timeout; var head = document.getElementsByTagName('head')[0]; var script = document.createElement('script'); // function name var fnName = options.callbackName || 'torque_' + Date.now(); if (torque.isFunction(fnName)) { fnName = fnName(); } 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); } function get(url, callback, options) { options = options || { method: 'GET', data: null, responseType: 'text' }; lastCall = { url: url, callback: callback }; var request = XMLHttpRequest; // from d3.js if (global.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = XDomainRequest; var req = new request(); req.open(options.method, url, true); function respond() { var status = req.status, result; var r = options.responseType === 'arraybuffer' ? req.response: req.responseText; if (!status && r || status >= 200 && status < 300 || status === 304) { callback(req); } else { callback(null); } } "onload" in req ? req.onload = req.onerror = respond : req.onreadystatechange = function() { req.readyState > 3 && respond(); }; req.onprogress = function() {}; req.responseType = options.responseType; //'arraybuffer'; 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); return req; } function post(url, data, callback) { return get(url, callback, { data: data, method: "POST" }); } module.exports = { get: get, post: post, jsonp: jsonp, lastCall: function() { return lastCall; } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./core":5}]},{},[11])(11) });