trackLayer.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { type CameraState } from '../camera/camera'
  2. import { calibratedLonLatToWorldTile } from '../../utils/projection'
  3. import { worldToScreen } from '../camera/camera'
  4. import { type MapLayer, type LayerRenderContext } from './mapLayer'
  5. import { type MapScene } from '../renderer/mapRenderer'
  6. export interface ScreenPoint {
  7. x: number
  8. y: number
  9. }
  10. function smoothTrackScreenPoints(points: ScreenPoint[]): ScreenPoint[] {
  11. if (points.length < 3) {
  12. return points
  13. }
  14. const smoothed: ScreenPoint[] = [points[0]]
  15. for (let index = 1; index < points.length - 1; index += 1) {
  16. const prev = points[index - 1]
  17. const current = points[index]
  18. const next = points[index + 1]
  19. smoothed.push({
  20. x: prev.x * 0.2 + current.x * 0.6 + next.x * 0.2,
  21. y: prev.y * 0.2 + current.y * 0.6 + next.y * 0.2,
  22. })
  23. }
  24. smoothed.push(points[points.length - 1])
  25. return smoothed
  26. }
  27. function buildVectorCamera(scene: MapScene): CameraState {
  28. return {
  29. centerWorldX: scene.exactCenterWorldX,
  30. centerWorldY: scene.exactCenterWorldY,
  31. viewportWidth: scene.viewportWidth,
  32. viewportHeight: scene.viewportHeight,
  33. visibleColumns: scene.visibleColumns,
  34. rotationRad: scene.rotationRad,
  35. }
  36. }
  37. export class TrackLayer implements MapLayer {
  38. projectPoints(scene: MapScene): ScreenPoint[] {
  39. const camera = buildVectorCamera(scene)
  40. return scene.track.map((point) => {
  41. const worldPoint = calibratedLonLatToWorldTile(point, scene.zoom, scene.gpsCalibration, scene.gpsCalibrationOrigin)
  42. return worldToScreen(camera, worldPoint, false)
  43. })
  44. }
  45. draw(context: LayerRenderContext): void {
  46. const { ctx, scene } = context
  47. if (scene.trackMode === 'none') {
  48. return
  49. }
  50. const points = smoothTrackScreenPoints(this.projectPoints(scene))
  51. if (!points.length) {
  52. return
  53. }
  54. ctx.save()
  55. ctx.lineCap = 'round'
  56. ctx.lineJoin = 'round'
  57. if (scene.trackMode === 'tail') {
  58. const baseAlpha = 0.12 + scene.trackStyleConfig.glowStrength * 0.08
  59. points.forEach((screenPoint, index) => {
  60. if (index === 0) {
  61. return
  62. }
  63. const progress = index / Math.max(1, points.length - 1)
  64. ctx.strokeStyle = `rgba(84, 243, 216, ${baseAlpha + progress * 0.58})`
  65. ctx.lineWidth = 1.4 + progress * 4.2
  66. ctx.beginPath()
  67. ctx.moveTo(points[index - 1].x, points[index - 1].y)
  68. ctx.lineTo(screenPoint.x, screenPoint.y)
  69. ctx.stroke()
  70. })
  71. const head = points[points.length - 1]
  72. ctx.fillStyle = 'rgba(84, 243, 216, 0.24)'
  73. ctx.beginPath()
  74. ctx.arc(head.x, head.y, 11, 0, Math.PI * 2)
  75. ctx.fill()
  76. ctx.fillStyle = '#54f3d8'
  77. ctx.beginPath()
  78. ctx.arc(head.x, head.y, 5.2, 0, Math.PI * 2)
  79. ctx.fill()
  80. } else {
  81. ctx.strokeStyle = 'rgba(23, 109, 93, 0.96)'
  82. ctx.lineWidth = 4.2
  83. ctx.beginPath()
  84. points.forEach((screenPoint, index) => {
  85. if (index === 0) {
  86. ctx.moveTo(screenPoint.x, screenPoint.y)
  87. return
  88. }
  89. ctx.lineTo(screenPoint.x, screenPoint.y)
  90. })
  91. ctx.stroke()
  92. }
  93. ctx.restore()
  94. }
  95. }