standstill
Version:
Find locations where there has been no movement, a stop, within a GeoJSON track, typically recorded from a GPS
181 lines (151 loc) • 7.49 kB
JavaScript
var test = require('tape'),
almostEqual = require('almost-equal'),
findstops = require('../.'),
geojsonTrack = require('./sample-geojson.json'),
geojsonTrack2 = require('./sample-geojson2.json'),
geojsonTrack3 = require('./sample-geojson3.json');
geojsonTrack2.properties.coordTimes = geojsonTrack2.properties.positionData.map(function(d) { return d.date; });
test('does not accept empty input', function(t) {
try {
findstops();
t.fail();
} catch (e) {
t.pass();
}
t.end();
});
test('does not accept non-LineString input', function(t) {
try {
findstops({type: 'Point'});
t.fail();
} catch (e) {
}
try {
findstops({type: 'Feature'});
t.fail();
} catch (e) {
}
try {
findstops({type: 'Feature', geometry: {type: 'Point'}});
t.fail();
} catch (e) {
}
t.end();
});
test('can find stops', function(t) {
var stops = findstops(geojsonTrack).stops;
t.equal(stops.features.length, 1);
var stop = stops.features[0];
t.ok(almostEqual(10.825, stop.geometry.coordinates[0], 5e-4), stop.geometry.coordinates[0]);
t.ok(almostEqual(59.921, stop.geometry.coordinates[1], 5e-4), stop.geometry.coordinates[1]);
t.end();
});
test('can ignore maxTimeGap', function(t) {
var stops = findstops(geojsonTrack, {maxTimeGap: 24 * 60 * 60 * 1000}).stops;
t.equal(stops.features.length, 4);
var stop = stops.features[3];
t.ok(almostEqual(10.825, stop.geometry.coordinates[0], 5e-4), stop.geometry.coordinates[0]);
t.ok(almostEqual(59.921, stop.geometry.coordinates[1], 5e-4), stop.geometry.coordinates[1]);
t.end();
});
test('routes have one timestamp per coordinate', function(t) {
var routes = findstops(geojsonTrack, {maxTimeGap: 24 * 60 * 60 * 1000}).routes,
r,
i;
t.ok(routes.features.length > 0, 'contains at least one route (' + routes.length + ')');
for (i = 0; i < routes.features.length; i++) {
r = routes.features[i];
t.ok(r.geometry.coordinates.length, r.properties.coordTimes.length,
'route ' + i + ' does not have one coordinate per timestamp');
}
t.end();
});
test('routes do not overlap', function(t) {
var routes = findstops(geojsonTrack, {maxTimeGap: 24 * 60 * 60 * 1000}).routes,
r1,
r2,
i;
t.ok(routes.features.length > 1, 'contains at least two routes (' + routes.length + ')');
for (i = 1; i < routes.features.length; i++) {
r1 = routes.features[i - 1];
r2 = routes.features[i];
t.ok(r2.properties.coordTimes[0] > r1.properties.coordTimes[r1.properties.coordTimes.length - 1],
'route ' + i + ' does not overlap route ' + (i - 1));
}
t.end();
});
test('routes and stops together are as long as original geojson #1', function(t) {
var ts = function(d) { return new Date(d).getTime(); },
d = findstops(geojsonTrack, {maxTimeGap: 24 * 60 * 60 * 1000}),
indataTime = ts(geojsonTrack.properties.coordTimes[geojsonTrack.properties.coordTimes.length - 1]) -
ts(geojsonTrack.properties.coordTimes[0]),
stopTime = d.stops.features.reduce(function(t, s) {
return t + ts(s.properties.endTime) - ts(s.properties.startTime);
}, 0),
routeTime = d.routes.features.reduce(function(t, r) {
return t + ts(r.properties.coordTimes[r.properties.coordTimes.length - 1]) - ts(r.properties.coordTimes[0]);
}, 0),
aggregatedTime = stopTime + routeTime;
t.ok(almostEqual(aggregatedTime, indataTime, 5 * 60 * 1000), aggregatedTime + ' should be similar to ' + indataTime +
' (diff is ' + Math.round((aggregatedTime - indataTime) / (60 * 1000)) + ' minutes)');
t.end();
});
test('routes and stops together are as long as original geojson #2', function(t) {
var ts = function(d) { return new Date(d).getTime(); },
d = findstops(geojsonTrack2, {maxTimeGap: 24 * 60 * 60 * 1000}),
indataTime = ts(geojsonTrack2.properties.coordTimes[geojsonTrack2.properties.coordTimes.length - 1]) -
ts(geojsonTrack2.properties.coordTimes[0]),
stopTime = d.stops.features.reduce(function(t, s) {
return t + ts(s.properties.endTime) - ts(s.properties.startTime);
}, 0),
routeTime = d.routes.features.reduce(function(t, r) {
return t + ts(r.properties.coordTimes[r.properties.coordTimes.length - 1]) - ts(r.properties.coordTimes[0]);
}, 0),
aggregatedTime = stopTime + routeTime;
t.ok(almostEqual(aggregatedTime, indataTime, 5 * 60 * 1000), aggregatedTime + ' should be similar to ' + indataTime +
' (diff is ' + Math.round((aggregatedTime - indataTime) / (60 * 1000)) + ' minutes)');
t.end();
});
test('routes and stops together are as long as original geojson #3 with low tolerance', function(t) {
var ts = function(d) { return new Date(d).getTime(); },
d = findstops(geojsonTrack3, {maxTimeGap: 24 * 60 * 60 * 1000, stopTolerance: 0.05}),
indataTime = ts(geojsonTrack3.properties.coordTimes[geojsonTrack3.properties.coordTimes.length - 1]) -
ts(geojsonTrack3.properties.coordTimes[0]),
stopTime = d.stops.features.reduce(function(t, s) {
return t + ts(s.properties.endTime) - ts(s.properties.startTime);
}, 0),
routeTime = d.routes.features.reduce(function(t, r) {
return t + ts(r.properties.coordTimes[r.properties.coordTimes.length - 1]) - ts(r.properties.coordTimes[0]);
}, 0),
aggregatedTime = stopTime + routeTime;
t.ok(almostEqual(aggregatedTime, indataTime, 5 * 60 * 1000), aggregatedTime + ' should be similar to ' + indataTime +
' (diff is ' + Math.round((aggregatedTime - indataTime) / (60 * 1000)) + ' minutes)');
t.end();
});
test('finds long stop with only two positions', function(t) {
var ts = function(d) { return new Date(d).getTime(); },
d = findstops(geojsonTrack3, {maxTimeGap: 24 * 60 * 60 * 1000, stopTolerance: 0.05});
t.ok(d.stops.features.some(function(s) {
return s.properties.startTime === '2016-02-02 12:05:26' &&
s.properties.endTime === '2016-02-02 13:06:39';
}), 'should have stop from 12:05 to 13:06');
t.end();
});
// test('finds stops and routes in slow-moving geojson #3', function(t) {
// var ts = function(d) { return new Date(d).getTime(); },
// d = findstops(geojsonTrack3, {maxTimeGap: 24 * 60 * 60 * 1000}),
// indataTime = ts(geojsonTrack3.properties.coordTimes[geojsonTrack3.properties.coordTimes.length - 1]) -
// ts(geojsonTrack3.properties.coordTimes[0]),
// stopTime = d.stops.features.reduce(function(t, s) {
// return t + ts(s.properties.endTime) - ts(s.properties.startTime);
// }, 0),
// routeTime = d.routes.features.reduce(function(t, r) {
// return t + ts(r.properties.coordTimes[r.properties.coordTimes.length - 1]) - ts(r.properties.coordTimes[0]);
// }, 0),
// aggregatedTime = stopTime + routeTime;
// t.ok(almostEqual(aggregatedTime, indataTime, 5 * 60 * 1000), aggregatedTime + ' should be similar to ' + indataTime +
// ' (diff is ' + Math.round((aggregatedTime - indataTime) / (60 * 1000)) + ' minutes)');
// t.equal(d.stops.features.length, 4);
// t.equal(d.routes.features.length, 6);
// t.end();
// });