Files
hnh-map/frontend-nuxt/lib/LeafletCustomTypes.ts
Nikolay Tatarinov 225aaa36e7 Enhance map functionality and API documentation
- Updated API documentation for the `rebuildZooms` endpoint to clarify its long execution time and response behavior.
- Modified MapView component to manage tile cache invalidation after rebuilding zoom levels, ensuring fresh tile display.
- Introduced a new composable for handling tile cache invalidation state after admin actions.
- Enhanced character icon creation to reflect ownership status with distinct colors.
- Improved loading state handling in various components for better user experience during data fetching.
2026-03-01 19:09:46 +03:00

93 lines
3.4 KiB
TypeScript

import L, { Bounds, LatLng, Point } from 'leaflet'
export const TileSize = 100
export const HnHMaxZoom = 6
export const HnHMinZoom = 1
export const HnHDefaultZoom = 6
export interface GridCoordLayerOptions extends L.GridLayerOptions {
visible?: boolean
}
/**
* Grid layer that draws one coordinate label per Leaflet tile in the top-left corner.
* Uses only coords.z (do not use map.getZoom()) so labels stay in sync with tiles.
* SmartTileLayer has zoomReverse: true, so URL z = 6 - coords.z; backend maps z=6 → storageZ=0.
* Game tile for this Leaflet tile: scaleFactor = 2^(HnHMaxZoom - coords.z),
* topLeft = (coords.x * scaleFactor, coords.y * scaleFactor) — same (x,y) as backend tile.
*/
export const GridCoordLayer = L.GridLayer.extend({
options: {
visible: true,
},
createTile(coords: { x: number; y: number; z: number }) {
if (!this.options.visible) {
const element = document.createElement('div')
element.style.width = TileSize + 'px'
element.style.height = TileSize + 'px'
element.classList.add('map-tile')
return element
}
const element = document.createElement('div')
element.style.width = TileSize + 'px'
element.style.height = TileSize + 'px'
element.style.position = 'relative'
element.classList.add('map-tile')
const scaleFactor = Math.pow(2, HnHMaxZoom - coords.z)
const topLeft = { x: coords.x * scaleFactor, y: coords.y * scaleFactor }
// One label per Leaflet tile at top-left (2px, 2px); same (x,y) as backend tile for this coords.
const textElement = document.createElement('div')
textElement.classList.add('map-tile-text')
textElement.textContent = `(${topLeft.x}, ${topLeft.y})`
textElement.style.position = 'absolute'
textElement.style.left = '2px'
textElement.style.top = '2px'
textElement.style.fontSize = Math.max(8, 12 - Math.log2(scaleFactor) * 2) + 'px'
element.appendChild(textElement)
return element
},
}) as unknown as new (options?: GridCoordLayerOptions) => L.GridLayer
export interface ImageIconOptions extends L.IconOptions {
/** When the main icon image fails to load, use this URL (e.g. data URL or default marker). */
fallbackIconUrl?: string
}
export const ImageIcon = L.Icon.extend({
options: {
iconSize: [32, 32],
iconAnchor: [16, 16],
} as ImageIconOptions,
createIcon(oldIcon?: HTMLElement): HTMLElement {
const img = L.Icon.prototype.createIcon.call(this, oldIcon) as HTMLImageElement
const fallback = (this.options as ImageIconOptions).fallbackIconUrl
if (fallback && img && img.tagName === 'IMG') {
img.onerror = () => {
img.onerror = null
img.src = fallback
}
}
return img
},
}) as unknown as new (options?: ImageIconOptions) => L.Icon
const latNormalization = (90.0 * TileSize) / 2500000.0
const lngNormalization = (180.0 * TileSize) / 2500000.0
const HnHProjection = {
project(latlng: LatLng) {
return new Point(latlng.lat / latNormalization, latlng.lng / lngNormalization)
},
unproject(point: Point) {
return new LatLng(point.x * latNormalization, point.y * lngNormalization)
},
bounds: (() => new Bounds([-latNormalization, -lngNormalization], [latNormalization, lngNormalization]))(),
}
export const HnHCRS = L.extend({}, L.CRS.Simple, {
projection: HnHProjection,
}) as L.CRS