import { calibratedLonLatToWorldTile } from '../../utils/projection' import { worldToScreen, type CameraState } from '../camera/camera' import { type MapLayer, type LayerRenderContext } from './mapLayer' import { type MapScene } from '../renderer/mapRenderer' import { type ScreenPoint } from './trackLayer' 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 GpsLayer implements MapLayer { projectPoint(scene: MapScene): ScreenPoint { const camera = buildVectorCamera(scene) const worldPoint = calibratedLonLatToWorldTile(scene.gpsPoint, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin) return worldToScreen(camera, worldPoint, false) } getPulseRadius(pulseFrame: number): number { return 18 + 6 * (0.5 + 0.5 * Math.sin(pulseFrame / 6)) } draw(context: LayerRenderContext): void { const { ctx, scene, pulseFrame } = context const gpsScreenPoint = this.projectPoint(scene) const pulse = this.getPulseRadius(pulseFrame) ctx.save() ctx.beginPath() ctx.fillStyle = 'rgba(33, 158, 188, 0.22)' ctx.arc(gpsScreenPoint.x, gpsScreenPoint.y, pulse, 0, Math.PI * 2) ctx.fill() ctx.beginPath() ctx.fillStyle = '#21a1bc' ctx.arc(gpsScreenPoint.x, gpsScreenPoint.y, 9, 0, Math.PI * 2) ctx.fill() ctx.beginPath() ctx.strokeStyle = '#ffffff' ctx.lineWidth = 3 ctx.arc(gpsScreenPoint.x, gpsScreenPoint.y, 13, 0, Math.PI * 2) ctx.stroke() ctx.fillStyle = '#0b3d4a' ctx.font = 'bold 16px sans-serif' ctx.textAlign = 'left' ctx.textBaseline = 'bottom' ctx.fillText('GPS', gpsScreenPoint.x + 14, gpsScreenPoint.y - 12) ctx.restore() } }