We need to develop a system that synchronizes GPS data with a corresponding video recording, ensuring precise mapping between video frames and real-world locations. The system must extract a video frame every 5 meters of actual movement, even in challenging conditions such as GPS loss (e.g., tunnels) or vehicle stops (e.g., red lights).
GPS timestamps must be aligned with video timestamps.
If GPS timestamps and video timestamps don’t match exactly, the system should interpolate positions using previous and next GPS samples.
Extract one video frame every 5 meters of real-world distance traveled.
When GPS data is unavailable (e.g., in tunnels):
Estimate position using linear interpolation from the last known valid GPS points before and after the gap.
If there’s no future point (still inside the tunnel), use dead reckoning:
Continue movement estimation based on last known speed and direction.
If speed is near zero, assume the vehicle is stationary.
Edge Cases
Handle GPS accuracy degradation dynamically (e.g., discard data with poor accuracy).
Handle missing GPS updates gracefully.
Avoid duplicate extraction if two consecutive frames correspond to the same position.
This is my code. Every GPS singals come I use newPosition to store some data
public newPosition = (position: GeoPosition) => {
const EPSILON = 0.5;
const {
timestamp,
coords: {speed, accuracy, latitude, longitude},
} = position;
let magnetometerValueKeeper = this.magnetometerValue;
if (!speed) {
return null;
}
/**
* accuracy of gps signal must to be under @this ACCURACY_FACTOR
*/
if (accuracy > this.ACCURACY_FACTOR) {
return showNotification({
type: 'warning',
message: `${t('CameraCapture.weakSignal')}...`,
autoHide: false,
});
}
/**
* hiding @accuracy related message if was shown.
*/
hideMessage();
/**
* updating speed meter value if accuracy has acceptable value
*/
this.dispatchGPS({latitude, longitude, speed});
const speedToKM = Math.round(speed * 3.6);
/**
* if speed or @this pastPosition not be exist or it hasn't acceptable range
* for speed, current piece isn't valuable.
*/
// if (!this.pastPosition) {
// this.pastPosition = position;
// return;
// }
/**
* if first position was been received, must to assign difference milisecond to
* @differenceMS
*/
if (!this.differenceMS) {
const currentDate = new Date().getTime();
this.differenceMS = currentDate - this.videoStartedAt;
}
/**
* if first position wasn't been received, must to assign current position to past position.
*/
if (!this.pastPosition) {
this.pastPosition = position;
}
/**
* calculating metric distance between current and past position
*/
const distanceDelta = distanceCalculator.getDistance(
this.pastPosition.coords,
{
latitude,
longitude,
},
);
if (distanceDelta < EPSILON) {
return;
}
// if (distanceDelta <= this.distance) {
// this.dispatchGPS({latitude, longitude, speed});
// if (this.isFirstPosition) {
// this.isFirstPosition = false;
// } else {
// return;
// }
// }
this.survayTotal += distanceDelta;
this.distances.push(this.survayTotal);
this.dispatchCameraCapturing({survayTotal: this.survayTotal});
const newTime =
/**
* last submitted time or zero if @this times was been empty.
*/
(this.times[this.times.length - 1] || this.differenceMS) +
/**
* difference time between current and past timestamp as milliseconds.
*/
(timestamp - this.pastPosition.timestamp);
this.times.push(newTime);
this.accuracies.push(accuracy);
this.velocities.push(speedToKM);
this.latitudes.push(latitude);
this.longitudes.push(longitude);
this.magnetometerValues.push(magnetometerValueKeeper);
this.pastPosition = position;
delete position.mocked;
delete position.provider;
delete position.coords.altitudeAccuracy;
this.originalPositions.push(position);
};
When recoding video finished, we prepareData onRecordingFinished
public prepareData = async () => {
try {
if (Platform.OS === 'ios') {
showNotification({
type: 'info',
autoHide: false,
message: t('CameraCapture.inStoringData'),
});
}
if (this.video?.duration) {
console.log('danger::video?.duration', this.video.duration);
// Recalculate actual video start time
const actualVideoStartTime =
this.videoEndedAt - this.video.duration * 1000;
console.log('danger::actualVideoStartTime', actualVideoStartTime);
console.log('danger::videoStartedAt', this.videoStartedAt);
// Recalculate all times based on actual video start
const timingOffset = actualVideoStartTime - this.videoStartedAt;
this.times = this.times.map(time => time + timingOffset);
console.log('danger::times::after', this.times);
console.log('danger::timingOffset::after', timingOffset);
// Update videoStartedAt to actual time
this.videoStartedAt = actualVideoStartTime;
}
/**
* generating series by @this distance factor until reaching out to @this survayTotal
*/
const distancesSeries: number[] = [];
// [10, 50, 90, 110, 112, 180, 200, 1000]
// this.distance = 5;
// this.survayTotal = 1000;
// distancesSeries = [0,5,10,15,20,..., 995, 1000];
for (let i = 0; i <= this.survayTotal; i += this.distance) {
distancesSeries.push(i);
}
/**
* interpolating new latitudes by @distancesSeries and cumulative values of distances
*/
// [0,5,10,15,20,..., 995, 1000]
const newLatitudes = Interpolation.lerp(
distancesSeries,
this.distances,
this.latitudes,
);
/**
* interpolating new longitudes by @distancesSeries and cumulative values of distances
*/
const newLongitudes = Interpolation.lerp(
distancesSeries,
this.distances,
this.longitudes,
);
/**
* interpolating new milliseconds in order to reference to specific frame of captured
* video by @distancesSeries and cumulative values of distances
*/
this.currentMsList = Interpolation.lerp(
distancesSeries,
this.distances,
this.times,
).map(ms => {
return Math.trunc(ms);
});
/**
* interpolating new velocities by @distancesSeries and cumulative values of distances
*/
const newVelocities = Interpolation.lerp(
distancesSeries,
this.distances,
this.velocities,
);
/**
* interpolating new accuracies by @distancesSeries and cumulative values of distances
*/
const newAccuracies = Interpolation.lerp(
distancesSeries,
this.distances,
this.accuracies,
);
/**
* interpolating new orientations by @distancesSeries and cumulative values of distances
*/
const newOrientations = Interpolation.lerp(
distancesSeries,
this.distances,
this.magnetometerValues,
).map(mValue => {
return this.setOrientation(mValue);
});
}
We use lerp for interpolation
class Interpolation {
public findIntervalBorderIndex = (
point: number,
intervals: number[],
useRightBorder?: boolean,
) => {
//If point is beyond given intervals
if (point < intervals[0]) {
return 0;
}
if (point > intervals[intervals.length - 1]) {
return intervals.length - 1;
}
/**
* if point is inside interval, start searching on a full range of intervals.
*/
let indexOfNumberToCompare,
leftBorderIndex = 0,
rightBorderIndex = intervals.length - 1;
/**
* reduce searching range till it find an interval point belongs to using binary search
*/
while (rightBorderIndex - leftBorderIndex !== 1) {
indexOfNumberToCompare =
leftBorderIndex + Math.floor((rightBorderIndex - leftBorderIndex) / 2);
point >= intervals[indexOfNumberToCompare]
? (leftBorderIndex = indexOfNumberToCompare)
: (rightBorderIndex = indexOfNumberToCompare);
}
return useRightBorder ? rightBorderIndex : leftBorderIndex;
};
/**
* evaluates y-value at given x point for line that passes
* through the points (x0,y0) and (y1,y1)
* @param value
* @param x0
* @param y0
* @param x1
* @param y1
*/
public linearInterpolation = (
value: number,
x0: number,
y0: number,
x1: number,
y1: number,
) => {
let a = (y1 - y0) / (x1 - x0);
let b = -a * x0 + y0;
return a * value + b;
};
/**
* evaluates interpolating line/lines at the set of numbers
* or at a single number for the function y=f(x)
*
* number or set of numbers for which polynomial is calculated
* @param points
* @param valuesX set of distinct x values
* @param valuesY set of distinct y=f(x) values
* @param useRightBorder returns index of the right border of the interval
*/
public lerp = (
points: number | number[],
valuesX: number[],
valuesY: number[],
useRightBorder = false,
): number[] => {
let results: number[] = [];
if (typeof points === 'number') {
points = [points];
}
points.forEach(point => {
let index = this.findIntervalBorderIndex(point, valuesX, useRightBorder);
if (index === valuesX.length - 1) {
--index;
}
let interpolated = this.linearInterpolation(
point,
valuesX[index],
valuesY[index],
valuesX[index + 1],
valuesY[index + 1],
);
results.push(interpolated);
});
return results;
};
}
public startRecordVideo = async () => {
try {
enter code here
}
My problem is when staring video has a long stop in during video when stop again(traffic light) extract frame based on currentMsList are wrong. We have several repeated frame behind traffic light.