support for dates + cumulative + animated heatmaps
new default styles
This commit is contained in:
parent
4d1cf96853
commit
2c604a8279
@ -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: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <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]);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
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 times = Object.keys(timeSlots);
|
||||
var maxTime = times && times.length ? times[times.length - 1] : null;
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user