1.理论依据:
连续的GPS点中,静止不动的一段或者多段这样的点序列。把这些点序列处理成一个点,也就是拿这些序列的第一个点即可。理论依据如下:从第二个点开始,每个点都和第一个点进行距离计算和比较。至少比较N个点。当百分之M的点距离在X内的话,就继续计算比对。直到后面Z个连续点超出X,那么第一个点到超出X的第一个点,也就是Z的第一个点之前的点认为是静止状态。则这个序列被认为是静止的GPS序列,取停止点的中心点即可。后面从继续从Z的第一个点循环做这件事。若点不足N个,则不做任何处理。
2.降噪原理
在连续的GPS序列中,假设前半段静止,后半段运动。则后序所有点和第一个点进行距离计算后和时间的关系图如上。静止的点拟合后的结果一定是平行于X轴的,后半段的拟合在匀速的情况下,是一个0<角度<90度的线(变速运动的话时拟合的是曲线)
3.计算中心点
4.计算中心点核心代码(降噪)
javascript">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.识别停留点序列核心代码
javascript">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个点超出距离阈值Xwhile (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.完整方法
javascript">// 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个点超出距离阈值Xwhile (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
};