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> <style>
#map, html, body { #map, html, body {
width: 100%; width: 100%;
height: 100%; height: 95%;
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
@ -24,44 +24,98 @@
var map = new L.Map('map', { var map = new L.Map('map', {
zoomControl: true, zoomControl: true,
center: [40, -100], 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>' attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
}).addTo(map); }).addTo(map);
var CARTOCSS = [ var CARTOCSS_NORMAL = [
'Map {', 'Map {',
'-torque-time-attribute: "date";',
'-torque-aggregation-function: "count(cartodb_id)";',
'-torque-frame-count: 1000;',
'-torque-animation-duration: 200;',
'-torque-resolution: 2',
'}', '}',
'#layer {', '#layer{',
' marker-width: 3;', ' comp-op: lighter;',
' marker-fill-opacity: 0.8;', ' marker-line-color: #FFF;',
' marker-fill: #FEE391; ', ' marker-line-width: 0;',
' comp-op: "lighten";', ' marker-line-opacity: 1;',
' [value > 10] { marker-fill: #FEC44F; }', ' marker-type: ellipse;',
' [value > 20] { marker-fill: #CC4C02; }', ' marker-width: 2;',
' [value > 30] { marker-fill: #662506; }', ' marker-fill: #FF6600;',
' [frame-offset = 1] { marker-width: 10; marker-fill-opacity: 0.05;}', ' marker-opacity: 0.1',
' [frame-offset = 2] { marker-width: 15; marker-fill-opacity: 0.02;}', '}',
'#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'); ].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({ var torqueLayer = new L.TorqueLayer({
provider: 'internal', provider: 'internal',
resolution: 4, loop: loop,
start: 0, steps: steps,
end_date: 999, animationDuration: animationDuration,
step: 1, data_aggregation: cumulative ? "cumulative" : undefined,
steps: 1000, cartocss: cartocss
countby: 'count(amount)',
pixel_size: 3,
cartocss: CARTOCSS
}); });
torqueLayer.addTo(map); torqueLayer.addTo(map);
@ -71,7 +125,7 @@
Papa.parse(file, { Papa.parse(file, {
complete: function (results) { 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]; var point = results.data[i];
torqueLayer.provider.addPoint(point[1], point[0], point[3], point[2]); 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; return this * Math.PI / 180;
}; };
/*
* Options:
* steps:
*/
var internal = function (options) { var internal = function (options) {
this.setReady(false); this.setReady(false);
this.options = options; this.options = options;
if (this.options.data_aggregation) {
this.options.cumulative = this.options.data_aggregation === 'cumulative';
}
this.points = []; this.points = [];
this._tileQueue = []; this._tileQueue = [];
}; };
@ -17,7 +23,17 @@ var internal = function (options) {
internal.prototype = { internal.prototype = {
addPoint: function (lat, lon, time, value) { 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() { getBounds: function() {
return this.options.bounds; return this.options.bounds;
@ -26,6 +42,29 @@ internal.prototype = {
if (ready == false) { if (ready == false) {
this._ready = false; this._ready = false;
} else { } 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._ready = true;
this._processQueue(); this._processQueue();
this.options.ready && this.options.ready(); this.options.ready && this.options.ready();
@ -83,7 +122,7 @@ internal.prototype = {
return pointsInTile; 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. * 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 = []; var y = [];
/* /*
* timeSlots: * pointsInTilePerTimestamp:
*/ */
var timeSlots = {}; var pointsInThisTileByTimestamp = [];
var timeCount = []; var timeCount = [];
var timeIndex = []; var timeIndex = [];
var renderData = []; var renderData = [];
var renderDataPos = []; var renderDataPos = [];
for (var pointIdx = 0; pointIdx < pointsInTile.length; pointIdx++) { var accumulatedValues = [];
var point = pointsInTile[pointIdx];
for (var pointIdx = 0; pointIdx < pointsInThisTile.length; pointIdx++) {
var point = pointsInThisTile[pointIdx];
var tileXY = latLonToTileXY(point.lat, point.lon, tile.latLonBounds); var tileXY = latLonToTileXY(point.lat, point.lon, tile.latLonBounds);
x[pointIdx] = tileXY.x; var xInTile = tileXY.x;
y[pointIdx] = tileXY.y; var yInTile = tileXY.y;
x[pointIdx] = xInTile;
y[pointIdx] = yInTile;
var timeSlot = timeSlots[point.time] || (timeSlots[point.time] = []); var pointTimestamp;
timeSlot.push([pointIdx, point.value]); if (this.timestampType == 'date') {
pointTimestamp = this.timestamps[point.time];
} else {
pointTimestamp = point.time;
} }
var times = Object.keys(timeSlots); if (this.options.cumulative) {
var maxTime = times && times.length ? times[times.length - 1] : null; 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 renderDataIndex = 0;
var timeSlotIndex = 0; var timeStampIndex = 0;
for (var timestamp = 0; timestamp <= maxTime; ++timestamp) { for (var timestamp = 0; timestamp <= maxTileTimestamp; timestamp++) {
timeSlot = timeSlots[timestamp]; var pointsInThisTileForThisTimestamp = pointsInThisTileByTimestamp[timestamp];
if (timeSlot) { if (pointsInThisTileForThisTimestamp) {
for (var pointInTimeSlotIdx = 0; pointInTimeSlotIdx < timeSlot.length; pointInTimeSlotIdx++) { for (var i = 0; i < pointsInThisTileForThisTimestamp.length; i++) {
var pointInTimeSlot = timeSlot[pointInTimeSlotIdx]; var point = pointsInThisTileForThisTimestamp[i];
renderDataPos[renderDataIndex] = pointInTimeSlot[0]; renderDataPos[renderDataIndex] = point.idx;
renderData[renderDataIndex] = pointInTimeSlot[1]; renderData[renderDataIndex] = point.value;
++renderDataIndex; ++renderDataIndex;
} }
} }
timeIndex[timestamp] = timeSlotIndex; timeIndex[timestamp] = timeStampIndex;
var increase = timeSlot ? timeSlot.length : 0; var increase = pointsInThisTileForThisTimestamp ? pointsInThisTileForThisTimestamp.length : 0;
timeCount[timestamp] = increase; timeCount[timestamp] = increase;
timeSlotIndex += increase; timeStampIndex += increase;
} }
return { return {
x: x, x: x,
@ -143,7 +210,7 @@ internal.prototype = {
timeIndex: timeIndex, timeIndex: timeIndex,
renderDataPos: renderDataPos, renderDataPos: renderDataPos,
renderData: renderData, renderData: renderData,
maxDate: maxTime maxDate: maxTileTimestamp
}; };
}, },
getKeySpan: function () { getKeySpan: function () {
@ -152,11 +219,11 @@ internal.prototype = {
end: this.options.end * 1000, end: this.options.end * 1000,
step: this.options.step, step: this.options.step,
steps: this.options.steps, steps: this.options.steps,
columnType: this.options.is_time ? 'date': 'number' columnType: this.timestampType
}; };
}, },
getSteps: function () { getSteps: function () {
return Math.min(this.options.steps, this.options.steps); return this.options.steps;
} }
}; };