diff --git a/examples/navy_leaflet.html b/examples/navy_leaflet.html index 3f2561e..4c47ade 100644 --- a/examples/navy_leaflet.html +++ b/examples/navy_leaflet.html @@ -23,7 +23,7 @@ '-torque-aggregation-function: "count(cartodb_id)";', '-torque-frame-count: 760;', '-torque-animation-duration: 15;', - '-torque-resolution: 2', + '-torque-resolution: 1', '}', '#layer {', ' marker-width: 3;', @@ -57,7 +57,9 @@ table : 'ow', cartocss: CARTOCSS }); + torqueLayer.addTo(map); + //torqueLayer.setStep(30); torqueLayer.play() diff --git a/lib/torque/leaflet/canvas_layer.js b/lib/torque/leaflet/canvas_layer.js index c6bd094..8bb66a9 100644 --- a/lib/torque/leaflet/canvas_layer.js +++ b/lib/torque/leaflet/canvas_layer.js @@ -18,8 +18,7 @@ L.CanvasLayer = L.Class.extend({ opacity: 1, unloadInvisibleTiles: L.Browser.mobile, updateWhenIdle: L.Browser.mobile, - tileLoader: false, // installs tile loading events - zoomAnimation: true + tileLoader: true// installs tile loading events }, initialize: function (options) { @@ -30,10 +29,7 @@ L.CanvasLayer = L.Class.extend({ L.Util.setOptions(this, options); this._canvas = this._createCanvas(); // backCanvas for zoom animation - if (this.options.zoomAnimation) { - this._backCanvas = this._createCanvas(); - } - this._ctx = this._canvas.getContext('2d'); + this._backCanvas = this._createCanvas(); this.currentAnimationFrame = -1; this.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { @@ -51,10 +47,7 @@ L.CanvasLayer = L.Class.extend({ canvas.style.left = 0; canvas.style.pointerEvents = "none"; canvas.style.zIndex = this.options.zIndex || 0; - var className = 'leaflet-tile-container'; - if (this.options.zoomAnimation) { - className += ' leaflet-zoom-animated'; - } + var className = 'leaflet-tile-container leaflet-zoom-animated'; canvas.setAttribute('class', className); return canvas; }, @@ -68,10 +61,8 @@ L.CanvasLayer = L.Class.extend({ var tilePane = this._map._panes.tilePane; var _container = L.DomUtil.create('div', 'leaflet-layer'); _container.appendChild(this._canvas); - if (this.options.zoomAnimation) { - _container.appendChild(this._backCanvas); - this._backCanvas.style.display = 'none'; - } + _container.appendChild(this._backCanvas); + this._backCanvas.style.display = 'none'; tilePane.appendChild(_container); this._container = _container; @@ -87,14 +78,6 @@ L.CanvasLayer = L.Class.extend({ map.on('move', this.render, this); map.on('resize', this._reset, this); - if (this.options.zoomAnimation) { - map.on({ - 'zoomanim': this._animateZoom, - 'zoomend': this._endZoomAnim, - 'moveend': this._reset - }, this); - } - if(this.options.tileLoader) { this._initTileLoader(); } @@ -103,45 +86,40 @@ L.CanvasLayer = L.Class.extend({ }, _animateZoom: function (e) { - if (!this._animating) { - this._animating = true; - } - var back = this._backCanvas; + if (!this._animating) { + this._animating = true; + } + var back = this._backCanvas; - back.width = this._canvas.width; - back.height = this._canvas.height; + back.width = this._canvas.width; + back.height = this._canvas.height; - // paint current canvas in back canvas with trasnformation - var pos = this._canvas._leaflet_pos || { x: 0, y: 0 }; - back.getContext('2d').drawImage(this._canvas, 0, 0); + // paint current canvas in back canvas with trasnformation + var pos = this._canvas._leaflet_pos || { x: 0, y: 0 }; + back.getContext('2d').drawImage(this._canvas, 0, 0); - L.DomUtil.setPosition(back, L.DomUtil.getPosition(this._canvas)); + // hide original + this._canvas.style.display = 'none'; + back.style.display = 'block'; + var map = this._map; + var scale = map.getZoomScale(e.zoom); + var newCenter = map._latLngToNewLayerPoint(map.getCenter(), e.zoom, e.center); + var oldCenter = map._latLngToNewLayerPoint(e.center, e.zoom, e.center); - // hide original - this._canvas.style.display = 'none'; - back.style.display = 'block'; - var map = this._map; - var scale = map.getZoomScale(e.zoom); - var newCenter = map._latLngToNewLayerPoint(map.getCenter(), e.zoom, e.center); - var oldCenter = map._latLngToNewLayerPoint(e.center, e.zoom, e.center); + var origin = { + x: newCenter.x - oldCenter.x, + y: newCenter.y - oldCenter.y + }; - var origin = { - x: newCenter.x - oldCenter.x + pos.x, - y: newCenter.y - oldCenter.y + pos.y, - }; - - var bg = back; - var transform = L.DomUtil.TRANSFORM; - setTimeout(function() { - bg.style[transform] = L.DomUtil.getTranslateString(origin) + ' scale(' + e.scale + ') '; - }, 0) + var bg = back; + var transform = L.DomUtil.TRANSFORM; + bg.style[transform] = L.DomUtil.getTranslateString(origin) + ' scale(' + e.scale + ') '; }, _endZoomAnim: function () { - this._animating = false; - this._canvas.style.display = 'block'; - this._backCanvas.style.display = 'none'; - this._backCanvas.style[L.DomUtil.TRANSFORM] = ''; + this._animating = false; + this._canvas.style.display = 'block'; + this._backCanvas.style.display = 'none'; }, getCanvas: function() { @@ -161,7 +139,6 @@ L.CanvasLayer = L.Class.extend({ map.off({ 'viewreset': this._reset, 'move': this._render, - 'moveend': this._reset, 'resize': this._reset, 'zoomanim': this._animateZoom, 'zoomend': this._endZoomAnim @@ -181,9 +158,6 @@ L.CanvasLayer = L.Class.extend({ setZIndex: function(zIndex) { this._canvas.style.zIndex = zIndex; - if (this.options.zoomAnimation) { - this._backCanvas.style.zIndex = zIndex; - } }, bringToFront: function () { @@ -198,12 +172,6 @@ L.CanvasLayer = L.Class.extend({ var size = this._map.getSize(); this._canvas.width = size.x; this._canvas.height = size.y; - - // fix position - var pos = L.DomUtil.getPosition(this._map.getPanes().mapPane); - if (pos) { - L.DomUtil.setPosition(this._canvas, { x: -pos.x, y: -pos.y }); - } this.onResize(); this._render(); }, diff --git a/lib/torque/leaflet/torque.js b/lib/torque/leaflet/torque.js index 470f4b2..3974fb7 100644 --- a/lib/torque/leaflet/torque.js +++ b/lib/torque/leaflet/torque.js @@ -48,8 +48,9 @@ L.TorqueLayer = L.CanvasLayer.extend({ L.CanvasLayer.prototype.initialize.call(this, options); - this.options.renderer = this.options.renderer || 'point'; + //this.options.renderer = this.options.renderer || 'point'; this.options.provider = this.options.provider || 'windshaft'; + this.renderer = new WebGLRenderer(this._canvas); options.ready = function() { self.fire("change:bounds", { @@ -64,7 +65,7 @@ L.TorqueLayer = L.CanvasLayer.extend({ }; this.provider = new this.providers[this.options.provider](options); - this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options); + //this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options); // for each tile shown on the map request the data @@ -72,6 +73,28 @@ L.TorqueLayer = L.CanvasLayer.extend({ 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; + + // prepare vb + var xy = []; + var keys = {} + var init, end; + for(var key = 0; key < tileData.maxDate; ++key) { + init = xy.length/2; + var activePixels = tileData.timeCount[key]; + if(activePixels) { + var pixelIndex = tileData.timeIndex[key]; + for(var p = 0; p < activePixels; ++p) { + var posIdx = tileData.renderDataPos[pixelIndex + p]; + var c = tileData.renderData[pixelIndex + p]; + xy.push(tileData.x[posIdx]); + xy.push(255 - tileData.y[posIdx]); + } + } + end = xy.length/2; + keys[key] = [init, end - init + 1]; + } + tileData.vb = self.renderer.createVertexBuffer(xy); + tileData.keys = keys; self._tileLoaded(t, tileData); if (tileData) { self.redraw(); @@ -179,17 +202,55 @@ L.TorqueLayer = L.CanvasLayer.extend({ render: function() { if(this.hidden) return; var t, tile, pos; + if (!this.renderer) return; + var gl = this.renderer.gl; var canvas = this.getCanvas(); - canvas.width = canvas.width; - var ctx = canvas.getContext('2d'); + //var zoom = gl.getUniformLocation(this.renderer.activeProgram(), "zoom"); + var tilePos = gl.getUniformLocation(this.renderer.activeProgram(), "tilePos"); + var mapSize = gl.getUniformLocation(this.renderer.activeProgram(), "mapSize"); + var pSize = gl.getUniformLocation(this.renderer.activeProgram(), "pSize"); + //var center = L.CRS.EPSG3857.project(this._map.getCenter()); + //var _time = gl.getUniformLocation(this.renderer.activeProgram(), "iGlobalTime"); + + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.disable(gl.DEPTH_TEST); + //gl.depthFunc(gl.LEQUAL); + gl.clear(gl.COLOR_BUFFER_BIT); + + //gl.uniform1f(zoom, this._map.getZoom()); + gl.uniform2fv(mapSize, [ + this._map.getSize().x, + this._map.getSize().y + ]); + + var self = this; + function renderKey(tile, key, s) { + if (tile && tile.keys[key]) { + var count = tile.keys[key][1]; + if(count) { + var offset = tile.keys[key][0]; + pos = self.getTilePos(tile.coord); + gl.uniform1f(pSize, s); + gl.uniform2fv(tilePos, [ + pos.x - self._map.getSize().x/2, + -pos.y + self._map.getSize().y/2 + ]); + setBufferData(gl, self.renderer.activeProgram(), "pos", tile.vb); + gl.drawArrays(gl.POINT, offset, count); + } + } + } 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.key, pos.x, pos.y); - } + renderKey(tile, this.key, 10); + renderKey(tile, this.key - 1, 9); + renderKey(tile, this.key - 2, 8); + renderKey(tile, this.key - 3, 7); + renderKey(tile, this.key - 4, 6); + renderKey(tile, this.key - 5, 5); + renderKey(tile, this.key - 6, 4); } }, diff --git a/lib/torque/leaflet/webgl-debug.js b/lib/torque/leaflet/webgl-debug.js new file mode 100644 index 0000000..f8b9f57 --- /dev/null +++ b/lib/torque/leaflet/webgl-debug.js @@ -0,0 +1,863 @@ +/* +** Copyright (c) 2012 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and /or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +// Various functions for helping debug WebGL apps. + +WebGLDebugUtils = function() { + +/** + * Wrapped logging function. + * @param {string} msg Message to log. + */ +var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } +}; + +/** + * Which arguements are enums. + * @type {!Object.} + */ +var glValidEnumContexts = { + + // Generic setters and getters + + 'enable': { 0:true }, + 'disable': { 0:true }, + 'getParameter': { 0:true }, + + // Rendering + + 'drawArrays': { 0:true }, + 'drawElements': { 0:true, 2:true }, + + // Shaders + + 'createShader': { 0:true }, + 'getShaderParameter': { 1:true }, + 'getProgramParameter': { 1:true }, + + // Vertex attributes + + 'getVertexAttrib': { 1:true }, + 'vertexAttribPointer': { 2:true }, + + // Textures + + 'bindTexture': { 0:true }, + 'activeTexture': { 0:true }, + 'getTexParameter': { 0:true, 1:true }, + 'texParameterf': { 0:true, 1:true }, + 'texParameteri': { 0:true, 1:true, 2:true }, + 'texImage2D': { 0:true, 2:true, 6:true, 7:true }, + 'texSubImage2D': { 0:true, 6:true, 7:true }, + 'copyTexImage2D': { 0:true, 2:true }, + 'copyTexSubImage2D': { 0:true }, + 'generateMipmap': { 0:true }, + + // Buffer objects + + 'bindBuffer': { 0:true }, + 'bufferData': { 0:true, 2:true }, + 'bufferSubData': { 0:true }, + 'getBufferParameter': { 0:true, 1:true }, + + // Renderbuffers and framebuffers + + 'pixelStorei': { 0:true, 1:true }, + 'readPixels': { 4:true, 5:true }, + 'bindRenderbuffer': { 0:true }, + 'bindFramebuffer': { 0:true }, + 'checkFramebufferStatus': { 0:true }, + 'framebufferRenderbuffer': { 0:true, 1:true, 2:true }, + 'framebufferTexture2D': { 0:true, 1:true, 2:true }, + 'getFramebufferAttachmentParameter': { 0:true, 1:true, 2:true }, + 'getRenderbufferParameter': { 0:true, 1:true }, + 'renderbufferStorage': { 0:true, 1:true }, + + // Frame buffer operations (clear, blend, depth test, stencil) + + 'clear': { 0:true }, + 'depthFunc': { 0:true }, + 'blendFunc': { 0:true, 1:true }, + 'blendFuncSeparate': { 0:true, 1:true, 2:true, 3:true }, + 'blendEquation': { 0:true }, + 'blendEquationSeparate': { 0:true, 1:true }, + 'stencilFunc': { 0:true }, + 'stencilFuncSeparate': { 0:true, 1:true }, + 'stencilMaskSeparate': { 0:true }, + 'stencilOp': { 0:true, 1:true, 2:true }, + 'stencilOpSeparate': { 0:true, 1:true, 2:true, 3:true }, + + // Culling + + 'cullFace': { 0:true }, + 'frontFace': { 0:true }, +}; + +/** + * Map of numbers to names. + * @type {Object} + */ +var glEnums = null; + +/** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ +function init(ctx) { + if (glEnums == null) { + glEnums = { }; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'number') { + glEnums[ctx[propertyName]] = propertyName; + } + } + } +} + +/** + * Checks the utils have been initialized. + */ +function checkInit() { + if (glEnums == null) { + throw 'WebGLDebugUtils.init(ctx) not called'; + } +} + +/** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ +function mightBeEnum(value) { + checkInit(); + return (glEnums[value] !== undefined); +} + +/** + * Gets an string version of an WebGL enum. + * + * Example: + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ +function glEnumToString(value) { + checkInit(); + var name = glEnums[value]; + return (name !== undefined) ? name : + ("*UNKNOWN WebGL ENUM (0x" + value.toString(16) + ")"); +} + +/** + * Returns the string version of a WebGL argument. + * Attempts to convert enum arguments to strings. + * @param {string} functionName the name of the WebGL function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ +function glFunctionArgToString(functionName, argumentIndex, value) { + var funcInfo = glValidEnumContexts[functionName]; + if (funcInfo !== undefined) { + if (funcInfo[argumentIndex]) { + return glEnumToString(value); + } + } + if (value === null) { + return "null"; + } else if (value === undefined) { + return "undefined"; + } else { + return value.toString(); + } +} + +/** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ +function glFunctionArgsToString(functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + for (var ii = 0; ii < args.length; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, ii, args[ii]); + } + return argStr; +}; + + +function makePropertyWrapper(wrapper, original, propertyName) { + //log("wrap prop: " + propertyName); + wrapper.__defineGetter__(propertyName, function() { + return original[propertyName]; + }); + // TODO(gmane): this needs to handle properties that take more than + // one value? + wrapper.__defineSetter__(propertyName, function(value) { + //log("set: " + propertyName); + original[propertyName] = value; + }); +} + +// Makes a function that calls a function on another object. +function makeFunctionWrapper(original, functionName) { + //log("wrap fn: " + functionName); + var f = original[functionName]; + return function() { + //log("call: " + functionName); + var result = f.apply(original, arguments); + return result; + }; +} + +/** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not gl.NO_ERROR. + * + * @param {!WebGLRenderingContext} ctx The webgl context to + * wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc + * The function to call when gl.getError returns an + * error. If not specified the default function calls + * console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. + * You can use this to log all calls for example. + */ +function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc) { + init(ctx); + opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + for (var ii = 0; ii < args.length; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, ii, args[ii]); + } + log("WebGL error "+ glEnumToString(err) + " in "+ functionName + + "(" + argStr + ")"); + }; + + // Holds booleans for each GL error so after we get the error ourselves + // we can still return it to the client app. + var glErrorShadow = { }; + + // Makes a function that calls a WebGL function and then calls getError. + function makeErrorWrapper(ctx, functionName) { + return function() { + if (opt_onFunc) { + opt_onFunc(functionName, arguments); + } + var result = ctx[functionName].apply(ctx, arguments); + var err = ctx.getError(); + if (err != 0) { + glErrorShadow[err] = true; + opt_onErrorFunc(err, functionName, arguments); + } + return result; + }; + } + + // Make a an object that has a copy of every property of the WebGL context + // but wraps all functions. + var wrapper = {}; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); + } else { + makePropertyWrapper(wrapper, ctx, propertyName); + } + } + + // Override the getError function with one that returns our saved results. + wrapper.getError = function() { + for (var err in glErrorShadow) { + if (glErrorShadow.hasOwnProperty(err)) { + if (glErrorShadow[err]) { + glErrorShadow[err] = false; + return err; + } + } + } + return ctx.NO_ERROR; + }; + + return wrapper; +} + +function resetToInitialState(ctx) { + var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); + var tmp = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); + for (var ii = 0; ii < numAttribs; ++ii) { + ctx.disableVertexAttribArray(ii); + ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); + ctx.vertexAttrib1f(ii, 0); + } + ctx.deleteBuffer(tmp); + + var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); + for (var ii = 0; ii < numTextureUnits; ++ii) { + ctx.activeTexture(ctx.TEXTURE0 + ii); + ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); + ctx.bindTexture(ctx.TEXTURE_2D, null); + } + + ctx.activeTexture(ctx.TEXTURE0); + ctx.useProgram(null); + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); + ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); + ctx.disable(ctx.BLEND); + ctx.disable(ctx.CULL_FACE); + ctx.disable(ctx.DEPTH_TEST); + ctx.disable(ctx.DITHER); + ctx.disable(ctx.SCISSOR_TEST); + ctx.blendColor(0, 0, 0, 0); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ZERO); + ctx.clearColor(0, 0, 0, 0); + ctx.clearDepth(1); + ctx.clearStencil(-1); + ctx.colorMask(true, true, true, true); + ctx.cullFace(ctx.BACK); + ctx.depthFunc(ctx.LESS); + ctx.depthMask(true); + ctx.depthRange(0, 1); + ctx.frontFace(ctx.CCW); + ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); + ctx.lineWidth(1); + ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); + ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // TODO: Delete this IF. + if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { + ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); + } + ctx.polygonOffset(0, 0); + ctx.sampleCoverage(1, false); + ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); + ctx.stencilMask(0xFFFFFFFF); + ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); + ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); + + // TODO: This should NOT be needed but Firefox fails with 'hint' + while(ctx.getError()); +} + +function makeLostContextSimulatingCanvas(canvas) { + var unwrappedContext_; + var wrappedContext_; + var onLost_ = []; + var onRestored_ = []; + var wrappedContext_ = {}; + var contextId_ = 1; + var contextLost_ = false; + var resourceId_ = 0; + var resourceDb_ = []; + var numCallsToLoseContext_ = 0; + var numCalls_ = 0; + var canRestore_ = false; + var restoreTimeout_ = 0; + + // Holds booleans for each GL error so can simulate errors. + var glErrorShadow_ = { }; + + canvas.getContext = function(f) { + return function() { + var ctx = f.apply(canvas, arguments); + // Did we get a context and is it a WebGL context? + if (ctx instanceof WebGLRenderingContext) { + if (ctx != unwrappedContext_) { + if (unwrappedContext_) { + throw "got different context" + } + unwrappedContext_ = ctx; + wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); + } + return wrappedContext_; + } + return ctx; + } + }(canvas.getContext); + + function wrapEvent(listener) { + if (typeof(listener) == "function") { + return listener; + } else { + return function(info) { + listener.handleEvent(info); + } + } + } + + var addOnContextLostListener = function(listener) { + onLost_.push(wrapEvent(listener)); + }; + + var addOnContextRestoredListener = function(listener) { + onRestored_.push(wrapEvent(listener)); + }; + + + function wrapAddEventListener(canvas) { + var f = canvas.addEventListener; + canvas.addEventListener = function(type, listener, bubble) { + switch (type) { + case 'webglcontextlost': + addOnContextLostListener(listener); + break; + case 'webglcontextrestored': + addOnContextRestoredListener(listener); + break; + default: + f.apply(canvas, arguments); + } + }; + } + + wrapAddEventListener(canvas); + + canvas.loseContext = function() { + if (!contextLost_) { + contextLost_ = true; + numCallsToLoseContext_ = 0; + ++contextId_; + while (unwrappedContext_.getError()); + clearErrors(); + glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; + var event = makeWebGLContextEvent("context lost"); + var callbacks = onLost_.slice(); + setTimeout(function() { + //log("numCallbacks:" + callbacks.length); + for (var ii = 0; ii < callbacks.length; ++ii) { + //log("calling callback:" + ii); + callbacks[ii](event); + } + if (restoreTimeout_ >= 0) { + setTimeout(function() { + canvas.restoreContext(); + }, restoreTimeout_); + } + }, 0); + } + }; + + canvas.restoreContext = function() { + if (contextLost_) { + if (onRestored_.length) { + setTimeout(function() { + if (!canRestore_) { + throw "can not restore. webglcontestlost listener did not call event.preventDefault"; + } + freeResources(); + resetToInitialState(unwrappedContext_); + contextLost_ = false; + numCalls_ = 0; + canRestore_ = false; + var callbacks = onRestored_.slice(); + var event = makeWebGLContextEvent("context restored"); + for (var ii = 0; ii < callbacks.length; ++ii) { + callbacks[ii](event); + } + }, 0); + } + } + }; + + canvas.loseContextInNCalls = function(numCalls) { + if (contextLost_) { + throw "You can not ask a lost contet to be lost"; + } + numCallsToLoseContext_ = numCalls_ + numCalls; + }; + + canvas.getNumCalls = function() { + return numCalls_; + }; + + canvas.setRestoreTimeout = function(timeout) { + restoreTimeout_ = timeout; + }; + + function isWebGLObject(obj) { + //return false; + return (obj instanceof WebGLBuffer || + obj instanceof WebGLFramebuffer || + obj instanceof WebGLProgram || + obj instanceof WebGLRenderbuffer || + obj instanceof WebGLShader || + obj instanceof WebGLTexture); + } + + function checkResources(args) { + for (var ii = 0; ii < args.length; ++ii) { + var arg = args[ii]; + if (isWebGLObject(arg)) { + return arg.__webglDebugContextLostId__ == contextId_; + } + } + return true; + } + + function clearErrors() { + var k = Object.keys(glErrorShadow_); + for (var ii = 0; ii < k.length; ++ii) { + delete glErrorShadow_[k]; + } + } + + function loseContextIfTime() { + ++numCalls_; + if (!contextLost_) { + if (numCallsToLoseContext_ == numCalls_) { + canvas.loseContext(); + } + } + } + + // Makes a function that simulates WebGL when out of context. + function makeLostContextFunctionWrapper(ctx, functionName) { + var f = ctx[functionName]; + return function() { + // log("calling:" + functionName); + // Only call the functions if the context is not lost. + loseContextIfTime(); + if (!contextLost_) { + //if (!checkResources(arguments)) { + // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; + // return; + //} + var result = f.apply(ctx, arguments); + return result; + } + }; + } + + function freeResources() { + for (var ii = 0; ii < resourceDb_.length; ++ii) { + var resource = resourceDb_[ii]; + if (resource instanceof WebGLBuffer) { + unwrappedContext_.deleteBuffer(resource); + } else if (resource instanceof WebGLFramebuffer) { + unwrappedContext_.deleteFramebuffer(resource); + } else if (resource instanceof WebGLProgram) { + unwrappedContext_.deleteProgram(resource); + } else if (resource instanceof WebGLRenderbuffer) { + unwrappedContext_.deleteRenderbuffer(resource); + } else if (resource instanceof WebGLShader) { + unwrappedContext_.deleteShader(resource); + } else if (resource instanceof WebGLTexture) { + unwrappedContext_.deleteTexture(resource); + } + } + } + + function makeWebGLContextEvent(statusMessage) { + return { + statusMessage: statusMessage, + preventDefault: function() { + canRestore_ = true; + } + }; + } + + return canvas; + + function makeLostContextSimulatingContext(ctx) { + // copy all functions and properties to wrapper + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrappedContext_[propertyName] = makeLostContextFunctionWrapper( + ctx, propertyName); + } else { + makePropertyWrapper(wrappedContext_, ctx, propertyName); + } + } + + // Wrap a few functions specially. + wrappedContext_.getError = function() { + loseContextIfTime(); + if (!contextLost_) { + var err; + while (err = unwrappedContext_.getError()) { + glErrorShadow_[err] = true; + } + } + for (var err in glErrorShadow_) { + if (glErrorShadow_[err]) { + delete glErrorShadow_[err]; + return err; + } + } + return wrappedContext_.NO_ERROR; + }; + + var creationFunctions = [ + "createBuffer", + "createFramebuffer", + "createProgram", + "createRenderbuffer", + "createShader", + "createTexture" + ]; + for (var ii = 0; ii < creationFunctions.length; ++ii) { + var functionName = creationFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + var obj = f.apply(ctx, arguments); + obj.__webglDebugContextLostId__ = contextId_; + resourceDb_.push(obj); + return obj; + }; + }(ctx[functionName]); + } + + var functionsThatShouldReturnNull = [ + "getActiveAttrib", + "getActiveUniform", + "getBufferParameter", + "getContextAttributes", + "getAttachedShaders", + "getFramebufferAttachmentParameter", + "getParameter", + "getProgramParameter", + "getProgramInfoLog", + "getRenderbufferParameter", + "getShaderParameter", + "getShaderInfoLog", + "getShaderSource", + "getTexParameter", + "getUniform", + "getUniformLocation", + "getVertexAttrib" + ]; + for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { + var functionName = functionsThatShouldReturnNull[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + var isFunctions = [ + "isBuffer", + "isEnabled", + "isFramebuffer", + "isProgram", + "isRenderbuffer", + "isShader", + "isTexture" + ]; + for (var ii = 0; ii < isFunctions.length; ++ii) { + var functionName = isFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return false; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + wrappedContext_.checkFramebufferStatus = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.checkFramebufferStatus); + + wrappedContext_.getAttribLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getAttribLocation); + + wrappedContext_.getVertexAttribOffset = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return 0; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getVertexAttribOffset); + + wrappedContext_.isContextLost = function() { + return contextLost_; + }; + + return wrappedContext_; + } +} + +return { + /** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + } + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ + 'init': init, + + /** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ + 'mightBeEnum': mightBeEnum, + + /** + * Gets an string version of an WebGL enum. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + 'glEnumToString': glEnumToString, + + /** + * Converts the argument of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 0, gl.TEXTURE_2D); + * + * would return 'TEXTURE_2D' + * + * @param {string} functionName the name of the WebGL function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + 'glFunctionArgToString': glFunctionArgToString, + + /** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ + 'glFunctionArgsToString': glFunctionArgsToString, + + /** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not NO_ERROR. + * + * You can supply your own function if you want. For example, if you'd like + * an exception thrown on any GL error you could do this + * + * function throwOnGLError(err, funcName, args) { + * throw WebGLDebugUtils.glEnumToString(err) + + * " was caused by call to " + funcName; + * }; + * + * ctx = WebGLDebugUtils.makeDebugContext( + * canvas.getContext("webgl"), throwOnGLError); + * + * @param {!WebGLRenderingContext} ctx The webgl context to wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc The function + * to call when gl.getError returns an error. If not specified the default + * function calls console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. You + * can use this to log all calls for example. + */ + 'makeDebugContext': makeDebugContext, + + /** + * Given a canvas element returns a wrapped canvas element that will + * simulate lost context. The canvas returned adds the following functions. + * + * loseContext: + * simulates a lost context event. + * + * restoreContext: + * simulates the context being restored. + * + * lostContextInNCalls: + * loses the context after N gl calls. + * + * getNumCalls: + * tells you how many gl calls there have been so far. + * + * setRestoreTimeout: + * sets the number of milliseconds until the context is restored + * after it has been lost. Defaults to 0. Pass -1 to prevent + * automatic restoring. + * + * @param {!Canvas} canvas The canvas element to wrap. + */ + 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, + + /** + * Resets a context to the initial state. + * @param {!WebGLRenderingContext} ctx The webgl context to + * reset. + */ + 'resetToInitialState': resetToInitialState +}; + +}(); + diff --git a/lib/torque/leaflet/webgl.js b/lib/torque/leaflet/webgl.js new file mode 100644 index 0000000..2393abc --- /dev/null +++ b/lib/torque/leaflet/webgl.js @@ -0,0 +1,218 @@ + +function glGetError(gl) { + var ctx, error; + ctx = gl.getCurrentContext(); + error = ctx.errorValue; + ctx.errorValue = GL_NO_ERROR; + return error; +} + +// webgl utils +function shaderProgram(gl, vs, fs) { + var prog = gl.createProgram(); + var addshader = function(type, source) { + var s = gl.createShader((type == 'vertex') ? + gl.VERTEX_SHADER : gl.FRAGMENT_SHADER); + gl.shaderSource(s, source); + gl.compileShader(s); + if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { + throw "Could not compile "+type+ + " shader:\n\n"+gl.getShaderInfoLog(s); + } + gl.attachShader(prog, s); + }; + addshader('vertex', vs); + addshader('fragment', fs); + gl.linkProgram(prog); + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + throw "Could not link the shader program!"; + } + return prog; +} + +function createVertexBuffer(gl, rsize, arr) { + var buff = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buff); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(arr), gl.STATIC_DRAW); + buff.vSize = rsize; + return buff; +} + +function setBufferData(gl, prog, attr_name, buff) { + gl.bindBuffer(gl.ARRAY_BUFFER, buff); + var attr = gl.getAttribLocation(prog, attr_name); + gl.enableVertexAttribArray(attr); + gl.vertexAttribPointer(attr, buff.vSize, gl.FLOAT, false, 0, 0); +} + +function initFB() { + var fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + fb.width = 512; + fb.height = 512; +} + +function WebGLRenderer(el) { + this.gl = el.getContext('webgl'); + this._init(); + this.width = el.width; + this.height = el.height; + this.image_cache = {} +} + + +/* + * #mn_mappluto_13v1{ + line-color: #FFF; + line-width: 1; + line-opacity: 1; + [numfloors<=104]{building-height:52;}[numfloors<=100]{building-height:50;}[numfloors<=96]{building-height:48;}[numfloors<=92]{building-height:46;}[numfloors<=88]{building-height:44;}[numfloors<=84]{building-height:42;}[numfloors<=80]{building-height:40;}[numfloors<=76]{building-height:38;}[numfloors<=72]{building-height:36;}[numfloors<=68]{building-height:34;}[numfloors<=64]{building-height:32;}[numfloors<=60]{building-height:30;}[numfloors<=56]{building-height:28;}[numfloors<=52]{building-height:26;}[numfloors<=48]{building-height:24;}[numfloors<=44]{building-height:22;}[numfloors<=40]{building-height:20;}[numfloors<=36]{building-height:18;}[numfloors<=32]{building-height:16;}[numfloors<=28]{building-height:14;}[numfloors<=24]{building-height:12;}[numfloors<=20]{building-height:10;}[numfloors<=16]{building-height:8;}[numfloors<=12]{building-height:0;} +} +#mn_mappluto_13v1 [ spiderman <= 576] { + building-fill: #B10026; +} +#mn_mappluto_13v1 [ spiderman <= 168] { + building-fill: #E31A1C; +} +#mn_mappluto_13v1 [ spiderman <= 98] { + building-fill: #FC4E2A; +} +#mn_mappluto_13v1 [ spiderman <= 73] { + building-fill: #FD8D3C; +} +#mn_mappluto_13v1 [ spiderman <= 45] { + building-fill: #FEB24C; +} +#mn_mappluto_13v1 [ spiderman <= 29] { + building-fill: #FED976; +} +#mn_mappluto_13v1 [ spiderman <= 14] { + building-fill: #FFFFB2; +} +*/ +//256/EARTH_RADIUS * 2 * Math.PI +//6378137 + +WebGLRenderer.prototype._init = function() { + var gl = this.gl; + var prog = shaderProgram(gl, + "precision highp float;\n"+ + //"#define M_PI 3.1415926535897932384626433832795\n" + + //"uniform float zoom;" + + "uniform vec2 tilePos;" + + "uniform vec2 mapSize;" + + "uniform float pSize;" + + "attribute vec2 pos;"+ + "void main() {"+ + //" float er = 6378137.0;" + + //" float s = (256.0*pow(2.0, zoom))/(er*M_PI*2.0);"+ + " gl_PointSize = pSize;" + + " vec2 p = vec2(pos.x, -pos.y);" + + " gl_Position = vec4((2.0*(p + tilePos)/mapSize), 0.0, 1.0);"+ + //" gl_Position = vec4((2.0*pos/mapSize) - vec2(1.0, 1.0), 0.0, 1.0);"+ + //" gl_Position = vec4(0.0, 0.0, 0.0, 1.0);"+ + "}", + "precision highp float;"+ + "void main() {"+ + "float d = 1.0 - pow(length(2.0*vec2(gl_PointCoord.s - 0.5, gl_PointCoord.t - 0.5)), 5.0);" + + "gl_FragColor = vec4(d, 0.0, 0.0, d);" + + "}" + ); + this.program = prog; + gl.useProgram(prog); + /*this.vertexBuffer = createVertexBuffer(gl, 2, [ + -1, -1, + -1, 1, + 1, -1, + 1, 1 + ]); + setBufferData(gl, prog, "pos", this.vertexBuffer); + */ + var err = gl.errorValue; + if(err !== 0) { + console.log(err); + } +}; + +function uploadTexture(gl, img) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,img); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + gl.bindTexture(gl.TEXTURE_2D, null); + return texture; +} + +WebGLRenderer.prototype.activeProgram = function() { + return this.program; +} + +WebGLRenderer.prototype.createVertexBuffer = function(data) { + return createVertexBuffer(this.gl, 2, data); +} + +/* +WebGLRenderer.prototype.loadImageTile = function(tile) { + var self = this; + //var layer = 'http://b.tiles.mapbox.com/v3/mapbox.mapbox-light/{{z}}/{{x}}/{{y}}.png64'; + layer = 'http://tile.stamen.com/toner/{{z}}/{{x}}/{{y}}.png'; + var url = layer.replace('{{z}}', tile.zoom).replace('{{x}}', tile.i).replace('{{y}}', tile.j); + var k = tile.zoom + '-' + tile.i + '-' + tile.j; + var i = this.image_cache[k]; + if(i === undefined) { + self.image_cache[k] = null; + var img = new Image(); + img.crossOrigin = "*"; + img.onload = function() { + self.image_cache[k] = uploadTexture(self.gl, img); + console.log(k + " loaded"); + //requestAnimationFrame(self.render); + }; + img.src = url; + } +} + +WebGLRenderer.prototype.renderTiles = function(tiles, center, zoom) { + var gl = this.gl; + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT); + + gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); + + var mapSize = gl.getUniformLocation(this.program, "mapSize"); + gl.uniform2fv(mapSize, [this.width, this.height]); + var zoom = gl.getUniformLocation(this.program, "zoom"); + gl.uniform1f(zoom, zoom); + var mapPos = gl.getUniformLocation(this.program, "mapPos"); + gl.uniform2fv(mapPos, [center.x, center.y]); + var tileImage = gl.getUniformLocation(this.program, "tileImage"); + + + for(var i = 0; i < tiles.length; ++i) { + var tile = tiles[i]; + this.loadImageTile(tile); + var k = tile.zoom + '-' + tile.i + '-' + tile.j; + var img = this.image_cache[k]; + if(img) { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, img); + gl.uniform1i(tileImage, 0); + var tilePos = gl.getUniformLocation(this.program, "tilePos"); + gl.uniform2fv(tilePos, [tile.x, tile.y]); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + } + } + + + var err = gl.errorValue; + if(err !== 0) { + console.log(err); + } +}; +*/ diff --git a/lib/torque/provider.json.js b/lib/torque/provider.json.js index 9eb72df..b3ec252 100644 --- a/lib/torque/provider.json.js +++ b/lib/torque/provider.json.js @@ -44,6 +44,7 @@ json.prototype = { + /** * return the torque tile encoded in an efficient javascript * structure: diff --git a/lib/torque/provider.windshaft.js b/lib/torque/provider.windshaft.js index ab79f2a..28cbeb3 100644 --- a/lib/torque/provider.windshaft.js +++ b/lib/torque/provider.windshaft.js @@ -40,6 +40,41 @@ }; json.prototype = { + aggregateByKey: function(callback) { + var url = this.templateUrl + .replace('{x}', 0) + .replace('{y}', 0) + .replace('{z}', 0) + .replace('{s}', 0) + + var self = this; + var extra = this._extraParams(); + torque.net.get( url, function (data) { + var rows = JSON.parse(data.responseText); + callback(self._aggregateByKey(rows)); + }) + }, + _aggregateByKey: function(rows) { + function getKeys(row) { + var keys = {}; + var dates = row.dates__uint16; + var vals = row.vals__uint8; + var valuesCount = vals.length; + for (var s = 0; s < valuesCount; ++s) { + keys[dates[s]] = vals[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; + }, /** * return the torque tile encoded in an efficient javascript