tileLayer.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { createTileGrid, type TileItem } from '../../utils/tile'
  2. import { isTileWithinBounds } from '../../utils/remoteMapConfig'
  3. import { getTileSizePx, type CameraState } from '../camera/camera'
  4. import { type MapScene } from '../renderer/mapRenderer'
  5. import { type TileStore } from '../tile/tileStore'
  6. import { type MapLayer, type LayerRenderContext } from './mapLayer'
  7. function buildGridKey(scene: MapScene, tileSize: number): string {
  8. return [
  9. scene.tileSource,
  10. scene.zoom,
  11. scene.centerTileX,
  12. scene.centerTileY,
  13. scene.viewportWidth,
  14. scene.viewportHeight,
  15. tileSize,
  16. scene.overdraw,
  17. ].join('|')
  18. }
  19. export class TileLayer implements MapLayer {
  20. lastVisibleTileCount: number
  21. lastReadyTileCount: number
  22. cachedGridKey: string
  23. cachedTiles: TileItem[]
  24. constructor() {
  25. this.lastVisibleTileCount = 0
  26. this.lastReadyTileCount = 0
  27. this.cachedGridKey = ''
  28. this.cachedTiles = []
  29. }
  30. prepareTiles(scene: MapScene, camera: CameraState, tileStore: TileStore): TileItem[] {
  31. const tileSize = getTileSizePx(camera)
  32. if (!tileSize) {
  33. this.lastVisibleTileCount = 0
  34. this.lastReadyTileCount = 0
  35. this.cachedGridKey = ''
  36. this.cachedTiles = []
  37. return []
  38. }
  39. const gridKey = buildGridKey(scene, tileSize)
  40. if (gridKey !== this.cachedGridKey) {
  41. this.cachedGridKey = gridKey
  42. this.cachedTiles = createTileGrid({
  43. urlTemplate: scene.tileSource,
  44. zoom: scene.zoom,
  45. centerTileX: scene.centerTileX,
  46. centerTileY: scene.centerTileY,
  47. viewportWidth: scene.viewportWidth,
  48. viewportHeight: scene.viewportHeight,
  49. tileSize,
  50. overdraw: scene.overdraw,
  51. }).filter((tile) => isTileWithinBounds(scene.tileBoundsByZoom, scene.zoom, tile.x, tile.y))
  52. }
  53. tileStore.queueVisibleTiles(this.cachedTiles, scene, gridKey)
  54. this.lastVisibleTileCount = this.cachedTiles.length
  55. this.lastReadyTileCount = 0
  56. return this.cachedTiles
  57. }
  58. draw(context: LayerRenderContext): void {
  59. const { ctx, scene, camera, tileStore } = context
  60. const tiles = this.prepareTiles(scene, camera, tileStore)
  61. for (const tile of tiles) {
  62. const entry = tileStore.getEntry(tile.url)
  63. const drawLeft = tile.leftPx + scene.translateX
  64. const drawTop = tile.topPx + scene.translateY
  65. if (entry && entry.status === 'ready' && entry.image) {
  66. ctx.drawImage(entry.image, drawLeft, drawTop, tile.sizePx, tile.sizePx)
  67. this.lastReadyTileCount += 1
  68. continue
  69. }
  70. const parentFallback = tileStore.getParentFallbackSlice(tile, scene)
  71. let drewFallback = false
  72. if (parentFallback) {
  73. ctx.drawImage(
  74. parentFallback.entry.image,
  75. parentFallback.sourceX,
  76. parentFallback.sourceY,
  77. parentFallback.sourceWidth,
  78. parentFallback.sourceHeight,
  79. drawLeft,
  80. drawTop,
  81. tile.sizePx,
  82. tile.sizePx,
  83. )
  84. drewFallback = true
  85. }
  86. const childFallback = tileStore.getChildFallback(tile, scene)
  87. if (childFallback) {
  88. const cellWidth = tile.sizePx / childFallback.division
  89. const cellHeight = tile.sizePx / childFallback.division
  90. for (const child of childFallback.children) {
  91. const childImageWidth = child.entry.image.width || 256
  92. const childImageHeight = child.entry.image.height || 256
  93. ctx.drawImage(
  94. child.entry.image,
  95. 0,
  96. 0,
  97. childImageWidth,
  98. childImageHeight,
  99. drawLeft + child.offsetX * cellWidth,
  100. drawTop + child.offsetY * cellHeight,
  101. cellWidth,
  102. cellHeight,
  103. )
  104. }
  105. drewFallback = true
  106. }
  107. if (!drewFallback) {
  108. ctx.fillStyle = entry && entry.status === 'error' ? '#d9b2b2' : '#dbeed4'
  109. ctx.fillRect(drawLeft, drawTop, tile.sizePx, tile.sizePx)
  110. }
  111. }
  112. }
  113. }