website/static/workers/trips-data-decoder.js (157 lines of code) (raw):
importScripts('./util.js');
const FLUSH_LIMIT = 5000;
let LOOP_LENGTH = 3600;
let TRAIL_LENGTH = 180;
const args = location.search.match((/[^&?]+/g)) || [];
args.forEach(function(arg) {
const tokens = arg.split('=');
if (tokens[0] === 'loop') {
LOOP_LENGTH = Number(tokens[1]);
}
if (tokens[0] === 'trail') {
TRAIL_LENGTH = Number(tokens[1]);
}
});
let segments;
let result = [];
let vertexCount = 0;
let tripsCount = 0;
onmessage = function(e) {
const lines = e.data.text.split('\n');
lines.forEach(function(l, i) {
if (!l) {
return;
}
if (!segments) {
segments = decodeSegments(l);
} else {
let trip = decodeTrip(l, segments);
const trip_offset = 0;
addTrip(sliceTrip(trip, -TRAIL_LENGTH, LOOP_LENGTH));
while (trip.endTime > LOOP_LENGTH - TRAIL_LENGTH) {
trip = shiftTrip(trip, -LOOP_LENGTH);
addTrip(sliceTrip(trip, -TRAIL_LENGTH, LOOP_LENGTH));
}
tripsCount++;
}
});
if (e.data.event === 'load') {
flush();
postMessage({action: 'end'});
}
};
function addTrip(trip) {
result.push(trip);
vertexCount += trip.path.length;
if (result.length >= FLUSH_LIMIT) {
flush();
}
}
function flush() {
postMessage({
action: 'add',
data: result,
meta: {trips: tripsCount, vertices: vertexCount, progress: tripsCount / 9970 * 0.8}
});
result = [];
}
function sliceTrip(trip, start, end) {
let i, startIndex = -1, endIndex = -1;
for (i = 0; i < trip.timestamps.length; i++) {
const t = trip.timestamps[i];
if (t > start && startIndex === -1) {
startIndex = Math.max(0, i - 1);
}
if (t > end && endIndex === -1) {
i++;
break;
}
}
endIndex = i;
return {
vendor: trip.vendor,
startTime: trip.startTime,
endTime: trip.endTime,
path: trip.path.slice(startIndex, endIndex),
timestamps: trip.timestamps.slice(startIndex, endIndex)
};
}
function shiftTrip(trip, offset) {
return {
vendor: trip.vendor,
startTime: trip.startTime + offset,
endTime: trip.endTime + offset,
path: trip.path,
timestamps: trip.timestamps.map(function(t) {
return t + offset;
})
};
}
function decodeTrip(str, segments) {
const vendor = decodeNumber(str.slice(0, 1), 90, 32);
const startTime = decodeNumber(str.slice(1, 3), 90, 32);
const endTime = decodeNumber(str.slice(3, 5), 90, 32);
const segs = decodeSegmentsArray(str.slice(5), segments);
const projectedTimes = segs.reduce(function(acc, seg, i) {
const t = acc[i] + seg[seg.length - 1][2];
return acc.concat(t);
}, [0]);
const rT = (endTime - startTime) / projectedTimes[projectedTimes.length - 1];
const z = Math.random() * 20;
const path = [];
const timestamps = [];
segs.forEach(function(seg, i) {
const t0 = projectedTimes[i];
seg.forEach(function (s, j) {
if (i === 0 || j > 0) {
path.push([s[0], s[1], z]);
timestamps.push((s[2] + t0) * rT + startTime);
}
});
});
return {
vendor,
startTime,
endTime,
path,
timestamps,
};
}
function decodeSegmentsArray(str, segments) {
const tokens = str.split(/([\x20-\x4c])/);
const segs = [];
for (let i = 1; i < tokens.length - 1; i += 2) {
const segIndexStr = String.fromCharCode(tokens[i].charCodeAt(0) + 45) + tokens[i + 1];
const segIndex = decodeNumber(segIndexStr, 45, 77);
segs.push(segments[segIndex]);
}
return segs;
}
function decodeSegments(str) {
const tokens = str.split(/([\x3e-\xff]+)/);
const result = [];
for (let i = 0; i < tokens.length - 1; i += 2) {
var T = decodeNumber(tokens[i], 30, 32);
var coords = decodePolyline(tokens[i + 1]);
var distances = coords.reduce(function(acc, c, j) {
let d = 0;
if (j > 0) {
d = acc[j - 1] + distance(coords[j], coords[j - 1]);
}
return acc.concat(d);
}, []);
var D = distances[distances.length - 1];
result[i / 2] = coords.map(function(c, j) {
return [c[0], c[1], distances[j] / D * T];
});
}
return result;
}
/*
* adapted from turf-distance http://turfjs.org
*/
function distance(from, to) {
const degrees2radians = Math.PI / 180;
const dLat = degrees2radians * (to[1] - from[1]);
const dLon = degrees2radians * (to[0] - from[0]);
const lat1 = degrees2radians * from[1];
const lat2 = degrees2radians * to[1];
const a = Math.pow(Math.sin(dLat / 2), 2) +
Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
return Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}