diff --git a/lib/torque/renderer/point.js b/lib/torque/renderer/point.js index d68b6b4..9e6007d 100644 --- a/lib/torque/renderer/point.js +++ b/lib/torque/renderer/point.js @@ -3,9 +3,6 @@ var cartocss = require('./cartocss_render'); var Profiler = require('../profiler'); var carto = global.carto || require('carto'); var Filters = require('./torque_filters'); -var d3 = require('d3'); -var contour = require('./contour'); -var cSpline = require('cardinal-spline'); var TAU = Math.PI * 2; var DEFAULT_CARTOCSS = [ @@ -62,7 +59,6 @@ var cSpline = require('cardinal-spline'); this._gradients = {}; this._forcePoints = false; - this.globalGrid = []; } torque.extend(PointRenderer.prototype, torque.Event, { @@ -109,10 +105,74 @@ var cSpline = require('cardinal-spline'); this._sprites = []; }, + + // + // generate sprite based on cartocss style + // + generateSprite: function(shader, value, shaderVars) { + var self = this; + var prof = Profiler.metric('torque.renderer.point.generateSprite').start(); + var st = shader.getStyle({ + value: value + }, shaderVars); + if(this._style === null || this._style !== st){ + this._style = st; + } + + var pointSize = st['marker-width']; + if (!pointSize) { + return null; + } + + if (st['marker-opacity'] === 0 && !st['marker-line-opacity']) { + return null; + } + + var canvas = this._createCanvas(); + var ctx = canvas.getContext('2d'); + + var markerFile = st["marker-file"] || st["point-file"]; + var qualifiedUrl = markerFile && this._qualifyURL(markerFile); + + if (qualifiedUrl && this._iconsToLoad <= 0 && this._icons[qualifiedUrl]) { + var img = this._icons[qualifiedUrl]; + + var dWidth = Math.min(st['marker-width'] * 2 || img.width, cartocss.MAX_SPRITE_RADIUS * 2); + var dHeight = Math.min((st['marker-height'] || dWidth) * (img.width / img.height), cartocss.MAX_SPRITE_RADIUS * 2); + + canvas.width = ctx.width = dWidth; + canvas.height = ctx.height = dHeight; + + ctx.scale(dWidth/img.width, dHeight/img.height); + + cartocss.renderSprite(ctx, img, st); + } else { + // take into account the exterior ring to calculate the size + var canvasSize = (st['marker-line-width'] || 0) + pointSize*2; + var w = ctx.width = canvas.width = ctx.height = canvas.height = Math.ceil(canvasSize); + ctx.translate(w/2, w/2); + + var mt = st['marker-type']; + if (mt && mt === 'rectangle') { + cartocss.renderRectangle(ctx, st); + } else { + cartocss.renderPoint(ctx, st); + } + } + prof.end(true); + if (torque.flags.sprites_to_images) { + var i = this._createImage(); + i.src = canvas.toDataURL(); + return i; + } + + return canvas; + }, + // // renders all the layers (and frames for each layer) from cartocss // - renderTile: function(tile, key, pos) { + renderTile: function(tile, key, callback) { if (this._iconsToLoad > 0) { this.on('allIconsLoaded', function() { this.renderTile.apply(this, [tile, key, callback]); @@ -129,12 +189,14 @@ var cSpline = require('cardinal-spline'); for(var fr = 0; fr < layer.frames().length; ++fr) { var frame = layer.frames()[fr]; var fr_sprites = sprites[frame] || (sprites[frame] = []); - this._renderTile(tile, key - frame, frame, fr_sprites, layer, pos); + this._renderTile(tile, key - frame, frame, fr_sprites, layer); } } } prof.end(true); + + return callback && callback(null); }, _createCanvas: function() { @@ -178,7 +240,7 @@ var cSpline = require('cardinal-spline'); // renders a tile in the canvas for key defined in // the torque tile // - _renderTile: function(tile, key, frame_offset, sprites, shader, pos) { + _renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) { if (!this._canvas) return; var prof = Profiler.metric('torque.renderer.point.renderTile').start(); @@ -194,61 +256,27 @@ var cSpline = require('cardinal-spline'); var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1) var activePixels = tile.timeCount[key]; var anchor = this.options.resolution/2; - - /* - Torque Isolines - To be moved somewhere else (ideally something link isolines.js within /renderer/) - */ - var isolines = true; - if (isolines) { - this._gridData(tile); - } - prof.end(true); - }, - - _gridData: function(tile){ - var valsPerTile = this.TILE_SIZE/this.options.resolution; - var difX = tile.coord.x - this.firstTileCoords.coord.x; - var difY = tile.coord.y - this.firstTileCoords.coord.y; - if (difX < 0 || difY < 0) return; - // baseIndex is the distance to the upper left corner of the grid, in cells - var baseIndex = { - x: (difX) * valsPerTile, - y: (difY) * valsPerTile - } - - for(var i = 0; i < tile.renderData.length; i++){ - var x = tile.x[i], y = tile.y[i]; - this.globalGrid[baseIndex.y + (256 - y) / this.options.resolution -1][baseIndex.x + x/this.options.resolution] = tile.renderData[i]; - } - }, - - _getPipe: function(cell, contour){ - var parsedCell = cell.map(function(cornerValue){ - if (cornerValue >= contour){ - return "1"; + if (activePixels) { + var pixelIndex = tile.timeIndex[key]; + for(var p = 0; p < activePixels; ++p) { + var posIdx = tile.renderDataPos[pixelIndex + p]; + var c = tile.renderData[pixelIndex + p]; + if (c) { + var sp = sprites[c]; + if (sp === undefined) { + sp = sprites[c] = this.generateSprite(shader, c, torque.extend({ zoom: tile.z, 'frame-offset': frame_offset }, shaderVars)); + } + if (sp) { + var x = tile.x[posIdx]- (sp.width >> 1) + anchor; + var y = tileMax - tile.y[posIdx] + anchor; // flip mercator + ctx.drawImage(sp, x, y - (sp.height >> 1)); + } } - return "0"; - }).join(""); - var type = parseInt(parsedCell, 2); - var interpolated = true; - var N = interpolated? [this._lerp(cell[1], cell[0], contour), 0]: [0.5,0], - S = interpolated? [this._lerp(cell[2], cell[3], contour), 1]: [0.5,1], - E = interpolated? [1, this._lerp(cell[2], cell[1], contour)]: [1,0.5], - W = interpolated? [0, this._lerp(cell[3], cell[0], contour)]: [0,0.5] - if (type === 0 || type === 15) return null; - if (type === 1 || type === 14) return [W, S] - if (type === 2 || type === 13) return [S, E] - if (type === 3 || type === 12) return [W, E] - if (type === 4 || type === 11) return [N, E] - if (type === 6 || type === 9) return [N, S] - if (type === 7 || type === 8) return [W, N] - if (type === 5) return [W, N, S, E] - if (type === 10) return [W, S, N, E] - }, + } + } + - _lerp: function(valueA, valueB, contourValue){ - return 1 + (-0.5) * (contourValue - valueA) / (valueB - valueA); + prof.end(true); }, setBlendMode: function(b) { @@ -374,85 +402,44 @@ var cSpline = require('cardinal-spline'); } }, - drawIsolines: function(){ - if (this.globalGrid.length > 0) { - var style = this._shader.getLayers()[1].getStyle({}, {zoom: 2}); - var cellsY = this.globalGrid.length-1; - var contourValues = [0, 4, 8, 16, 32, 64, 128]; - var ctx = this._ctx; - var res = this.options.resolution; - var startPos = this.firstTileCoords.pos; - ctx.strokeStyle = style["-isoline-line-color"] || "black"; - ctx.lineWidth = style["-isoline-line-width"] || 2; - ctx.globalAlpha = style["-isoline-line-opacity"] || 0.8; - var grad = style["-isoline-line-ramp"] && this._generateGradient(style["-isoline-line-ramp"].args); - for (var c = 0; c < contourValues.length; c++) { - if(style["-isoline-line-decay"]){ - var max = contourValues[contourValues.length - 1]; - ctx.globalAlpha = (contourValues[c]/max)*(style["-isoline-line-opacity"] || 0.8); - } - if(grad){ - ctx.strokeStyle = "rgb("+grad[4*contourValues[c]+1]+","+grad[4*contourValues[c]+2]+","+grad[4*contourValues[c]+3]+")"; - } - for (var y = 0; y < cellsY; y++) { - for (var x = 0; x < this.globalGrid[y].length-1; x++){ - var currentCell = [ - this.globalGrid[y][x], - this.globalGrid[y][x+1], - this.globalGrid[y+1][x+1], - this.globalGrid[y+1][x] - ]; - var pipe = this._getPipe(currentCell, contourValues[c]); - if (pipe){ - ctx.beginPath(); - ctx.moveTo(res * (x + 0.5 + pipe[0][0]), res * (y + 0.5 + pipe[0][1])); - ctx.lineTo(res * (x + 0.5 + pipe[1][0]), res * (y + 0.5 + pipe[1][1])); - ctx.stroke(); - if (pipe.length === 4){ - ctx.beginPath(); - ctx.moveTo(res * (x + 0.5 + pipe[2][0]), res * (y + 0.5 + pipe[2][1])); - ctx.lineTo(res * (x + 0.5 + pipe[3][0]), res * (y + 0.5 + pipe[3][1])); - ctx.stroke(); - } - } - - } - } - } - ctx.globalAlpha = 0; - } - }, - - _generateGradient: function(ramp){ - var gradientCanvas = this._createCanvas(), - gctx = gradientCanvas.getContext('2d'), - gradient = gctx.createLinearGradient(0, 0, 0, 256); - gradientCanvas.width = 1; - gradientCanvas.height = 256; - - for (var i = 0; i < ramp.length; i++) { - var color = "rgb("+ramp[i].rgb[0]+","+ramp[i].rgb[1]+","+ramp[i].rgb[2]+")"; - gradient.addColorStop(i/(ramp.length-1), color); - } - gctx.fillStyle = gradient; - gctx.fillRect(0, 0, 1, 256); - - return gctx.getImageData(0, 0, 1, 256).data; - }, - applyFilters: function(){ - this.drawIsolines(); - // for (var y = 0; y < this.globalGrid.length; y++){ - // for (var x = 0; x < this.globalGrid[0].length; x++){ - // ctx.beginPath(); - // ctx.arc(res/2 + x*res, res/2 + y*res, 1, 0, 2 * Math.PI, false); - // var value = this.globalGrid[y][x]; - // ctx.fillStyle = "rgb("+0+", "+value * 50+", "+value * 50+")"; - // if(value === 0) ctx.fillStyle = "red"; - // ctx.fill(); - // } - // } - } + if(this._style){ + if(this._style['image-filters']){ + function gradientKey(imf){ + var hash = "" + for(var i = 0; i < imf.args.length; i++){ + var rgb = imf.args[i].rgb; + hash += rgb[0] + ":" + rgb[1] + ":" + rgb[2]; + } + return hash; + } + var gradient = this._gradients[gradientKey(this._style['image-filters'])]; + if(!gradient){ + function componentToHex(c) { + var hex = c.toString(16); + return hex.length == 1 ? "0" + hex : hex; + } + + function rgbToHex(r, g, b) { + return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); + } + gradient = {}; + var colorize = this._style['image-filters'].args; + + var increment = 1/colorize.length; + for (var i = 0; i < colorize.length; i++){ + var key = increment * i + increment; + var rgb = colorize[i].rgb; + var formattedColor = rgbToHex(rgb[0], rgb[1], rgb[2]); + gradient[key] = formattedColor; + } + this._gradients[gradientKey(this._style['image-filters'])] = gradient; + } + this._filters.gradient(gradient); + this._filters.draw(); + } + } + } });