Skip to content

Instantly share code, notes, and snippets.

@lam0819
Last active February 3, 2025 10:30
Show Gist options
  • Save lam0819/3d30081d9d9bafb0c7992cdaaac059a9 to your computer and use it in GitHub Desktop.
Save lam0819/3d30081d9d9bafb0c7992cdaaac059a9 to your computer and use it in GitHub Desktop.
GPSTracker
class GPSTracker {
constructor(gpsData = [], geofenceBounds = null, requirements = { minDistance: 1, minSpeed: 5 }) {
this.gpsData = gpsData;
this.geofenceBounds = geofenceBounds;
this.requirements = requirements;
}
// Haversine 距离计算
static haversine(lat1, lng1, lat2, lng2) {
const R = 6371e3; // 地球半径(米)
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lng2 - lng1) * Math.PI / 180;
const a = Math.sin(Δφ / 2) ** 2 + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
// 计算总指标
calculateMetrics() {
if (this.gpsData.length < 2) return { error: "至少需要两个数据点" };
let totalDistance = 0, totalTime = 0;
for (let i = 1; i < this.gpsData.length; i++) {
const prev = this.gpsData[i - 1];
const curr = this.gpsData[i];
totalDistance += GPSTracker.haversine(prev.lat, prev.lng, curr.lat, curr.lng);
totalTime += (new Date(curr.time) - new Date(prev.time)) / 1000; // 秒
}
return {
totalDistance: totalDistance / 1000, // 转为公里
totalTime: totalTime / 3600, // 转为小时
avgSpeed: (totalDistance / 1000) / (totalTime / 3600) || 0
};
}
// 验证是否达标
checkRequirements() {
const metrics = this.calculateMetrics();
if (metrics.error) return metrics;
return {
isDistanceMet: metrics.totalDistance >= this.requirements.minDistance,
isSpeedMet: metrics.avgSpeed >= this.requirements.minSpeed,
...metrics
};
}
// 验证是否在围栏内
validateGeofence() {
if (!this.geofenceBounds) return { error: "未定义地理围栏" };
const { minLat, maxLat, minLng, maxLng } = this.geofenceBounds;
return this.gpsData.every(point =>
point.lat >= minLat &&
point.lat <= maxLat &&
point.lng >= minLng &&
point.lng <= maxLng
);
}
// 新增:检测异常停留
detectStops(maxStopDistance = 10, maxStopTime = 300) { // 默认:10米内停留>5分钟
const stops = [];
let currentStop = null;
for (let i = 1; i < this.gpsData.length; i++) {
const prev = this.gpsData[i - 1];
const curr = this.gpsData[i];
const distance = GPSTracker.haversine(prev.lat, prev.lng, curr.lat, curr.lng);
const timeDiff = (new Date(curr.time) - new Date(prev.time)) / 1000;
// 发现停留开始
if (distance <= maxStopDistance && timeDiff > maxStopTime) {
if (!currentStop) {
currentStop = {
start: prev.time,
location: { lat: prev.lat, lng: prev.lng },
duration: 0
};
}
currentStop.duration += timeDiff;
currentStop.end = curr.time;
} else if (currentStop) {
// 停留结束
stops.push({
...currentStop,
location: this._averageLocation([prev, curr])
});
currentStop = null;
}
}
return stops;
}
// 新增:分段配速分析
analyzeSegments() {
const segments = [];
for (let i = 1; i < this.gpsData.length; i++) {
const prev = this.gpsData[i - 1];
const curr = this.gpsData[i];
const distance = GPSTracker.haversine(prev.lat, prev.lng, curr.lat, curr.lng) / 1000;
const time = (new Date(curr.time) - new Date(prev.time)) / 1000 / 60; // 分钟
segments.push({
pace: time / distance,
speed: distance / (time / 60),
start: prev.time,
end: curr.time
});
}
return segments;
}
// 辅助方法:计算平均坐标
_averageLocation(points) {
const sum = points.reduce((acc, p) => {
acc.lat += p.lat;
acc.lng += p.lng;
return acc;
}, { lat: 0, lng: 0 });
return {
lat: sum.lat / points.length,
lng: sum.lng / points.length
};
}
// 更新后的总结果报告
getResult() {
return {
requirements: this.checkRequirements(),
geofence: this.validateGeofence(),
stops: this.detectStops(),
segments: this.analyzeSegments()
};
}
}
// 示例数据(替换为你的实际数据)
const myGPSData = [
{ lat: 22.2823, lng: 114.1865, time: "2023-10-05T08:00:00Z" },
{ lat: 22.2826, lng: 114.1869, time: "2023-10-05T08:05:00Z" },
{ lat: 22.2818, lng: 114.1872, time: "2023-10-05T08:15:00Z" }
];
// 定义地理围栏(可选)
const victoriaParkBounds = {
minLat: 22.2810,
maxLat: 22.2836,
minLng: 114.1848,
maxLng: 114.1876
};
// 创建实例
const tracker = new GPSTracker(
myGPSData,
victoriaParkBounds,
{ minDistance: 1, minSpeed: 5 } // 目标:至少1公里,配速≥5km/h
);
// 获取完整报告
console.log("跑步验证结果:", tracker.getResult());
// 直接读取指标
const metrics = tracker.calculateMetrics();
console.log(`总距离: ${metrics.totalDistance.toFixed(2)} km`);
console.log(`平均速度: ${metrics.avgSpeed.toFixed(2)} km/h`);
// 控制台输出结果:
// 跑步验证结果: {
// requirements: {
// isDistanceMet: true,
// isSpeedMet: false,
// totalDistance: 0.82,
// totalTime: 0.25,
// avgSpeed: 3.28
// },
// geofence: true
// }
// 检测所有超过3分钟的停留
console.log("异常停留:", tracker.detectStops(10, 180));
const dataWithStops = [
{ lat: 22.2823, lng: 114.1865, time: "2023-10-05T08:00:00Z" },
{ lat: 22.2823, lng: 114.1865, time: "2023-10-05T08:05:00Z" }, // 原地停留5分钟
{ lat: 22.2823, lng: 114.1866, time: "2023-10-05T08:10:00Z" }, // 轻微移动
{ lat: 22.2823, lng: 114.1865, time: "2023-10-05T08:15:00Z" }, // 再次停留5分钟
];
const tracker = new GPSTracker(dataWithStops);
// 输出示例:
// [
// {
// start: "2023-10-05T08:00:00Z",
// end: "2023-10-05T08:05:00Z",
// duration: 300,
// location: { lat: 22.2823, lng: 114.1865 }
// },
// {
// start: "2023-10-05T08:10:00Z",
// end: "2023-10-05T08:15:00Z",
// duration: 300,
// location: { lat: 22.2823, lng: 114.18655 }
// }
// ]
// 查看分段配速
console.log("分段配速:", tracker.analyzeSegments());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment