Improve flights example.
Reduce memory use.  Reduce computation time.  Log how log it has been
running.  Show flights with one data point as very short lines (they
look like dots).  Fix some typos in comments.
Added query parameters for location and zoom.
MODIFY
examples/flights/main.js
examples/flights/main.js
* the polling and update interval.
* keep: 3600 (default) or a time in seconds. If specified and collecting live
* data, only keep this duration of data. If 0, keep all data.
+ * x: starting center longitude. Default -75.
+ * y: starting center latitude. Default 40.
+ * zoom: starting zoom level. Default 7.
*/
-var query = utils.getQuery();
+var query = utils.getQuery(),
+ canSelect = query.select !== 'false',
+ hasOpacity = query.opacity !== 'false',
+ hasScale = query.scale !== 'false';
var map, layer, feature, ranges, data;
// We use a d3 color scale, but getting colors from it is slow. Since we use a
// 10-part piecewise linear scale (with 11 specification values), we can use a
-// 10 * 256 + 1 size array to use pre-computed colors with surity that it has
+// 10 * 256 + 1 size array to use pre-computed colors with surety that it has
// all possible values for 8-bit color displays. We can manually interpolate
// to use the scale array.
var d3Scale = d3.scale.linear()
@@ -54,7 +60,7 @@ function draw(drawData) {
ranges = data.ranges;
if (!ranges) {
ranges = {};
- ['time', 'z', 'v', 'vz'].forEach(function (key) {
+ ['time', 'z', 'v'].forEach(function (key) {
ranges[key] = {min: data[0][key][0], max: data[0][key][0]};
data.forEach(function (line) {
for (var i = 0; i < line[key].length; i += 1) {
@@ -62,21 +68,23 @@ function draw(drawData) {
ranges[key].max = Math.max(ranges[key].max, line[key][i]);
}
});
+ ranges[key].range = ranges[key].max - ranges[key].min;
});
}
feature.data(data);
}
- feature.draw();
+ // feature.draw();
+ map.scheduleAnimationFrame(feature.draw);
}
// Create a map object
map = geo.map({
node: '#map',
center: {
- x: -75,
- y: 40
+ x: query.x !== undefined ? +query.x : -75,
+ y: query.y !== undefined ? +query.y : 40
},
- zoom: 7
+ zoom: query.zoom !== undefined ? +query.zoom : 7
});
// Add the default osm layer
@@ -96,44 +104,42 @@ map.layers()[0].attribution(map.layers()[0].attribution() + '. Flight data from
layer = map.createLayer('feature', {features: [geo.lineFeature.capabilities.multicolor]});
// Create a line feature
-feature = layer.createFeature('line', {selectionAPI: query.select !== 'false'})
+feature = layer.createFeature('line', {selectionAPI: canSelect})
// For the line accessor, we can return any array that is the length of the
- // number of verticies in the line.
+ // number of vertices in the line.
.line(function (d) {
return d.time;
})
- .position(function (d, i, l, li) {
- return {x: data[li].lon[i], y: data[li].lat[i]};
+ .position(function (d, i, l) {
+ return {x: l.lon[i], y: l.lat[i]};
})
.style({
- 'strokeColor': function (d, i, l, li) {
- if (query.scale === 'false') {
+ strokeColor: function (d, i, l) {
+ if (!hasScale) {
// d3 scales are slow
// return d3Scale(0);
return scale[0];
}
- var val = (data[li].v[i] - ranges.v.min) / (ranges.v.max - ranges.v.min);
+ var val = (l.v[i] - ranges.v.min) / ranges.v.range;
// d3 scales are slow
// return d3Scale(val);
- val = Math.round(d3ScaleParts * val);
- return val < 0 ? scale[0] : val > d3ScaleParts ? scale[d3ScaleParts] : scale[val];
+ return val < 0 ? scale[0] : val > 1 ? scale[d3ScaleParts] : scale[Math.round(d3ScaleParts * val)];
},
- 'strokeWidth': function (d, i, l, li) {
- var width = 0.5 + 4.5 * (data[li].z[i] - ranges.z.min) / (ranges.z.max - ranges.z.min);
- if (data[li].hover) {
+ strokeWidth: function (d, i, l) {
+ var width = 0.5 + 3.0 * (l.z[i] - ranges.z.min) / ranges.z.range;
+ if (canSelect && l.hover) {
width = width * 2 + 1;
}
return width;
},
- 'strokeOpacity': function (d, i, l, li) {
- if (data[li].hover) {
+ strokeOpacity: function (d, i, l) {
+ if (canSelect && l.hover) {
return 1;
}
- if (query.opacity === 'false') {
+ if (!hasOpacity) {
return 0.25;
}
- var opac = 0.05 + 0.70 * (data[li].time[i] - ranges.time.min) / (ranges.time.max - ranges.time.min);
- return opac;
+ return 0.05 + 0.70 * (d - ranges.time.min) / ranges.time.range;
},
lineCap: 'round',
lineJoin: 'round'
@@ -141,11 +147,12 @@ feature = layer.createFeature('line', {selectionAPI: query.select !== 'false'})
.geoOff(geo.event.feature.mouseover)
.geoOff(geo.event.feature.mouseout)
.geoOn(geo.event.feature.mouseover, function (evt) {
- if (!evt.top) { return; }
+ if (!evt.top || evt.data.hover) { return; }
data.forEach(function (d) {
d.hover = false;
});
evt.data.hover = true;
+ console.log(evt.data.id);
this.modified();
feature.draw();
});

MODIFY
examples/flights/worker.js
examples/flights/worker.js
@@ -35,11 +35,12 @@ var rr = {
onmessage = function (evt) {
var interval = evt.data.interval || 30000,
keep = evt.data.keep,
- starttime = Date.now();
+ starttime = Date.now(),
+ firsttime = starttime;
/**
* Fetch data from the source url, process it if successful, and schedule
- * anotehr data collection when done or errored.
+ * another data collection when done or errored.
*/
function fetchData() {
var xhr = new XMLHttpRequest();
@@ -63,7 +64,7 @@ onmessage = function (evt) {
}
/**
- * Wait until the next collection time then fecth more data.
+ * Wait until the next collection time then fetch more data.
*/
function wait() {
var nexttime = starttime,
@@ -92,7 +93,7 @@ onmessage = function (evt) {
var cutoff = data.time - (Math.ceil(keep / interval) + 0.5) * interval / 1000;
Object.keys(flights).forEach(function (id) {
while (flights[id].time[0] < cutoff) {
- ['time', 'lon', 'lat', 'z', 'v', 'dir', 'vz'].forEach(function (key) {
+ ['time', 'lon', 'lat', 'z', 'v', 'dir'].forEach(function (key) {
flights[id][key].shift();
});
if (!flights[id].time.length) {
@@ -112,7 +113,8 @@ onmessage = function (evt) {
if (!record[rr.icao24] || !record[rr.callsign] ||
!record[rr.timePosition] || !record[rr.lastContact] ||
record[rr.longitude] === null || record[rr.latitude] === null ||
- record[rr.geoAltitude] === null || record[rr.onGround] ||
+ record[rr.geoAltitude] === null || record[rr.geoAltitude] < -450 ||
+ record[rr.geoAltitude] > 15000 || record[rr.onGround] ||
record[rr.velocity] === null || record[rr.velocity] < 0 ||
record[rr.velocity] > 360 || record[rr.trueTrack] === null ||
record[rr.verticalRate] === null ||
@@ -126,7 +128,6 @@ onmessage = function (evt) {
state.z = record[rr.geoAltitude];
state.v = record[rr.velocity];
state.dir = record[rr.trueTrack];
- state.vz = record[rr.verticalRate];
if (flights[id]) {
last = flights[id].time.length - 1;
// If the current state is later than the most recent recorded state,
@@ -137,9 +138,9 @@ onmessage = function (evt) {
// If we haven't seen this aircraft in a long time or it has jumped a
// large distance, make it a separate track.
if (Math.abs(state.time - flights[id].time[last]) > timegap ||
- Math.abs(state.z - flights[id].z[last] > 200 ||
+ Math.abs(state.z - flights[id].z[last]) > 500 ||
((state.lat - flights[id].lat[last]) ** 2 +
- (state.lon - flights[id].lon[last]) ** 2) ** 0.5 > 0.5)) {
+ (state.lon - flights[id].lon[last]) ** 2) ** 0.5 > 0.5) {
if (last) {
flights[id + '_' + flights[id].time[last]] = flights[id];
}
@@ -148,17 +149,28 @@ onmessage = function (evt) {
}
// Store data in arrays to reduce memory use.
if (!flights[id]) {
- flights[id] = {time: [], lon: [], lat: [], z: [], v: [], dir: [], vz: []};
+ flights[id] = {time: [], lon: [], lat: [], z: [], v: [], dir: [], id: id};
}
- ['time', 'lon', 'lat', 'z', 'v', 'dir', 'vz'].forEach(function (key) {
+ ['time', 'lon', 'lat', 'z', 'v', 'dir'].forEach(function (key) {
flights[id][key].push(state[key]);
});
});
- // Ouput any aircraft that where the track has two or more points
+ // Output any aircraft that where the track has two or more points. For
+ // tracks with one point, duplicate the point.
var output = [], ranges = {};
for (id in flights) {
if (flights[id].time.length > 1) {
output.push(flights[id]);
+ } else if (flights[id].time.length === 1) {
+ output.push({
+ time: [flights[id].time[0] - 1e-6, flights[id].time[0]],
+ lon: [flights[id].lon[0] - 1e-6, flights[id].lon[0]],
+ lat: [flights[id].lat[0] - 1e-6, flights[id].lat[0]],
+ z: [flights[id].z[0] - 1e-6, flights[id].z[0]],
+ v: [flights[id].v[0] - 1e-6, flights[id].v[0]],
+ dir: [flights[id].dir[0], flights[id].dir[0]],
+ id: flights[id].id
+ });
}
}
if (!output.length) {
@@ -166,7 +178,7 @@ onmessage = function (evt) {
}
// Compute the ranges in the data. It is better to do it in the worker
// than the main script, as this is non-blocking.
- ['time', 'z', 'v', 'vz'].forEach(function (key) {
+ ['time', 'z', 'v'].forEach(function (key) {
ranges[key] = {min: output[0][key][0], max: output[0][key][0]};
output.forEach(function (line) {
for (var i = 0; i < line[key].length; i += 1) {
@@ -178,10 +190,15 @@ onmessage = function (evt) {
}
}
});
+ if (ranges[key].max === ranges[key].min) {
+ ranges[key].min -= 1;
+ }
+ ranges[key].range = ranges[key].max - ranges[key].min;
});
output.ranges = ranges;
// Send the results to the main script.
postMessage(output);
+ console.log('elapsed', (Date.now() - firsttime) / 1000);
}
// start a data collection immediately.

Improve flights example.

Reduce memory use.  Reduce computation time.  Log how log it has been
running.  Show flights with one data point as very short lines (they
look like dots).  Fix some typos in comments.

Added query parameters for location and zoom.