132 lines
3.8 KiB
JavaScript
132 lines
3.8 KiB
JavaScript
/**
|
||
* 轨迹点解析与清洗:兼容小程序 map 组件 polyline,减少乱线/飘移。
|
||
* - 仅保留合法经纬度;支持 lat/lng 字段别名
|
||
* - 修正常见的经纬度字段写反(|lat|>90)
|
||
* - 按时间稳定排序
|
||
* - 剔除明显 GPS 飞点(短时超高速跳变)
|
||
*/
|
||
|
||
function haversineMeters(lat1, lon1, lat2, lon2) {
|
||
const R = 6371000
|
||
const φ1 = (lat1 * Math.PI) / 180
|
||
const φ2 = (lat2 * Math.PI) / 180
|
||
const Δφ = ((lat2 - lat1) * Math.PI) / 180
|
||
const Δλ = ((lon2 - lon1) * Math.PI) / 180
|
||
const a =
|
||
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||
return R * c
|
||
}
|
||
|
||
/**
|
||
* 从单行日志解析纬度、经度(已修正写反)
|
||
*/
|
||
export function parseTrackLatLng(raw) {
|
||
if (!raw || typeof raw !== 'object') return null
|
||
let lat = parseFloat(raw.latitude != null ? raw.latitude : raw.lat)
|
||
let lng = parseFloat(
|
||
raw.longitude != null ? raw.longitude : raw.lng != null ? raw.lng : raw.lon
|
||
)
|
||
if (!isFinite(lat) || !isFinite(lng)) return null
|
||
if (lat === 0 && lng === 0) return null
|
||
// 设备/接口偶发把经度写进 latitude(中国境内经度约 70~140,|lat|会>90)
|
||
if (Math.abs(lat) > 90) {
|
||
const t = lat
|
||
lat = lng
|
||
lng = t
|
||
}
|
||
if (Math.abs(lat) > 90 || Math.abs(lng) > 180) return null
|
||
return { latitude: lat, longitude: lng }
|
||
}
|
||
|
||
/**
|
||
* 去掉短时超大跳跃点(km/h)
|
||
*/
|
||
export function filterGpsSpikes(points, maxSpeedKmH) {
|
||
if (!points || points.length < 2) return points || []
|
||
const out = [points[0]]
|
||
for (let i = 1; i < points.length; i++) {
|
||
const prev = out[out.length - 1]
|
||
const cur = points[i]
|
||
const tPrev = new Date(prev.time).getTime()
|
||
const tCur = new Date(cur.time).getTime()
|
||
const dt = Math.max(1, (tCur - tPrev) / 1000)
|
||
if (!isFinite(dt) || dt <= 0) {
|
||
out.push(cur)
|
||
continue
|
||
}
|
||
const d = haversineMeters(
|
||
prev.latitude,
|
||
prev.longitude,
|
||
cur.latitude,
|
||
cur.longitude
|
||
)
|
||
const speedKmh = (d / dt) * 3.6
|
||
if (speedKmh > maxSpeedKmH) continue
|
||
out.push(cur)
|
||
}
|
||
return out
|
||
}
|
||
|
||
/**
|
||
* @param {Array} rows 接口返回的 locationLog 列表
|
||
* @param {(row: object) => object} mapExtra 合并到轨迹点的额外字段(不含 latitude/longitude/time)
|
||
* @param {{ maxSpeedKmH?: number }} options
|
||
*/
|
||
export function normalizeLocationLogList(rows, mapExtra, options) {
|
||
const maxSpeedKmH = (options && options.maxSpeedKmH) || 180
|
||
if (!Array.isArray(rows) || rows.length === 0) return []
|
||
const mapped = []
|
||
for (let i = 0; i < rows.length; i++) {
|
||
const row = rows[i]
|
||
const ll = parseTrackLatLng(row)
|
||
if (!ll) continue
|
||
const extra = typeof mapExtra === 'function' ? mapExtra(row) || {} : {}
|
||
mapped.push({
|
||
...ll,
|
||
...extra,
|
||
time: row.at,
|
||
_sortIndex: i,
|
||
})
|
||
}
|
||
mapped.sort((a, b) => {
|
||
const ta = new Date(a.time).getTime()
|
||
const tb = new Date(b.time).getTime()
|
||
const na = isNaN(ta)
|
||
const nb = isNaN(tb)
|
||
if (na && nb) return a._sortIndex - b._sortIndex
|
||
if (na) return 1
|
||
if (nb) return -1
|
||
if (ta !== tb) return ta - tb
|
||
return a._sortIndex - b._sortIndex
|
||
})
|
||
const deduped = []
|
||
for (let j = 0; j < mapped.length; j++) {
|
||
const p = mapped[j]
|
||
const last = deduped[deduped.length - 1]
|
||
if (
|
||
last &&
|
||
last.latitude === p.latitude &&
|
||
last.longitude === p.longitude &&
|
||
last.time === p.time
|
||
) {
|
||
continue
|
||
}
|
||
deduped.push(p)
|
||
}
|
||
const filtered = filterGpsSpikes(deduped, maxSpeedKmH)
|
||
return filtered.map(({ _sortIndex, ...rest }) => rest)
|
||
}
|
||
|
||
/**
|
||
* 小程序 map.polyline.points 仅传经纬度,避免多余字段干扰原生解析
|
||
*/
|
||
export function toPolylinePointList(trackPoints) {
|
||
if (!trackPoints || !trackPoints.length) return []
|
||
return trackPoints.map((p) => ({
|
||
latitude: p.latitude,
|
||
longitude: p.longitude,
|
||
}))
|
||
}
|