Skip to content

Commit bd7eace

Browse files
committed
fix: Avoid start/end transitions overlap
Consider this scenario: - A location point P1 is produced at 12:00:00 (T1), the last of a trip. - An upload is done, with the stop transitions set at T1 + 1s = 12:00:01 - P2 is produced at 12:00:01 (T2), the first of new trip - At upload time, the start transitions will be set at T2 - 1s = 12:00:00 Then, the start transition timestamp of the new trip will be before the stop transition of the previous trip, which is not possible, and will be discarded by the server, which will result in a missing trip. To solve this, we now add/substract 1ms to the transitions w.r.t. to the first or last location point. This should prevent overlapping transitions, as long as locations between 2 trips are not produced in the same ms
1 parent 4494e24 commit bd7eace

File tree

6 files changed

+87
-20
lines changed

6 files changed

+87
-20
lines changed

src/app/domain/geolocation/tracking/consts.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const MIN_SPEED_BETWEEN_DISTANT_POINTS = 0.5 // In m/s. Note the average
3535
export const MAX_DOCS_PER_BATCH = 30000 // Should be less than 10 MB. Should never reach 15 MB.
3636
export const MIN_DISTANCE_TO_USE_LAST_POINT = 50 // In meters.
3737
export const MAX_DISTANCE_TO_USE_LAST_POINT = 6000 // In meters. This value is quite high, but we noticed on iOS + Android that it can take this far to wake up when network data is disabled
38+
export const TRANSITION_TIME_SHIFT = 0.01 // In seconds. This is useful to ensure that start transitions are set just before the trip start and just after the trip end
3839

3940
// All speed values in m/s
4041
export const AVG_WALKING_SPEED = 1.34

src/app/domain/geolocation/tracking/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ export const handleConnectivityChange = async event => {
268268
* @property {number} [untilTs] - Until when the locations points should be fetched. Default is 0
269269
* @property {boolean} [force] - Whether or not the upload should be forced
270270
*
271-
* @param {Params} - The method parmas
271+
* @param {Params} - The method params
272+
* @returns {Promise<number>} The number of uploaded tracking data points
272273
*/
273274
export const startOpenPathUploadAndPipeline = async ({
274275
untilTs = 0,

src/app/domain/geolocation/tracking/tracking.js

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import {
3030
UNKNOWN_ACTIVITY,
3131
VEHICLE_ACTIVITY,
3232
WALKING_ACTIVITY,
33-
AVG_WALKING_SPEED
33+
AVG_WALKING_SPEED,
34+
TRANSITION_TIME_SHIFT
3435
} from '/app/domain/geolocation/tracking/consts'
3536

3637
/**
@@ -50,7 +51,7 @@ export const createDataBatch = (locations, nRun, maxBatchSize) => {
5051
* @param {Array<Location>} locations - The location points to upload
5152
* @param {string} user - The openpath user id
5253
* @param {{boolean}} - Additional params
53-
* @returns {number} The number of uploaded tracking data
54+
* @returns {Promise<number>} The number of uploaded tracking data
5455
*/
5556
export const uploadTrackingData = async (
5657
locations,
@@ -143,7 +144,10 @@ const prepareMotionData = async ({ locations, lastUploadedPoint }) => {
143144
new Date(1000 * (getTs(point) - 1)) +
144145
's'
145146
)
146-
await addStartTransitions(contentToUpload, getTs(point) - 1)
147+
await addStartTransitions(
148+
contentToUpload,
149+
getTs(point) - TRANSITION_TIME_SHIFT
150+
)
147151
} else {
148152
const deltaT = getTs(point) - getTs(previousPoint)
149153
if (deltaT > MAX_TEMPORAL_DELTA) {
@@ -160,10 +164,17 @@ const prepareMotionData = async ({ locations, lastUploadedPoint }) => {
160164
// Force a stop/start transition when the temporal delta is too high
161165
// Note forcing the transition won't automatically create a new trip, typically a long-distance
162166
// flight of 12h+ should behave as one trip, even with this transition.
163-
await addStopTransitions(contentToUpload, getTs(previousPoint) + 1, {
164-
withForceStop: true
165-
})
166-
await addStartTransitions(contentToUpload, getTs(point) - 1)
167+
await addStopTransitions(
168+
contentToUpload,
169+
getTs(previousPoint) + TRANSITION_TIME_SHIFT,
170+
{
171+
withForceStop: true
172+
}
173+
)
174+
await addStartTransitions(
175+
contentToUpload,
176+
getTs(point) - TRANSITION_TIME_SHIFT
177+
)
167178
} else if (deltaT > LARGE_TEMPORAL_DELTA) {
168179
const distanceM = getDistanceFromLatLonInM(previousPoint, point)
169180
Log('Spatial distance between points : ' + distanceM)
@@ -172,10 +183,17 @@ const prepareMotionData = async ({ locations, lastUploadedPoint }) => {
172183

173184
if (speed < MIN_SPEED_BETWEEN_DISTANT_POINTS) {
174185
Log('Very slow speed: force transition')
175-
await addStopTransitions(contentToUpload, getTs(previousPoint) + 1, {
176-
withForceStop: true
177-
})
178-
await addStartTransitions(contentToUpload, getTs(point) - 1)
186+
await addStopTransitions(
187+
contentToUpload,
188+
getTs(previousPoint) + TRANSITION_TIME_SHIFT,
189+
{
190+
withForceStop: true
191+
}
192+
)
193+
await addStartTransitions(
194+
contentToUpload,
195+
getTs(point) - TRANSITION_TIME_SHIFT
196+
)
179197
// Here we forced a hard stop transition. This typically happens when the tracking was lost for
180198
// a significant time, for a significant distance, but with a speed not significant enough to be
181199
// considered as a continuing trip.
@@ -200,7 +218,10 @@ const prepareMotionData = async ({ locations, lastUploadedPoint }) => {
200218
const lastStopTransitionTs = await getLastStopTransitionTs()
201219
if (lastStopTransitionTs >= lastStartTransitionTs) {
202220
Log('Based on timestamps, this is a new trip')
203-
await addStartTransitions(contentToUpload, getTs(point) - 1)
221+
await addStartTransitions(
222+
contentToUpload,
223+
getTs(point) - TRANSITION_TIME_SHIFT
224+
)
204225
}
205226
}
206227

@@ -223,9 +244,13 @@ const prepareMotionData = async ({ locations, lastUploadedPoint }) => {
223244
// The stop transition might be missed?
224245
const deltaLastPoint = Date.now() / 1000 - lastPointTs
225246
Log('Delta last point : ' + deltaLastPoint)
226-
await addStopTransitions(contentToUpload, lastPointTs + 1, {
227-
withForceStop: false
228-
})
247+
await addStopTransitions(
248+
contentToUpload,
249+
lastPointTs + TRANSITION_TIME_SHIFT,
250+
{
251+
withForceStop: false
252+
}
253+
)
229254

230255
return contentToUpload
231256
}
@@ -252,10 +277,16 @@ export const createNewStartPoint = (previousPoint, nextPoint) => {
252277
nextPoint.coords.speed > 0 ? nextPoint.coords.speed : AVG_WALKING_SPEED
253278
// Calculate the time to subtract based on speed and distance
254279
const timeToSubtract = (distance / speed) * 1000
255-
const newTime = date.getTime() - timeToSubtract
256-
if (newTime > new Date(previousPoint.timestamp).getTime()) {
280+
let newTime = date.getTime() - timeToSubtract
281+
282+
// Check the new time is greater than the previous location point, plus 1s for safety.
283+
// Here, we want to prevent overlaping points, that might results in overlapping transitions.
284+
if (newTime > new Date(previousPoint.timestamp).getTime() + 1000) {
257285
// Subtract the time from the date
258-
date.setTime(date.getTime() - timeToSubtract)
286+
date.setTime(newTime)
287+
} else {
288+
newTime = date.getTime() - 1000
289+
date.setTime(newTime)
259290
}
260291
const newTimestamp = date.toISOString()
261292
const newPoint = { ...previousPoint, timestamp: newTimestamp }

src/app/domain/geolocation/tracking/tracking.spec.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,28 @@ describe('createNewStartPoint', () => {
255255
new Date(nextPoint.timestamp).getTime()
256256
)
257257
})
258+
it('should create starting point just before next point if too close from the previous point', () => {
259+
const prevPoint = {
260+
coords: {
261+
latitude: 45.766501482079,
262+
longitude: 4.828593684206228,
263+
speed: 4.66
264+
},
265+
timestamp: '2024-03-15T08:24:59'
266+
}
267+
const nextPoint = {
268+
coords: {
269+
latitude: 45.766841923882495,
270+
longitude: 4.82800307468357,
271+
speed: 1
272+
},
273+
timestamp: '2024-03-15T08:25:10'
274+
}
275+
const newPoint = createNewStartPoint(prevPoint, nextPoint)
276+
expect(new Date(newPoint.timestamp).getTime()).toEqual(
277+
new Date(nextPoint.timestamp).getTime() - 1000
278+
)
279+
})
258280
})
259281

260282
describe('inferMotionActivity', () => {

src/app/domain/geolocation/tracking/upload.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ const filterBadContent = contentToUpload => {
7878
})
7979
}
8080

81+
/**
82+
* Get local location points and upload them
83+
*
84+
* @typedef {object} Params
85+
* @property {number} [untilTs] - Until when the locations points should be fetched. Default is 0
86+
* @property {boolean} [force] - Whether or not the upload should be forced
87+
*
88+
* @param {string} user - The openpath user
89+
* @param {Params} - The method params
90+
* @returns {number} The number of uploaded tracking data points
91+
*/
8192
export const uploadData = async (user, { untilTs = 0, force = false } = {}) => {
8293
Log(`Starting upload process for user ${user} ${force ? 'forced' : ''}`)
8394

src/app/domain/geolocation/tracking/user.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ export const createOpenPathUserIfMissing = async user => {
6767
return uuid
6868
} catch (err) {
6969
// If the user actually exist, the creation will return the existing user
70-
Log(`Error when trying to get openpath user, let's create one`)
70+
Log(`Error when trying to get openpath user ${user}, let's create one`)
71+
Log(err)
7172
return createUser(user)
7273
}
7374
}

0 commit comments

Comments
 (0)