+92
| [ | ||
| [ | ||
| "2014-11-28 09:48:38", | ||
| "Recording starts", | ||
| 0 | ||
| ], | ||
| [ | ||
| "2014-11-28 09:48:38", | ||
| "Route #0 starts.", | ||
| 0 | ||
| ], | ||
| [ | ||
| "2014-11-28 10:00:05", | ||
| "Route #0 ends.", | ||
| 687000 | ||
| ], | ||
| [ | ||
| "2014-11-28 10:00:28", | ||
| "Stop #0 start.", | ||
| 23000 | ||
| ], | ||
| [ | ||
| "2014-11-28 10:41:19", | ||
| "Stop #0 ends.", | ||
| 2451000 | ||
| ], | ||
| [ | ||
| "2014-11-28 10:41:51", | ||
| "Route #1 starts.", | ||
| 32000 | ||
| ], | ||
| [ | ||
| "2014-11-28 11:40:54", | ||
| "Route #1 ends.", | ||
| 3543000 | ||
| ], | ||
| [ | ||
| "2014-11-28 11:42:14", | ||
| "Stop #1 start.", | ||
| 80000 | ||
| ], | ||
| [ | ||
| "2014-11-28 13:25:15", | ||
| "Stop #1 ends.", | ||
| 6181000 | ||
| ], | ||
| [ | ||
| "2014-11-28 13:25:41", | ||
| "Route #2 starts.", | ||
| 26000 | ||
| ], | ||
| [ | ||
| "2014-11-28 14:58:32", | ||
| "Route #2 ends.", | ||
| 5571000 | ||
| ], | ||
| [ | ||
| "2014-11-28 14:58:41", | ||
| "Stop #2 start.", | ||
| 9000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:46:38", | ||
| "Stop #2 ends.", | ||
| 2877000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:50:13", | ||
| "Route #3 starts.", | ||
| 215000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:50:54", | ||
| "Route #3 ends.", | ||
| 41000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:50:57", | ||
| "Stop #3 start.", | ||
| 3000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:57:11", | ||
| "Recording ends", | ||
| 374000 | ||
| ], | ||
| [ | ||
| "2014-11-28 15:57:11", | ||
| "Stop #3 ends.", | ||
| 0 | ||
| ] | ||
| ] |
+31
-30
@@ -21,11 +21,19 @@ var distance = require('turf-distance'), | ||
| handleCandidate = function(result) { | ||
| var c = result.stopCandidate; | ||
| var c = result.stopCandidate, | ||
| coordSum; | ||
| if (c) { | ||
| if (result.lastTimestamp - c.startTime >= filter.stopMinTime) { | ||
| result.stops.features.push(point([c.lngSum / c.numCoords, c.latSum / c.numCoords], { | ||
| startTime: c.startTime, | ||
| endTime: result.lastTimestamp | ||
| if (result.lastTimestamp - c.startTimeStamp >= filter.stopMinTime) { | ||
| coordSum = c.coords.reduce(function(center, c) { | ||
| center[0] += c[0]; | ||
| center[1] += c[1]; | ||
| return center; | ||
| }, [0, 0]); | ||
| result.stops.features.push(point([coordSum[0] / c.coords.length, coordSum[1] / c.coords.length], { | ||
| startTime: c.times[0], | ||
| endTime: c.times[c.times.length - 1] | ||
| })); | ||
| result.routes.features.push(linestring(result.currentRoute, {coordTimes: result.currentRouteTimes})); | ||
| result.currentRoute = []; | ||
| result.currentRouteTimes = []; | ||
@@ -45,30 +53,23 @@ return true; | ||
| if (result.lastPoint) { | ||
| if (!candidate) { | ||
| candidate = result.stopCandidate = { | ||
| startTimeStamp: timestamp, | ||
| coords: [], | ||
| times: [] | ||
| }; | ||
| } | ||
| candidate.coords.push(c); | ||
| candidate.times.push(t); | ||
| if (result.lastPoint && candidate) { | ||
| d = distance(p, result.lastPoint); | ||
| if (d > filter.stopTolerance || timestamp - result.lastTimestamp > filter.maxTimeGap) { | ||
| handleCandidate(result); | ||
| if (!handleCandidate(result)) { | ||
| result.currentRoute = result.currentRoute.concat(candidate.coords); | ||
| result.currentRouteTimes = result.currentRouteTimes.concat(candidate.times); | ||
| } | ||
| delete result.stopCandidate; | ||
| if (result.candidateRouteCoords.length) { | ||
| result.currentRoute = result.currentRoute.concat(result.candidateRouteCoords); | ||
| result.currentRouteTimes = result.currentRouteTimes.concat(result.candidateRouteTimes); | ||
| result.candidateRouteCoords = []; | ||
| result.candidateRouteTimes = []; | ||
| } | ||
| result.candidateRouteCoords.push(c); | ||
| result.candidateRouteTimes.push(t); | ||
| } else { | ||
| if (!candidate) { | ||
| candidate = result.stopCandidate = { | ||
| startTime: result.lastTimestamp, | ||
| lngSum: 0, | ||
| latSum: 0, | ||
| numCoords: 0 | ||
| }; | ||
| } | ||
| candidate.lngSum += c[0]; | ||
| candidate.latSum += c[1]; | ||
| candidate.numCoords++; | ||
| result.candidateRouteCoords.push(c); | ||
| result.candidateRouteTimes.push(t); | ||
| result.currentRoute.push(c); | ||
| result.currentRouteTimes.push(t); | ||
| } | ||
@@ -75,0 +76,0 @@ } |
+1
-1
| { | ||
| "name": "standstill", | ||
| "version": "1.0.0", | ||
| "version": "1.0.1", | ||
| "description": "Find locations where there has been no movement, a stop, within a GeoJSON track, typically recorded from a GPS", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+25
-3
@@ -1,4 +0,26 @@ | ||
| var findstops = require('./'); | ||
| track = require('./test/sample-geojson.json'); | ||
| var findstops = require('./'), | ||
| track = require('./test/sample-geojson.json'), | ||
| d = findstops(track, {maxTimeGap: 24 * 60 * 60 * 1000}), | ||
| stopStarts = d.stops.features.map(function(s, i) { | ||
| return [s.properties.startTime, 'Stop #' + i + ' start.']; | ||
| }), | ||
| stopEnds = d.stops.features.map(function(s, i) { | ||
| return [s.properties.endTime, 'Stop #' + i + ' ends.']; | ||
| }), | ||
| routeStarts = d.routes.features.map(function(s, i) { | ||
| return [s.properties.coordTimes[0], 'Route #' + i + ' starts.']; | ||
| }), | ||
| routeEnds = d.routes.features.map(function(s, i) { | ||
| return [s.properties.coordTimes[s.properties.coordTimes.length - 1], 'Route #' + i + ' ends.']; | ||
| }), | ||
| events = [ | ||
| [track.properties.coordTimes[0], 'Recording starts'], | ||
| [track.properties.coordTimes[track.properties.coordTimes.length - 1], 'Recording ends'] | ||
| ].concat(stopStarts).concat(stopEnds).concat(routeStarts).concat(routeEnds); | ||
| console.log(JSON.stringify(findstops(track, {maxTimeGap: 24 * 60 * 60 * 1000}), null, ' ')); | ||
| events.sort(function(a, b) { return a[0] < b[0] ? -1 : b[0] < a[0] ? 1 : 0; }); | ||
| events.forEach(function(e, i) { | ||
| e[2] = i > 0 ? new Date(e[0]).getTime() - new Date(events[i - 1][0]).getTime() : 0; | ||
| }); | ||
| console.log(JSON.stringify(events, null, ' ')); |
+52
-0
@@ -58,1 +58,53 @@ var test = require('tape'), | ||
| }); | ||
| 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', 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(); | ||
| }); |
50817
9.16%8
14.29%1926
8.81%