Last active
February 3, 2025 10:30
-
-
Save lam0819/3d30081d9d9bafb0c7992cdaaac059a9 to your computer and use it in GitHub Desktop.
GPSTracker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 示例数据(替换为你的实际数据) | |
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