1.理论依据:
连续的GPS点中,静止不动的一段或者多段这样的点序列。把这些点序列处理成一个点,也就是拿这些序列的第一个点即可。理论依据如下:从第二个点开始,每个点都和第一个点进行距离计算和比较。至少比较N个点。当百分之M的点距离在X内的话,就继续计算比对。直到后面Z个连续点超出X,那么第一个点到超出X的第一个点,也就是Z的第一个点之前的点认为是静止状态。则这个序列被认为是静止的GPS序列,取停止点的中心点即可。后面从继续从Z的第一个点循环做这件事。若点不足N个,则不做任何处理。
2.降噪原理
在连续的GPS序列中,假设前半段静止,后半段运动。则后序所有点和第一个点进行距离计算后和时间的关系图如上。静止的点拟合后的结果一定是平行于X轴的,后半段的拟合在匀速的情况下,是一个0<角度<90度的线(变速运动的话时拟合的是曲线)
3.计算中心点
4.计算中心点核心代码(降噪)
function calculateGeographicalCenter(points) {
if (!points || points.length === 0) {
return null;
}
let xSum = 0;
let ySum = 0;
let zSum = 0;
points.forEach(point => {
const latRad = degreesToRadians(point.lat);
const lonRad = degreesToRadians(point.lon);
xSum += Math.cos(latRad) * Math.cos(lonRad);
ySum += Math.cos(latRad) * Math.sin(lonRad);
zSum += Math.sin(latRad);
});
const numPoints = points.length;
const xAvg = xSum / numPoints;
const yAvg = ySum / numPoints;
const zAvg = zSum / numPoints;
const lonAvg = Math.atan2(yAvg, xAvg);
const hyp = Math.sqrt(xAvg * xAvg + yAvg * yAvg);
const latAvg = Math.atan2(zAvg, hyp);
return {
lat: radiansToDegrees(latAvg),
lon: radiansToDegrees(lonAvg)
};
}
function degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
function radiansToDegrees(radians) {
return radians * (180 / Math.PI);
}
5.识别停留点序列核心代码
function calculateDistance(point1, point2) {
// 使用Haversine公式计算两个GPS点之间的距离
const R = 6371e3; // 地球半径,单位为米
const lat1 = point1.lat * Math.PI / 180; // 第一个点的纬度转换为弧度
const lat2 = point2.lat * Math.PI / 180; // 第二个点的纬度转换为弧度
const deltaLat = (point2.lat - point1.lat) * Math.PI / 180; // 纬度差转换为弧度
const deltaLon = (point2.lon - point1.lon) * Math.PI / 180; // 经度差转换为弧度
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); // Haversine公式的中间变量
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // Haversine公式的另一个中间变量
return R * c; // 计算最终距离,单位为米
}
function findStaticPoints(gpsPoints, N, M, X, Z) {
const finalPoints = []; // 存储最终的轨迹点
let i = 0;
while (i < gpsPoints.length) {
let countWithinThreshold = 0; // 记录在距离阈值内的点的数量
let j = i + 1;
// 从第i个点开始,比较后续的N个点
for (; j < gpsPoints.length && j < i + N; j++) {
if (calculateDistance(gpsPoints[i], gpsPoints[j]) <= X) {
countWithinThreshold++; // 统计在距离阈值X内的点的数量
}
}
// 如果在N个点中有至少M%的点在距离阈值内
if (countWithinThreshold / N >= M / 100) {
let staticEndIndex = i + N; // 静止状态的结束索引初始值
// 检查后续点,直到找到连续Z个点超出距离阈值X
while (staticEndIndex < gpsPoints.length) {
let outOfThresholdCount = 0; // 记录超出阈值的点的数量
for (let k = staticEndIndex; k < staticEndIndex + Z && k < gpsPoints.length; k++) {
if (calculateDistance(gpsPoints[i], gpsPoints[k]) > X) {
outOfThresholdCount++;
}
}
// 如果连续Z个点中有Z个点超出距离阈值X,则认为静止状态结束
if (outOfThresholdCount >= Z) {
break;
}
staticEndIndex++;
}
finalPoints.push(gpsPoints[i]); // 将静止状态的第一个点加入结果数组
i = staticEndIndex; // 跳到静止状态结束的点继续处理
} else {
finalPoints.push(gpsPoints[i]); // 非静止状态点,直接加入结果数组
i++; // 不满足静止条件,继续检查下一个点
}
}
return finalPoints; // 返回处理后的轨迹点数组
}
6.完整方法
// N: 10, // 最少比较的点数
// M: 70, // 距离阈值内的点的百分比
// X: 35, // 距离阈值,单位为米
// Z: 10, // 判断静止状态结束的连续点数
const pointsResult: any = {
stopPoints: [], //记录所有停留中心点
finalPoints: [] // 存储最终的轨迹点
};
function clear() {
pointsResult.stopPoints = []
pointsResult.finalPoints = []
}
function findStaticPoints(gpsPoints, N = 10, M = 70, X = 50, Z = 10) {
clear()
let i = 0;
while (i < gpsPoints.length) {
let countWithinThreshold = 0; // 记录在距离阈值内的点的数量
let j = i + 1;
// 从第i个点开始,比较后续的N个点
for (; j < gpsPoints.length && j < i + N; j++) {
if (calculateDistance(gpsPoints[i], gpsPoints[j]) <= X) {
countWithinThreshold++; // 统计在距离阈值X内的点的数量
}
}
// 如果在N个点中有至少M%的点在距离阈值内
if (countWithinThreshold / N >= M / 100) {
let staticEndIndex = i + N; // 静止状态的结束索引初始值
const staticPointsSequence = gpsPoints.slice(i, i + N);
// 检查后续点,直到找到连续Z个点超出距离阈值X
while (staticEndIndex < gpsPoints.length) {
let outOfThresholdCount = 0; // 记录超出阈值的点的数量
for (let k = staticEndIndex; k < staticEndIndex + Z && k < gpsPoints.length; k++) {
if (calculateDistance(gpsPoints[i], gpsPoints[k]) > X) {
outOfThresholdCount++;
}
}
// 如果连续Z个点中有Z个点超出距离阈值X,则认为静止状态结束
if (outOfThresholdCount >= Z) {
break;
}
staticPointsSequence.push(gpsPoints[staticEndIndex]);
staticEndIndex++;
}
const centerPoint: any = calculateGeographicalCenter(staticPointsSequence);
pointsResult.finalPoints.push(centerPoint); // 将中心点加入结果数组
pointsResult.stopPoints.push(centerPoint);
i = staticEndIndex; // 跳到静止状态结束的点继续处理
} else {
pointsResult.finalPoints.push(gpsPoints[i]); // 非静止状态点,直接加入结果数组
i++; // 不满足静止条件,继续检查下一个点
}
}
return pointsResult; // 返回处理后的轨迹点数组
}
function calculateDistance(point1, point2) {
// 使用Haversine公式计算两个GPS点之间的距离
const R = 6371e3; // 地球半径,单位为米
const lat1 = point1.latitude1 * Math.PI / 180; // 第一个点的纬度转换为弧度
const lat2 = point2.latitude1 * Math.PI / 180; // 第二个点的纬度转换为弧度
const deltaLat = (point2.latitude1 - point1.latitude1) * Math.PI / 180; // 纬度差转换为弧度
const deltaLon = (point2.longitude1 - point1.longitude1) * Math.PI / 180; // 经度差转换为弧度
const a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); // Haversine公式的中间变量
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // Haversine公式的另一个中间变量
return R * c; // 计算最终距离,单位为米
}
function calculateGeographicalCenter(points) {
if (!points || points.length === 0) {
return null;
}
let xSum = 0;
let ySum = 0;
let zSum = 0;
points.forEach(point => {
const latRad = degreesToRadians(point.latitude1);
const lonRad = degreesToRadians(point.longitude1);
xSum += Math.cos(latRad) * Math.cos(lonRad);
ySum += Math.cos(latRad) * Math.sin(lonRad);
zSum += Math.sin(latRad);
});
const numPoints = points.length;
const xAvg = xSum / numPoints;
const yAvg = ySum / numPoints;
const zAvg = zSum / numPoints;
const lonAvg = Math.atan2(yAvg, xAvg);
const hyp = Math.sqrt(xAvg * xAvg + yAvg * yAvg);
const latAvg = Math.atan2(zAvg, hyp);
return {
latitude1: radiansToDegrees(latAvg),
longitude1: radiansToDegrees(lonAvg)
};
}
function degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
function radiansToDegrees(radians) {
return radians * (180 / Math.PI);
}
export {
findStaticPoints
};