Compare commits
53 Commits
master
...
bi_provide
Author | SHA1 | Date | |
---|---|---|---|
|
2bc79e183f | ||
|
75bdbbfb80 | ||
|
419403998d | ||
|
029f08b2f9 | ||
|
f1a07e7cd9 | ||
|
1eb1344916 | ||
|
ce6ba46893 | ||
|
4421294a4e | ||
|
19e0145fee | ||
|
754ab64a9a | ||
|
a393186f9f | ||
|
c660bc8fe7 | ||
|
7c2d0b353a | ||
|
f82283ff54 | ||
|
59e533af10 | ||
|
d95f044ffc | ||
|
b210ba8b22 | ||
|
f399acb8cc | ||
|
c500a9c025 | ||
|
5299314fb4 | ||
|
3e35097496 | ||
|
880bf9870f | ||
|
c9e87f4365 | ||
|
21e28bf66b | ||
|
ea2fe75fbc | ||
|
e93c6169bd | ||
|
33c79dad0c | ||
|
b46489d8eb | ||
|
c4fa9b3351 | ||
|
a822c369c7 | ||
|
a61976727c | ||
|
1058d7ee8f | ||
|
b7f38d5996 | ||
|
55847bda56 | ||
|
164643d0f1 | ||
|
e65367e8a6 | ||
|
1fa44a8ae1 | ||
|
87b93e6f9e | ||
|
dbf48ea091 | ||
|
6dd0251be8 | ||
|
19e35ffe2c | ||
|
7a1f206d5e | ||
|
1ec2324b83 | ||
|
e0606ee295 | ||
|
695cab290a | ||
|
ff4809b08c | ||
|
4ad0dba547 | ||
|
37f4367da2 | ||
|
53d5072c4b | ||
|
71fc89a8d8 | ||
|
fd5bc0f732 | ||
|
130d72c872 | ||
|
c27b2471da |
@ -17,6 +17,31 @@ L.Mixin.TileLoader = {
|
||||
this._removeTiles();
|
||||
},
|
||||
|
||||
visibleTiles: function() {
|
||||
if (!this._map) { return []; }
|
||||
var j, i, point, tiles = [];
|
||||
var bounds = this._map.getPixelBounds(),
|
||||
zoom = this._map.getZoom(),
|
||||
tileSize = this.options.tileSize;
|
||||
|
||||
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);
|
||||
|
||||
for (j = tileBounds.min.y; j <= tileBounds.max.y; j++) {
|
||||
for (i = tileBounds.min.x; i <= tileBounds.max.x; i++) {
|
||||
tiles.push({ x: i, y: j, z: zoom });
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
},
|
||||
|
||||
_updateTiles: function () {
|
||||
|
||||
if (!this._map) { return; }
|
||||
@ -55,11 +80,12 @@ L.Mixin.TileLoader = {
|
||||
},
|
||||
|
||||
_removeOtherTiles: function (bounds) {
|
||||
var self = this;
|
||||
var kArr, x, y, z, key;
|
||||
var zoom = this._map.getZoom();
|
||||
|
||||
for (key in this._tiles) {
|
||||
if (this._tiles.hasOwnProperty(key)) {
|
||||
function checkTile(c, key) {
|
||||
if (c.hasOwnProperty(key)) {
|
||||
kArr = key.split(':');
|
||||
x = parseInt(kArr[0], 10);
|
||||
y = parseInt(kArr[1], 10);
|
||||
@ -67,22 +93,41 @@ L.Mixin.TileLoader = {
|
||||
|
||||
// remove tile if it's out of bounds
|
||||
if (zoom !== z || x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
|
||||
this._removeTile(key);
|
||||
self._removeTile(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (key in this._tiles) {
|
||||
checkTile(this._tiles, key);
|
||||
}
|
||||
for (key in this._tilesLoading) {
|
||||
checkTile(this._tilesLoading, key);
|
||||
}
|
||||
},
|
||||
|
||||
_removeTile: function (key) {
|
||||
this.fire('tileRemoved', this._tiles[key]);
|
||||
delete this._tiles[key];
|
||||
if (this._tilesLoading[key]) {
|
||||
--this._tilesToLoad;
|
||||
delete this._tilesLoading[key];
|
||||
}
|
||||
},
|
||||
|
||||
_tileKey: function(tilePoint) {
|
||||
return tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom;
|
||||
},
|
||||
|
||||
_tileFromKey: function(key) {
|
||||
var kArr = key.split(':');
|
||||
return {
|
||||
x: parseInt(kArr[0], 10),
|
||||
y: parseInt(kArr[1], 10),
|
||||
z: parseInt(kArr[2], 10)
|
||||
}
|
||||
},
|
||||
|
||||
_tileShouldBeLoaded: function (tilePoint) {
|
||||
var k = this._tileKey(tilePoint);
|
||||
return !(k in this._tiles) && !(k in this._tilesLoading);
|
||||
|
@ -72,8 +72,8 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
|
||||
if (this.options.tileJSON) this.options.provider = 'tileJSON';
|
||||
|
||||
this.provider = new this.providers[this.options.provider](options);
|
||||
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), options);
|
||||
this.provider = new this.providers[this.options.provider](this.options);
|
||||
this.renderer = new this.renderers[this.options.renderer](this.getCanvas(), this.options);
|
||||
|
||||
options.ready = function() {
|
||||
self.fire("change:bounds", {
|
||||
@ -243,6 +243,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer.applyFilters();
|
||||
|
||||
// prepare caches if the animation is not running
|
||||
@ -341,6 +342,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
setCartoCSS: function(cartocss) {
|
||||
if (this.provider.options.named_map) throw new Error("CartoCSS style on named maps is read-only");
|
||||
if (!this.renderer) throw new Error('renderer is not valid');
|
||||
this.options.cartocss = cartocss;
|
||||
var shader = new carto.RendererJS().render(cartocss);
|
||||
this.renderer.setShader(shader);
|
||||
|
||||
@ -398,6 +400,7 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
var t, tile, pos, value = null, xx, yy;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
if (tile) {
|
||||
pos = this.getTilePos(tile.coord);
|
||||
xx = x - pos.x;
|
||||
yy = y - pos.y;
|
||||
@ -408,16 +411,66 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* return tile pos given screen x,y coordinates
|
||||
*/
|
||||
getTilePosForPixel: function(x, y) {
|
||||
var t, tile, pos, xx, yy;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
if (tile) {
|
||||
pos = this.getTilePos(tile.coord);
|
||||
xx = x - pos.x;
|
||||
yy = y - pos.y;
|
||||
if (xx >= 0 && yy >= 0 && xx < this.renderer.TILE_SIZE && yy <= this.renderer.TILE_SIZE) {
|
||||
return {
|
||||
x: xx,
|
||||
y: yy,
|
||||
tile: tile.coord
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* return a list of the closest values for a given coord.
|
||||
* returned format is [{ x: .., y: .., value: ...}, .. ]
|
||||
*/
|
||||
getClosestValuesFor: function(x, y, dist, step) {
|
||||
var xf = x + dist,
|
||||
yf = y + dist,
|
||||
_x = x;
|
||||
var values = []
|
||||
for(_y = y; _y < yf; _y += this.options.resolution){
|
||||
for(_x = x; _x < xf; _x += this.options.resolution){
|
||||
var thisValue = this.getValueForPos(_x,_y);
|
||||
if (thisValue !== null) {
|
||||
var bb = thisValue.bbox;
|
||||
var xy = this._map.latLngToContainerPoint([bb[0].lat, bb[0].lon]);
|
||||
values.push({
|
||||
x: xy.x,
|
||||
y: xy.y,
|
||||
value: thisValue.value
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
},
|
||||
|
||||
getValueForBBox: function(x, y, w, h) {
|
||||
var xf = x + w, yf = y + h, _x=x;
|
||||
var sum = 0;
|
||||
for(_y = y; _y<yf; _y+=this.options.resolution){
|
||||
for(_x = x; _x<xf; _x+=this.options.resolution){
|
||||
for(_y = y; _y < yf; _y += this.options.resolution){
|
||||
for(_x = x; _x < xf; _x += this.options.resolution){
|
||||
var thisValue = this.getValueForPos(_x,_y);
|
||||
if (thisValue){
|
||||
if (thisValue) {
|
||||
var bb = thisValue.bbox;
|
||||
var xy = this._map.latLngToContainerPoint([bb[1].lat, bb[1].lon]);
|
||||
if(xy.x < xf && xy.y < yf){
|
||||
@ -429,6 +482,20 @@ L.TorqueLayer = L.CanvasLayer.extend({
|
||||
return sum;
|
||||
},
|
||||
|
||||
/** return the number of points for a step */
|
||||
pointCount: function(step) {
|
||||
var t, tile;
|
||||
step = step === undefined ? this.key: step;
|
||||
var c = 0;
|
||||
for(t in this._tiles) {
|
||||
tile = this._tiles[t];
|
||||
if (tile) {
|
||||
c += tile.timeCount[step];
|
||||
}
|
||||
}
|
||||
return c;
|
||||
},
|
||||
|
||||
invalidate: function() {
|
||||
this.provider.reload();
|
||||
},
|
||||
|
@ -31,13 +31,15 @@ var Filters = require('./torque_filters');
|
||||
"dst-atop": 'destination-atop',
|
||||
"xor": 'xor',
|
||||
"darken": 'darken',
|
||||
"lighten": 'lighten'
|
||||
"lighten": 'lighten',
|
||||
"screen": "screen"
|
||||
}
|
||||
|
||||
function compop2canvas(compop) {
|
||||
return COMP_OP_TO_CANVAS[compop] || compop;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// this renderer just render points depending of the value
|
||||
//
|
||||
@ -110,11 +112,19 @@ var Filters = require('./torque_filters');
|
||||
// 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);
|
||||
|
||||
var ret = this.generateSpriteForStyle(st);
|
||||
prof.end(true);
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateSpriteForStyle: function(st) {
|
||||
var self = this;
|
||||
|
||||
if(this._style === null || this._style !== st){
|
||||
this._style = st;
|
||||
}
|
||||
@ -148,9 +158,14 @@ var Filters = require('./torque_filters');
|
||||
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 canvasSize = (st['marker-line-width'] || 0) + pointSize*2 + 2;
|
||||
canvasSize = Math.ceil(canvasSize);
|
||||
// the sprite should be placed in the center of a pixel not in the middle so
|
||||
// make the canvas size odd
|
||||
canvasSize += canvasSize % 2 === 0 ? 1 : 0;
|
||||
var w = ctx.width = canvas.width = ctx.height = canvas.height = canvasSize;
|
||||
w = Math.floor(w/2) + 1;
|
||||
ctx.translate(w, w);
|
||||
|
||||
var mt = st['marker-type'];
|
||||
if (mt && mt === 'rectangle') {
|
||||
@ -159,7 +174,6 @@ var Filters = require('./torque_filters');
|
||||
cartocss.renderPoint(ctx, st);
|
||||
}
|
||||
}
|
||||
prof.end(true);
|
||||
if (torque.flags.sprites_to_images) {
|
||||
var i = this._createImage();
|
||||
i.src = canvas.toDataURL();
|
||||
@ -242,7 +256,6 @@ var Filters = require('./torque_filters');
|
||||
//
|
||||
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
|
||||
if (!this._canvas) return;
|
||||
|
||||
var prof = Profiler.metric('torque.renderer.point.renderTile').start();
|
||||
var ctx = this._ctx;
|
||||
var blendMode = compop2canvas(shader.eval('comp-op')) || this.options.blendmode;
|
||||
@ -261,7 +274,6 @@ var Filters = require('./torque_filters');
|
||||
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));
|
||||
@ -273,7 +285,6 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
prof.end(true);
|
||||
@ -459,6 +470,8 @@ var Filters = require('./torque_filters');
|
||||
}
|
||||
});
|
||||
|
||||
PointRenderer.COMP_OP_TO_CANVAS = COMP_OP_TO_CANVAS;
|
||||
|
||||
|
||||
// exports public api
|
||||
module.exports = PointRenderer;
|
||||
|
@ -1,160 +1,65 @@
|
||||
var torque = require('../');
|
||||
var cartocss = require('./cartocss_render');
|
||||
var Profiler = require('../profiler');
|
||||
var carto = global.carto || require('carto');
|
||||
var Filters = require('./torque_filters');
|
||||
var PointRenderer = require('./point')
|
||||
|
||||
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 PixelRenderer = function(canvas, options) {
|
||||
PointRenderer.call(this, canvas, options);
|
||||
}
|
||||
|
||||
var TAU = Math.PI * 2;
|
||||
torque.extend(PixelRenderer.prototype, PointRenderer.prototype, {
|
||||
|
||||
//
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
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");
|
||||
return {
|
||||
width: st['marker-width'],
|
||||
color: st['marker-fill'],
|
||||
fill_opacity: st['marker-fill-opacity'] === undefined ? 1.0: st['marker-fill-opacity']
|
||||
}
|
||||
this._shader = this._cartoCssStyle.getLayers()[0].shader;
|
||||
},
|
||||
|
||||
setCanvas: function(canvas) {
|
||||
if(!canvas) return;
|
||||
this._canvas = canvas;
|
||||
this._ctx = canvas.getContext('2d');
|
||||
},
|
||||
|
||||
accumulate: function(tile, keys) {
|
||||
var prof = Profiler.metric('RectangleRender:accumulate').start();
|
||||
var x, y, posIdx, p, k, key, activePixels, pixelIndex;
|
||||
var res = this.options.resolution;
|
||||
var s = 256/res;
|
||||
var accum = new Float32Array(s*s);
|
||||
|
||||
if(typeof(keys) !== 'object') {
|
||||
keys = [keys];
|
||||
}
|
||||
|
||||
for(k = 0; k < keys.length; ++k) {
|
||||
key = keys[k];
|
||||
activePixels = tile.timeCount[key];
|
||||
if(activePixels) {
|
||||
pixelIndex = tile.timeIndex[key];
|
||||
for(p = 0; p < activePixels; ++p) {
|
||||
posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
x = tile.x[posIdx]/res;
|
||||
y = tile.y[posIdx]/res;
|
||||
accum[x*s + y] += tile.renderData[pixelIndex + p];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prof.end();
|
||||
return accum;
|
||||
},
|
||||
|
||||
renderTileAccum: function(accum, px, py) {
|
||||
var prof = Profiler.metric('RectangleRender:renderTileAccum').start();
|
||||
var color, x, y, alpha;
|
||||
var res = this.options.resolution;
|
||||
_renderTile: function(tile, key, frame_offset, sprites, shader, shaderVars) {
|
||||
if (!this._canvas) return;
|
||||
var prof = Profiler.metric('torque.renderer.point.renderTile').start();
|
||||
var ctx = this._ctx;
|
||||
var s = (256/res) | 0;
|
||||
var s2 = s*s;
|
||||
var colors = this._colors;
|
||||
if(this.options.blendmode) {
|
||||
ctx.globalCompositeOperation = this.options.blendmode;
|
||||
if (this.options.cumulative && key > tile.maxDate) {
|
||||
//TODO: precache because this tile is not going to change
|
||||
key = tile.maxDate;
|
||||
}
|
||||
var polygon_alpha = this._shader['polygon-opacity'] || function() { return 1.0; };
|
||||
for(var i = 0; i < s2; ++i) {
|
||||
var xy = i;
|
||||
var value = accum[i];
|
||||
if(value) {
|
||||
x = (xy/s) | 0;
|
||||
y = xy % s;
|
||||
// by-pass the style generation for improving performance
|
||||
color = this._shader['polygon-fill']({ value: value }, { zoom: 0 });
|
||||
ctx.fillStyle = color;
|
||||
//TODO: each function should have a default value for each
|
||||
//property defined in the cartocss
|
||||
alpha = polygon_alpha({ value: value }, { zoom: 0 });
|
||||
if(alpha === null) {
|
||||
alpha = 1.0;
|
||||
}
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.fillRect(x * res, 256 - res - y * res, res, res);
|
||||
}
|
||||
}
|
||||
prof.end();
|
||||
},
|
||||
|
||||
//
|
||||
// renders a tile in the canvas for key defined in
|
||||
// the torque tile
|
||||
//
|
||||
renderTile: function(tile, key, callback) {
|
||||
if(!this._canvas) return;
|
||||
|
||||
var res = this.options.resolution;
|
||||
|
||||
//var prof = Profiler.get('render').start();
|
||||
var ctx = this._ctx;
|
||||
var colors = this._colors;
|
||||
var activepixels = tile.timeCount[key];
|
||||
if(activepixels) {
|
||||
var w = this._canvas.width;
|
||||
var h = this._canvas.height;
|
||||
//var imageData = ctx.getImageData(0, 0, w, h);
|
||||
//var pixels = imageData.data;
|
||||
var tileMax = this.options.resolution * (this.TILE_SIZE/this.options.resolution - 1)
|
||||
var activePixels = tile.x.length;
|
||||
var anchor = this.options.resolution/2;
|
||||
if (activePixels) {
|
||||
var pixelIndex = tile.timeIndex[key];
|
||||
for(var p = 0; p < activePixels; ++p) {
|
||||
var posIdx = tile.renderDataPos[pixelIndex + p];
|
||||
var c = tile.renderData[pixelIndex + p];
|
||||
if(c) {
|
||||
var color = colors[Math.min(c, colors.length - 1)];
|
||||
var x = tile.x[posIdx];// + px;
|
||||
var y = tile.y[posIdx]; //+ py;
|
||||
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
|
||||
if (sp.fill_opacity > 0) {
|
||||
ctx.globalAlpha = sp.fill_opacity
|
||||
ctx.fillStyle = sp.color;
|
||||
ctx.fillRect(x, y, sp.width, sp.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(x, y, res, res);
|
||||
/*
|
||||
|
||||
for(var xx = 0; xx < res; ++xx) {
|
||||
for(var yy = 0; yy < res; ++yy) {
|
||||
var idx = 4*((x+xx) + w*(y + yy));
|
||||
pixels[idx + 0] = color[0];
|
||||
pixels[idx + 1] = color[1];
|
||||
pixels[idx + 2] = color[2];
|
||||
pixels[idx + 3] = color[3];
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
//ctx.putImageData(imageData, 0, 0);
|
||||
}
|
||||
//prof.end();
|
||||
return callback && callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// exports public api
|
||||
module.exports = RectanbleRenderer;
|
||||
module.exports = PixelRenderer;
|
||||
|
Loading…
Reference in New Issue
Block a user