You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.4 KiB
JavaScript
241 lines
7.4 KiB
JavaScript
/* TODO:
|
|
- get maxSpeed of track
|
|
*/
|
|
|
|
var gpx2geojson = require('idris-gpx'),
|
|
async = require('async'),
|
|
turf = require('turf'),
|
|
turfMeta = require('turf-meta');
|
|
|
|
module.exports = function(grunt) {
|
|
|
|
grunt.initConfig({
|
|
parseGPX: {
|
|
src: 'data/*.gpx',
|
|
dest: 'tracks.json',
|
|
pretty: true
|
|
},
|
|
ftp_push: {
|
|
nroo: {
|
|
options: {
|
|
host: '88.79.198.170',
|
|
dest: '/httpdocs/gps-traffic/',
|
|
username: 'yvbfqhoh',
|
|
password: 'uk7Xx%74'
|
|
},
|
|
files: [{
|
|
expand: true,
|
|
cwd: '.',
|
|
src: [
|
|
'*.html',
|
|
'*.json',
|
|
'static/**'
|
|
]
|
|
}]
|
|
}
|
|
},
|
|
connect: {
|
|
server: {
|
|
options: {
|
|
open: true,
|
|
livereload: true,
|
|
debug: true,
|
|
hostname: 'localhost'
|
|
}
|
|
}
|
|
},
|
|
watch: {
|
|
sources: {
|
|
files: ['<%= parseGPX.src %>'],
|
|
tasks: ['parseGPX'],
|
|
options: { livereload: true }
|
|
},
|
|
static: {
|
|
files: ['static/**', 'index.html', '<%= parseGPX.dest %>'],
|
|
options: { livereload: true }
|
|
}
|
|
}
|
|
});
|
|
|
|
grunt.loadNpmTasks('grunt-ftp-push');
|
|
grunt.loadNpmTasks('grunt-contrib-connect');
|
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
|
|
|
grunt.registerTask('parseGPX', function() {
|
|
grunt.config.requires('parseGPX.src');
|
|
grunt.config.requires('parseGPX.dest');
|
|
|
|
var done = this.async();
|
|
var paths = grunt.file.expand({ filter: 'isFile' }, grunt.config('parseGPX.src'));
|
|
|
|
async.map(paths, function(path, callback) {
|
|
var basename = path.split('/').pop().split('.');
|
|
|
|
if (basename[basename.length-1] !== 'gpx') return callback('unknown file format');
|
|
|
|
gpx2geojson.points(path, function(json) {
|
|
getSpeedProfile(json, function(err, result) {
|
|
grunt.file.write('speedprofiles/' + basename[0] + '.json', JSON.stringify(result));
|
|
});
|
|
processGeoJSON(json, basename[0].split('_'), callback);
|
|
});
|
|
},
|
|
function allParsed(err, result) {
|
|
if (err) return done(err);
|
|
grunt.file.write(grunt.config('parseGPX.dest'),
|
|
JSON.stringify(result, null, grunt.config('parseGPX.pretty') ? 2 : 0));
|
|
done();
|
|
});
|
|
});
|
|
|
|
grunt.registerTask('publish', ['parseGPX', 'ftp_push']);
|
|
grunt.registerTask('default', ['parseGPX', 'connect', 'watch']);
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
function getSpeedProfile(geojson, callback) {
|
|
var data = JSON.parse(JSON.stringify(geojson), function(key, val) {
|
|
return !isNaN(parseFloat(val)) && isFinite(val) ? parseFloat(val) : val;
|
|
});
|
|
var prevPoint = null, distance = 0; result = [];
|
|
|
|
turfMeta.featureEach(data, function(point) {
|
|
// create a line from the waypoints
|
|
if (prevPoint !== null) {
|
|
var duration = new Date(point.properties.time) - new Date(prevPoint.properties.time);
|
|
duration /= 1000 * 60 * 60; // convert millisec to hours
|
|
var sectionDist = turf.distance(prevPoint, point, 'kilometers');
|
|
distance += sectionDist;
|
|
result.push({
|
|
timestamp: point.properties.time,
|
|
speed: sectionDist / duration,
|
|
distance: distance
|
|
});
|
|
}
|
|
prevPoint = point;
|
|
});
|
|
|
|
return callback(null, result);
|
|
};
|
|
|
|
|
|
/**
|
|
* converts geojson points to a linestring & processes meta information
|
|
* @param geojson: geojson featurecollection containing points
|
|
* @param tags: optional array of tag strings
|
|
* @param done: node style callback function
|
|
* @returns object in the form of { meta: {...}, events: {...}, track: {...} }
|
|
*/
|
|
function processGeoJSON(geojson, tags, done) {
|
|
// create a clone of the data & convert strings to numbers where possible
|
|
var data = JSON.parse(JSON.stringify(geojson), function(key, val) {
|
|
return !isNaN(parseFloat(val)) && isFinite(val) ? parseFloat(val) : val;
|
|
});
|
|
|
|
var prevPoint = null, prevLine = null, lines = [], eventPoints = [], result = {
|
|
meta: {
|
|
tags: tags || [],
|
|
length: 0, // kilometers
|
|
duration: 0, // hours
|
|
standingtime: 0, // hours
|
|
maxSpeed: 0, // km/h
|
|
avgSpeed: 0, // km/h
|
|
date: data.features[0].properties.time,
|
|
events: {}
|
|
},
|
|
events: {},
|
|
track: turf.featureCollection(lines)
|
|
};
|
|
|
|
turfMeta.featureEach(data, function(point) {
|
|
// create a line from the waypoints
|
|
if (prevPoint !== null) {
|
|
var linestring = turf.lineString([
|
|
prevPoint.geometry.coordinates, point.geometry.coordinates
|
|
]);
|
|
var duration = new Date(point.properties.time) - new Date(prevPoint.properties.time);
|
|
duration /= 1000 * 60 * 60; // convert millisec to hours
|
|
linestring.properties.length = roundFloat(turf.distance(prevPoint, point, 'kilometers'), 4);
|
|
linestring.properties.speed = roundFloat(linestring.properties.length / duration);
|
|
linestring.properties.bearing = roundFloat(turf.bearing(prevPoint, point), 1);
|
|
linestring.properties.elevation = roundFloat(point.properties.ele - prevPoint.properties.ele);
|
|
lines.push(linestring);
|
|
|
|
// update global metadata
|
|
result.meta.length += linestring.properties.length;
|
|
result.meta.duration += duration;
|
|
if (linestring.properties.speed <= 10)
|
|
result.meta.standingtime += duration;
|
|
if (result.meta.maxSpeed < linestring.properties.speed)
|
|
result.meta.maxSpeed = linestring.properties.speed;
|
|
|
|
linestring.properties.trackPosition = roundFloat(result.meta.length, 3);
|
|
}
|
|
|
|
prevPoint = point;
|
|
});
|
|
|
|
prevPoint = null;
|
|
result.meta.avgSpeed = result.meta.length / result.meta.duration;
|
|
result.meta.standingtime = result.meta.standingtime;
|
|
result.meta.length = roundFloat(result.meta.length);
|
|
result.meta.duration = roundFloat(result.meta.duration);
|
|
|
|
// detect events
|
|
var events = {
|
|
'stop': {
|
|
fn: function isStop(line, prevLine) {
|
|
return (line.properties.speed <= 10); // slower than 10km/h?
|
|
},
|
|
coordIndex: 1 // apply the event to the i coordinate of the linestring
|
|
},
|
|
/*'turn': {
|
|
fn: function isTurn(line, prevLine) {
|
|
var bearingThresh = 64; // degrees
|
|
var distanceThresh = 0.005; // km
|
|
var angle = line.properties.bearing - prevLine.properties.bearing;
|
|
if (angle > 180) angle -= 360;
|
|
else if (angle < -180) angle += 360;
|
|
|
|
if (prevLine.properties.length > distanceThresh && Math.abs(angle) >= bearingThresh)
|
|
return true;
|
|
return false;
|
|
},
|
|
coordIndex: 0
|
|
}*/
|
|
};
|
|
|
|
// init eventcounters
|
|
for (var type in events) result.meta.events[type + 's'] = 0;
|
|
|
|
// add a point to result, if at least one event occured on the current line
|
|
turfMeta.featureEach(result.track, function(line) {
|
|
if (prevLine !== null) {
|
|
var point = turf.point(line.geometry.coordinates[1], { events: [] });
|
|
|
|
for (var type in events) {
|
|
if (events[type].fn(line, prevLine)) {
|
|
if (events[type].coordIndex === 1) point.properties.events.push(type);
|
|
else if (prevPoint) prevPoint.properties.events.push(type);
|
|
result.meta.events[type + 's']++;
|
|
}
|
|
}
|
|
eventPoints.push(point);
|
|
}
|
|
prevLine = line;
|
|
prevPoint = point;
|
|
});
|
|
|
|
result.events = eventPoints.filter(function(val, i, arr) {
|
|
return (val.properties.events.length > 0);
|
|
})
|
|
|
|
done(null, result);
|
|
}
|
|
|
|
function roundFloat(val, decimals) {
|
|
var digits = decimals === undefined ? 1e3 : Number('1e' + decimals);
|
|
return Math.round(val * digits) / digits;
|
|
}
|