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