import { type CameraState } from '../camera/camera' import { calibratedLonLatToWorldTile } from '../../utils/projection' import { worldToScreen } from '../camera/camera' import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapScene } from '../renderer/mapRenderer' export interface ScreenPoint { x: number y: number } function smoothTrackScreenPoints(points: ScreenPoint[]): ScreenPoint[] { if (points.length < 3) { return points } const smoothed: ScreenPoint[] = [points[0]] for (let index = 1; index < points.length - 1; index += 1) { const prev = points[index - 1] const current = points[index] const next = points[index + 1] smoothed.push({ x: prev.x * 0.2 + current.x * 0.6 + next.x * 0.2, y: prev.y * 0.2 + current.y * 0.6 + next.y * 0.2, }) } smoothed.push(points[points.length - 1]) return smoothed } function buildVectorCamera(scene: MapScene): CameraState { return { centerWorldX: scene.exactCenterWorldX, centerWorldY: scene.exactCenterWorldY, viewportWidth: scene.viewportWidth, viewportHeight: scene.viewportHeight, visibleColumns: scene.visibleColumns, rotationRad: scene.rotationRad, } } export class TrackLayer implements MapLayer { projectPoints(scene: MapScene): ScreenPoint[] { const camera = buildVectorCamera(scene) return scene.track.map((point) => { const worldPoint = calibratedLonLatToWorldTile(point, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin) return worldToScreen(camera, worldPoint, false) }) } draw(context: LayerRenderContext): void { const { ctx, scene } = context if (scene.trackMode === 'none') { return } const points = smoothTrackScreenPoints(this.projectPoints(scene)) if (!points.length) { return } ctx.save() ctx.lineCap = 'round' ctx.lineJoin = 'round' if (scene.trackMode === 'tail') { const baseAlpha = 0.12 + scene.trackStyleConfig.glowStrength * 0.08 points.forEach((screenPoint, index) => { if (index === 0) { return } const progress = index / Math.max(1, points.length - 1) ctx.strokeStyle = `rgba(84, 243, 216, ${baseAlpha + progress * 0.58})` ctx.lineWidth = 1.4 + progress * 4.2 ctx.beginPath() ctx.moveTo(points[index - 1].x, points[index - 1].y) ctx.lineTo(screenPoint.x, screenPoint.y) ctx.stroke() }) const head = points[points.length - 1] ctx.fillStyle = 'rgba(84, 243, 216, 0.24)' ctx.beginPath() ctx.arc(head.x, head.y, 11, 0, Math.PI * 2) ctx.fill() ctx.fillStyle = '#54f3d8' ctx.beginPath() ctx.arc(head.x, head.y, 5.2, 0, Math.PI * 2) ctx.fill() } else { ctx.strokeStyle = 'rgba(23, 109, 93, 0.96)' ctx.lineWidth = 4.2 ctx.beginPath() points.forEach((screenPoint, index) => { if (index === 0) { ctx.moveTo(screenPoint.x, screenPoint.y) return } ctx.lineTo(screenPoint.x, screenPoint.y) }) ctx.stroke() } ctx.restore() } }