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