Files
hnh-map/frontend-nuxt/lib/SmartTileLayer.ts
Nikolay Tatarinov dc53b79d84 Enhance map update handling and connection stability
- Introduced mechanisms to detect stale connections in the map updates, allowing for automatic reconnection if no messages are received within a specified timeframe.
- Updated the `refresh` method in `SmartTileLayer` to return a boolean indicating whether the tile was refreshed, improving the handling of tile updates.
- Enhanced the `Send` method in the `Topic` struct to drop messages for full subscribers while keeping them subscribed, ensuring continuous delivery of future updates.
- Added a keepalive mechanism in the `WatchGridUpdates` handler to maintain the connection and prevent timeouts.
2026-03-04 21:29:53 +03:00

91 lines
2.8 KiB
TypeScript

import L, { Util, Browser } from 'leaflet'
interface SmartTileLayerCache {
[key: string]: number | undefined
}
export const SmartTileLayer = L.TileLayer.extend({
cache: {} as SmartTileLayerCache,
invalidTile: '',
map: 0,
getTileUrl(coords: { x: number; y: number; z: number }) {
if (!this._map) return this.invalidTile
let zoom
try {
zoom = this._getZoomForUrl()
} catch {
return this.invalidTile
}
return this.getTrueTileUrl(coords, zoom)
},
getTrueTileUrl(coords: { x: number; y: number }, zoom: number) {
const data: Record<string, string | number | undefined> = {
r: Browser.retina ? '@2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
map: this.map,
z: zoom,
}
if (this._map && !this._map.options.crs.infinite) {
const invertedY = this._globalTileRange.max.y - coords.y
if (this.options.tms) {
data.y = invertedY
}
data['-y'] = invertedY
}
const cacheKey = `${data.map}:${data.x}:${data.y}:${data.z}`
data.cache = this.cache[cacheKey]
// Don't request tiles for invalid/unknown map (avoids 404 spam in console)
const mapId = Number(data.map)
if (data.map === undefined || data.map === null || mapId < 1) {
return this.invalidTile
}
// Only use placeholder when server explicitly marks tile as invalid (-1)
if (data.cache === -1) {
return this.invalidTile
}
// Allow tile request when map is valid even if SSE snapshot hasn't arrived yet
// (avoids empty map when proxy/SSE delays or drops first message)
if (data.cache === undefined || data.cache === null) {
data.cache = 0
}
return Util.template(this._url, Util.extend(data, this.options))
},
refresh(x: number, y: number, z: number): boolean {
let zoom = z
const maxZoom = this.options.maxZoom
const zoomReverse = this.options.zoomReverse
const zoomOffset = this.options.zoomOffset
if (zoomReverse) {
zoom = maxZoom - zoom
}
zoom += zoomOffset
const key = `${x}:${y}:${zoom}`
const tile = this._tiles[key]
if (!tile?.el) return false
const newUrl = this.getTrueTileUrl({ x, y }, z)
if (tile.el.dataset.tileUrl === newUrl) return true
tile.el.dataset.tileUrl = newUrl
tile.el.src = newUrl
tile.el.classList.add('tile-fresh')
const el = tile.el
setTimeout(() => el.classList.remove('tile-fresh'), 400)
return true
},
}) as unknown as new (urlTemplate: string, options?: L.TileLayerOptions) => L.TileLayer & {
cache: SmartTileLayerCache
invalidTile: string
map: number
getTrueTileUrl: (coords: { x: number; y: number }, zoom: number) => string
refresh: (x: number, y: number, z: number) => boolean
}