/** * 轨迹点解析与清洗:兼容小程序 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, })) }