support for dates + cumulative + animated heatmaps

new default styles
This commit is contained in:
Dani Carrion 2015-06-30 22:04:05 +02:00
parent 4d1cf96853
commit 2c604a8279
2 changed files with 176 additions and 55 deletions

View File

@ -5,7 +5,7 @@
<style>
#map, html, body {
width: 100%;
height: 100%;
height: 95%;
padding: 0;
margin: 0;
}
@ -24,44 +24,98 @@
var map = new L.Map('map', {
zoomControl: true,
center: [40, -100],
zoom: 3
zoom: 5
});
L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
L.tileLayer('http://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
}).addTo(map);
var CARTOCSS = [
var CARTOCSS_NORMAL = [
'Map {',
'-torque-time-attribute: "date";',
'-torque-aggregation-function: "count(cartodb_id)";',
'-torque-frame-count: 1000;',
'-torque-animation-duration: 200;',
'-torque-resolution: 2',
'}',
'#layer {',
' marker-width: 3;',
' marker-fill-opacity: 0.8;',
' marker-fill: #FEE391; ',
' comp-op: "lighten";',
' [value > 10] { marker-fill: #FEC44F; }',
' [value > 20] { marker-fill: #CC4C02; }',
' [value > 30] { marker-fill: #662506; }',
' [frame-offset = 1] { marker-width: 10; marker-fill-opacity: 0.05;}',
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}',
'#layer{',
' comp-op: lighter;',
' marker-line-color: #FFF;',
' marker-line-width: 0;',
' marker-line-opacity: 1;',
' marker-type: ellipse;',
' marker-width: 2;',
' marker-fill: #FF6600;',
' marker-opacity: 0.1',
'}',
'#layer::point2 {',
' marker-width: 1;',
' marker-fill: #FF6600;',
' marker-fill-opacity: 1;',
' marker-line-color: #fff;',
' marker-line-width: 1;',
' marker-line-opacity: 0;',
'}',
'#layer::point3 {',
' marker-width: 4;',
' marker-fill: #ff6600;',
' marker-fill-opacity: .2;',
' marker-line-color: #fff;',
' marker-line-width: 1;',
' marker-line-opacity: 0;',
'}',
'#layer::point {',
' marker-width: 6;',
' marker-fill: #ff6600;',
' marker-fill-opacity: 0;',
' marker-line-color: #ff6600;',
' marker-line-width: 1;',
' marker-line-opacity: .1;',
'}',
'#layer[frame-offset=1] {',
' marker-width:2;',
' marker-opacity:0.45;',
'}',
'#layer[frame-offset=2]{',
' marker-width:4;',
' marker-opacity:0.225;',
'}',
'#layer[frame-offset=3]{',
' marker-width:6;',
' marker-opacity:0.1;',
'}',
'#layer[frame-offset=4]{',
' marker-width:8;',
' marker-opacity:0.05;',
'}',
'#layer[frame-offset=5]{',
' marker-width:10;',
' marker-opacity:0.02;',
'}'
].join('\n');
var CARTOCSS_INTENSITY = [
'Map {',
'}',
'#layer {',
' image-filters: colorize-alpha(blue, cyan, lightgreen, yellow , orange, red);',
' marker-file: url(http://s3.amazonaws.com/com.cartodb.assets.static/alphamarker.png);',
' marker-fill-opacity: 0.4*[value];',
' marker-width: 35;',
'}'
].join('\n');
var pointsToLoad = 1000;
var animationDuration = 60;
var steps = pointsToLoad;
var cumulative = true;
var loop = true;
var cartocss = CARTOCSS_NORMAL;
var torqueLayer = new L.TorqueLayer({
provider: 'internal',
resolution: 4,
start: 0,
end_date: 999,
step: 1,
steps: 1000,
countby: 'count(amount)',
pixel_size: 3,
cartocss: CARTOCSS
loop: loop,
steps: steps,
animationDuration: animationDuration,
data_aggregation: cumulative ? "cumulative" : undefined,
cartocss: cartocss
});
torqueLayer.addTo(map);
@ -71,7 +125,7 @@
Papa.parse(file, {
complete: function (results) {
for (var i = 1; i <= 1000; i++) {
for (var i = 1; i <= pointsToLoad && i < results.data.length; i++) {
var point = results.data[i];
torqueLayer.provider.addPoint(point[1], point[0], point[3], point[2]);
}

View File

@ -6,10 +6,16 @@ Number.prototype.toRad = function () {
return this * Math.PI / 180;
};
/*
* Options:
* steps:
*/
var internal = function (options) {
this.setReady(false);
this.options = options;
if (this.options.data_aggregation) {
this.options.cumulative = this.options.data_aggregation === 'cumulative';
}
this.points = [];
this._tileQueue = [];
};
@ -17,7 +23,17 @@ var internal = function (options) {
internal.prototype = {
addPoint: function (lat, lon, time, value) {
this.points.push({lat: parseFloat(lat), lon: parseFloat(lon), time: parseInt(time), value: parseFloat(value)});
if (typeof(time) == "number") {
time = parseInt(time);
if (this.timestampType === undefined) {
this.timestampType = "number";
}
} else {
if (this.timestampType === undefined) {
this.timestampType = "date";
}
}
this.points.push({lat: parseFloat(lat), lon: parseFloat(lon), time: time, value: parseFloat(value) || value});
},
getBounds: function() {
return this.options.bounds;
@ -26,6 +42,29 @@ internal.prototype = {
if (ready == false) {
this._ready = false;
} else {
var self = this;
this.points.sort(function (point1, point2) {
if (self.timestampType == "number") {
return point1.value - point2.value;
} else {
return new Date(point1.value) - new Date(point2.value);
}
});
if (this.timestampType == "date") {
this.timestamps = {}; // {date: timestamp_id}
var timestampIdx = 0;
for (var i = 0; i < this.points.length; i++) {
var point = this.points[i];
this.timestamps[point.time] || (this.timestamps[point.time] = timestampIdx++);
}
}
if (this.timestampType == "number") {
this.maxTimestamp = this.points[this.points.length - 1].time;
} else {
this.maxTimestamp = this.timestamps[this.points[this.points.length - 1].time];
}
this._ready = true;
this._processQueue();
this.options.ready && this.options.ready();
@ -83,7 +122,7 @@ internal.prototype = {
return pointsInTile;
},
processTile: function (tile, pointsInTile) {
processTile: function (tile, pointsInThisTile) {
/*
* For each this.points[i], x[i] and y[i] are the offsets in pixels from the tile boundaries.
*/
@ -91,44 +130,72 @@ internal.prototype = {
var y = [];
/*
* timeSlots:
* pointsInTilePerTimestamp:
*/
var timeSlots = {};
var pointsInThisTileByTimestamp = [];
var timeCount = [];
var timeIndex = [];
var renderData = [];
var renderDataPos = [];
for (var pointIdx = 0; pointIdx < pointsInTile.length; pointIdx++) {
var point = pointsInTile[pointIdx];
var accumulatedValues = [];
for (var pointIdx = 0; pointIdx < pointsInThisTile.length; pointIdx++) {
var point = pointsInThisTile[pointIdx];
var tileXY = latLonToTileXY(point.lat, point.lon, tile.latLonBounds);
x[pointIdx] = tileXY.x;
y[pointIdx] = tileXY.y;
var xInTile = tileXY.x;
var yInTile = tileXY.y;
x[pointIdx] = xInTile;
y[pointIdx] = yInTile;
var timeSlot = timeSlots[point.time] || (timeSlots[point.time] = []);
timeSlot.push([pointIdx, point.value]);
var pointTimestamp;
if (this.timestampType == 'date') {
pointTimestamp = this.timestamps[point.time];
} else {
pointTimestamp = point.time;
}
var times = Object.keys(timeSlots);
var maxTime = times && times.length ? times[times.length - 1] : null;
if (this.options.cumulative) {
if (accumulatedValues[xInTile] === undefined) {
accumulatedValues[xInTile] = [];
}
if (accumulatedValues[xInTile][yInTile] === undefined) {
accumulatedValues[xInTile][yInTile] = 0;
}
accumulatedValues[xInTile][yInTile] += point.value;
point.value = accumulatedValues[xInTile][yInTile];
for (var futureTimestamps = pointTimestamp; futureTimestamps < this.maxTimestamp; futureTimestamps++) {
var pointsInTileForFutureTimestamp = pointsInThisTileByTimestamp[futureTimestamps] || (pointsInThisTileByTimestamp[futureTimestamps] = []);
pointsInTileForFutureTimestamp.push({idx: pointIdx, value: point.value});
}
console.log(pointsInThisTileByTimestamp.length);
} else {
var pointsInTileForThisTimestamp = pointsInThisTileByTimestamp[pointTimestamp] || (pointsInThisTileByTimestamp[pointTimestamp] = []);
pointsInTileForThisTimestamp.push({idx: pointIdx, value: point.value});
}
}
var pointsInThisTileByTimestamp_keys = Object.keys(pointsInThisTileByTimestamp);
var maxTileTimestamp = pointsInThisTileByTimestamp_keys[pointsInThisTileByTimestamp_keys.length - 1] || 0;
var renderDataIndex = 0;
var timeSlotIndex = 0;
for (var timestamp = 0; timestamp <= maxTime; ++timestamp) {
timeSlot = timeSlots[timestamp];
if (timeSlot) {
for (var pointInTimeSlotIdx = 0; pointInTimeSlotIdx < timeSlot.length; pointInTimeSlotIdx++) {
var pointInTimeSlot = timeSlot[pointInTimeSlotIdx];
renderDataPos[renderDataIndex] = pointInTimeSlot[0];
renderData[renderDataIndex] = pointInTimeSlot[1];
var timeStampIndex = 0;
for (var timestamp = 0; timestamp <= maxTileTimestamp; timestamp++) {
var pointsInThisTileForThisTimestamp = pointsInThisTileByTimestamp[timestamp];
if (pointsInThisTileForThisTimestamp) {
for (var i = 0; i < pointsInThisTileForThisTimestamp.length; i++) {
var point = pointsInThisTileForThisTimestamp[i];
renderDataPos[renderDataIndex] = point.idx;
renderData[renderDataIndex] = point.value;
++renderDataIndex;
}
}
timeIndex[timestamp] = timeSlotIndex;
var increase = timeSlot ? timeSlot.length : 0;
timeIndex[timestamp] = timeStampIndex;
var increase = pointsInThisTileForThisTimestamp ? pointsInThisTileForThisTimestamp.length : 0;
timeCount[timestamp] = increase;
timeSlotIndex += increase;
timeStampIndex += increase;
}
return {
x: x,
@ -143,7 +210,7 @@ internal.prototype = {
timeIndex: timeIndex,
renderDataPos: renderDataPos,
renderData: renderData,
maxDate: maxTime
maxDate: maxTileTimestamp
};
},
getKeySpan: function () {
@ -152,11 +219,11 @@ internal.prototype = {
end: this.options.end * 1000,
step: this.options.step,
steps: this.options.steps,
columnType: this.options.is_time ? 'date': 'number'
columnType: this.timestampType
};
},
getSteps: function () {
return Math.min(this.options.steps, this.options.steps);
return this.options.steps;
}
};