import { onBeforeUnmount, ref, type Ref } from 'vue';
|
import type { LngLat } from 'vue-tianditu2';
|
import { toStamp } from '../hooks/use-map';
|
|
export type TrackPoint = { longitude: number; latitude: number; time?: any; speed?: number };
|
|
export function useTrackPlayback(points: Ref<TrackPoint[]>) {
|
const playing = ref(false);
|
const speed = ref(5);
|
const progress = ref(0);
|
const playPos = ref<LngLat | null>(null);
|
let raf = 0;
|
let startedAt = 0;
|
let elapsed = 0;
|
|
function path(): LngLat[] {
|
return points.value
|
.filter(p => Number(p.longitude) && Number(p.latitude))
|
.map(p => [Number(p.longitude), Number(p.latitude)] as LngLat);
|
}
|
|
function duration() {
|
const list = points.value;
|
if (list.length < 2) return 8000;
|
const t0 = toStamp(list[0].time);
|
const t1 = toStamp(list[list.length - 1].time);
|
const real = t1 > t0 ? t1 - t0 : list.length * 2000;
|
return Math.max(real, 4000);
|
}
|
|
function at(ratio: number): LngLat | null {
|
const list = path();
|
if (!list.length) return null;
|
if (list.length === 1) return list[0];
|
const times = points.value.map((p, i) => toStamp(p.time) || i * 1000);
|
const t0 = times[0];
|
const t1 = times[times.length - 1];
|
const span = Math.max(t1 - t0, 1);
|
const target = t0 + span * ratio;
|
let i = 0;
|
while (i < times.length - 1 && times[i + 1] < target) i += 1;
|
const a = list[i];
|
const b = list[Math.min(i + 1, list.length - 1)];
|
const seg = Math.max(times[Math.min(i + 1, times.length - 1)] - times[i], 1);
|
const k = Math.min(Math.max((target - times[i]) / seg, 0), 1);
|
return [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k];
|
}
|
|
function tick(now: number) {
|
if (!playing.value) return;
|
const total = duration() / Math.max(speed.value, 1);
|
elapsed += now - startedAt;
|
startedAt = now;
|
const ratio = Math.min(elapsed / total, 1);
|
progress.value = Math.round(ratio * 100);
|
playPos.value = at(ratio);
|
if (ratio >= 1) {
|
playing.value = false;
|
return;
|
}
|
raf = requestAnimationFrame(tick);
|
}
|
|
function play() {
|
if (!points.value.length) return;
|
if (progress.value >= 100) {
|
progress.value = 0;
|
elapsed = 0;
|
}
|
playing.value = true;
|
startedAt = performance.now();
|
raf = requestAnimationFrame(tick);
|
}
|
|
function pause() {
|
playing.value = false;
|
cancelAnimationFrame(raf);
|
}
|
|
function stop() {
|
pause();
|
elapsed = 0;
|
progress.value = 0;
|
playPos.value = points.value.length
|
? [Number(points.value[0].longitude), Number(points.value[0].latitude)]
|
: null;
|
}
|
|
function reset() {
|
stop();
|
playPos.value = null;
|
}
|
|
onBeforeUnmount(pause);
|
|
return { playing, speed, progress, playPos, path, play, pause, stop, reset };
|
}
|