tileLayer.ts 3.7 KB

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