From bd5a59d8277688401d9089d3fada43b011e2c295 Mon Sep 17 00:00:00 2001 From: Mark Kalsbeek Date: Sun, 25 Jan 2026 11:47:18 +0100 Subject: [PATCH] switch to pinia for state mangement --- ui/deno.json | 2 +- ui/deno.lock | 20 +-- ui/src/App.vue | 64 +++---- ui/src/main.js | 7 +- ui/src/stores/tiles.js | 347 ++++++++++++++++++++++++++++++++++++++ ui/src/utils/api.js | 3 - ui/src/utils/tileCache.js | 301 --------------------------------- 7 files changed, 383 insertions(+), 361 deletions(-) create mode 100644 ui/src/stores/tiles.js delete mode 100644 ui/src/utils/tileCache.js diff --git a/ui/deno.json b/ui/deno.json index d510699..e510412 100644 --- a/ui/deno.json +++ b/ui/deno.json @@ -2,10 +2,10 @@ "imports": { "@vitejs/plugin-vue": "npm:@vitejs/plugin-vue@^5", "maplibre-gl": "npm:maplibre-gl@^5.16.0", + "pinia": "npm:pinia@^3.0.4", "three": "npm:three@^0.182.0", "vite": "npm:vite@^7.2.1", "vue": "npm:vue@^3.5.23", - "pinia": "npm:pinia@^3.0.4", }, "tasks": { "dev": "deno run -A --node-modules-dir npm:vite", diff --git a/ui/deno.lock b/ui/deno.lock index 0ca909e..e54e4ee 100644 --- a/ui/deno.lock +++ b/ui/deno.lock @@ -398,14 +398,14 @@ "@vue/shared" ] }, - "@vue/devtools-api@7.7.7": { - "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "@vue/devtools-api@7.7.9": { + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", "dependencies": [ "@vue/devtools-kit" ] }, - "@vue/devtools-kit@7.7.7": { - "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "@vue/devtools-kit@7.7.9": { + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", "dependencies": [ "@vue/devtools-shared", "birpc", @@ -416,8 +416,8 @@ "superjson" ] }, - "@vue/devtools-shared@7.7.7": { - "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "@vue/devtools-shared@7.7.9": { + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", "dependencies": [ "rfdc" ] @@ -455,8 +455,8 @@ "@vue/shared@3.5.23": { "integrity": "sha512-0YZ1DYuC5o/YJPf6pFdt2KYxVGDxkDbH/1NYJnVJWUkzr8ituBEmFVQRNX2gCaAsFEjEDnLkWpgqlZA7htgS/g==" }, - "birpc@2.7.0": { - "integrity": "sha512-tub/wFGH49vNCm0xraykcY3TcRgX/3JsALYq/Lwrtti+bTyFHkCUAWF5wgYoie8P41wYwig2mIKiqoocr1EkEQ==" + "birpc@2.9.0": { + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==" }, "copy-anything@4.0.5": { "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", @@ -687,8 +687,8 @@ "kdbush" ] }, - "superjson@2.2.5": { - "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", + "superjson@2.2.6": { + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", "dependencies": [ "copy-anything" ] diff --git a/ui/src/App.vue b/ui/src/App.vue index 75a5761..0a201db 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -122,15 +122,7 @@ import { KNOWN_SITES } from './data/historicSites.js'; // UTILITIES // ============================================================================ import { calculateDistance, calculateBearing, extendRay } from './utils/geometry.js'; -import { getTileByCoordinates, getTileById, getTileMoundData, requestTileProcessing } from './utils/api.js'; -import { - getMetadata, setMetadata, findTileByCoords, hasMetadata, - getMoundData, setMoundData, hasMoundData, - getImageUrl, setImagesOnMap, areImagesOnMap, removeImagesFromMap, - setLoading, isMetadataLoading, isMoundLoading, isImageLoading, - isMetadataLoaded, isMoundLoaded, isReadyToRender, getImageAvailability, - getAllTileIdsWithImages, getAllTileIdsWithMounds -} from './utils/tileCache.js'; +import { useTilesStore } from './stores/tiles.js'; // For generating the pre-baked tiles: // import { batchRenderTiles } from './utils/batch-renderer.js'; @@ -140,6 +132,8 @@ import { // CONSTANTS // ============================================================================ +const tilesStore = useTilesStore(); + const DEFAULT_RENDER_SETTINGS = { azimuth: 90, altitude: 60, @@ -243,12 +237,12 @@ function webMercatorToLonLat(x, y) { // Check if tile images are loaded on map function areTileImagesLoaded(tileId) { - return areImagesOnMap(tileId); + return tilesStore.areImagesOnMap(tileId); } // Check if mound data is cached function isMoundDataCached(tileId) { - return hasMoundData(tileId); + return tilesStore.hasMoundData(tileId); } // ============================================================================ @@ -314,8 +308,8 @@ async function loadTileImages(tileId, tileMetadata, imageUrl = null) { // Determine which format to load (prefer PNG over JPG) if (!imageUrl) { imageUrl = tileMetadata.png_available - ? getImageUrl(tileId, 'png') - : getImageUrl(tileId, 'jpg'); + ? tilesStore.getImageUrl(tileId, 'png') + : tilesStore.getImageUrl(tileId, 'jpg'); } // Remove old source/layer if overwriting @@ -356,12 +350,7 @@ async function loadTileImages(tileId, tileMetadata, imageUrl = null) { } }, 'lidar-datum'); - setImagesOnMap(tileId); - - // Also cache the metadata if we received it - if (tileMetadata) { - setMetadata(tileId, tileMetadata); - } + tilesStore.markImagesOnMap(tileId); } catch (err) { console.error(`Failed to load images for ${tileId}:`, err); throw err; @@ -374,12 +363,8 @@ async function loadInitialTiles() { for (const tileName of TILE_NAMES) { try { - - // Fetch tile metadata by ID - const tileMetadata = await getTileById(tileName); - - // Cache the metadata - setMetadata(tileName, tileMetadata); + // Fetch tile metadata by ID (this also caches it) + const tileMetadata = await tilesStore.fetchMetadataById(tileName); // Only load if tile is ready and has images available if (tileMetadata.status === 'ready' && (tileMetadata.png_available || tileMetadata.jpg_available)) { @@ -393,7 +378,7 @@ async function loadInitialTiles() { } } - console.log(`Loaded ${getAllTileIdsWithImages().length} initial tiles`); + console.log(`Loaded ${tilesStore.getAllTileIdsWithImages.length} initial tiles`); } async function loadMoundData(tileId) { @@ -402,18 +387,12 @@ async function loadMoundData(tileId) { return; } - setLoading(tileId, 'mound', true); - try { - const buffer = await getTileMoundData(tileId); - const data = parseMoundBuffer(buffer); - setMoundData(tileId, data); + await tilesStore.fetchMoundData(tileId, parseMoundBuffer); console.log(`Loaded mound data for ${tileId}`); } catch (err) { console.error(`Failed to load mound data for ${tileId}:`, err); throw err; - } finally { - setLoading(tileId, 'mound', false); } } @@ -441,12 +420,9 @@ async function handleContextMenu(e) { // Try to fetch tile metadata from API try { - const tileData = await getTileByCoordinates(lat, lng); + const tileData = await tilesStore.fetchMetadataByCoords(lat, lng); if (tileData) { - // Cache the metadata - setMetadata(tileData.id, tileData); - contextMenu.value.tileData = tileData; contextMenu.value.imagesLoaded = areTileImagesLoaded(tileData.id); contextMenu.value.moundLoaded = isMoundDataCached(tileData.id); @@ -476,7 +452,7 @@ async function requestTileFromContextMenu() { }; // Start SSE request - requestTileProcessing( + tilesStore.requestTileProcessing( lat, lng, async (data) => { @@ -641,7 +617,7 @@ function toggleLidar() { if (!map) return; const visibility = showLidar.value ? 'visible' : 'none'; - for (const tileId of getAllTileIdsWithImages()) { + for (const tileId of tilesStore.getAllTileIdsWithImages) { const layerId = `tile-image-layer-${tileId}`; if (map.getLayer(layerId)) { map.setLayoutProperty(layerId, 'visibility', visibility); @@ -653,7 +629,7 @@ function updateLidarOpacity() { if (!map) return; const opacity = lidarOpacity.value / 100; - for (const tileId of getAllTileIdsWithImages()) { + for (const tileId of tilesStore.getAllTileIdsWithImages) { const layerId = `tile-image-layer-${tileId}`; if (map.getLayer(layerId)) { map.setPaintProperty(layerId, 'raster-opacity', opacity); @@ -669,10 +645,10 @@ function openSandbox() { sandboxVisible.value = true; setTimeout(() => { - const tilesWithMounds = getAllTileIdsWithMounds(); + const tilesWithMounds = tilesStore.getAllTileIdsWithMounds; if (sandboxRef.value && tilesWithMounds.length > 0) { const firstTileId = tilesWithMounds[0]; - const firstTile = getMoundData(firstTileId); + const firstTile = tilesStore.getMoundData(firstTileId); if (firstTile) { sandboxRef.value.loadTileData(firstTile); } @@ -688,7 +664,7 @@ async function openSandboxWithTile(tileId) { sandboxVisible.value = true; setTimeout(() => { - const moundData = getMoundData(tileId); + const moundData = tilesStore.getMoundData(tileId); if (sandboxRef.value && moundData) { sandboxRef.value.loadTileData(moundData, tileId); } @@ -701,7 +677,7 @@ function onRenderComplete(result) { // Result should contain: { dataURL, tileId, ... } if (result.tileId && result.dataURL) { - const metadata = getMetadata(result.tileId); + const metadata = tilesStore.getMetadata(result.tileId); if (metadata) { // Load the sandbox-rendered image onto the map loadTileImages(result.tileId, metadata, result.dataURL); diff --git a/ui/src/main.js b/ui/src/main.js index 48ac44e..c87c46b 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -1,6 +1,9 @@ import { createApp } from 'vue' +import { createPinia } from 'pinia' import App from './App.vue' - +const pinia = createPinia(); const app = createApp(App) -app.mount('#app') \ No newline at end of file + +app.use(pinia) +app.mount('#app') diff --git a/ui/src/stores/tiles.js b/ui/src/stores/tiles.js new file mode 100644 index 0000000..95f20da --- /dev/null +++ b/ui/src/stores/tiles.js @@ -0,0 +1,347 @@ +// ============================================================================ +// TILES STORE (Pinia) +// Centralized tile state management with integrated API calls +// ============================================================================ + +import { defineStore } from 'pinia'; +import { getTileByCoordinates, getTileById, getTileMoundData, requestTileProcessing } from '../utils/api.js'; + +export const useTilesStore = defineStore('tiles', { + // ========================================================================== + // STATE + // ========================================================================== + state: () => ({ + // Metadata cache - Map + metadata: new Map(), + + // Mound data cache - Map + mounds: new Map(), + + // Images on map tracker - Set + imagesOnMap: new Set(), + + // Loading state - Map + loading: new Map(), + }), + + // ========================================================================== + // GETTERS + // ========================================================================== + getters: { + // ------------------------------------------------------------------------ + // Metadata getters + // ------------------------------------------------------------------------ + getMetadata: (state) => (tileId) => { + return state.metadata.get(tileId) || null; + }, + + hasMetadata: (state) => (tileId) => { + return state.metadata.has(tileId); + }, + + findTileByCoords: (state) => (lat, lng) => { + for (const [tileId, meta] of state.metadata) { + if (lat >= meta.min_lat && lat <= meta.max_lat && + lng >= meta.min_lng && lng <= meta.max_lng) { + return tileId; + } + } + return null; + }, + + getAllTileIds: (state) => { + return Array.from(state.metadata.keys()); + }, + + // ------------------------------------------------------------------------ + // Mound data getters + // ------------------------------------------------------------------------ + getMoundData: (state) => (tileId) => { + return state.mounds.get(tileId) || null; + }, + + hasMoundData: (state) => (tileId) => { + return state.mounds.has(tileId); + }, + + getAllTileIdsWithMounds: (state) => { + return Array.from(state.mounds.keys()); + }, + + // ------------------------------------------------------------------------ + // Image getters + // ------------------------------------------------------------------------ + getImageUrl: () => (tileId, type) => { + const API_BASE = ''; // Same origin + return `${API_BASE}/tiles/${type}/${tileId}.${type}`; + }, + + areImagesOnMap: (state) => (tileId) => { + return state.imagesOnMap.has(tileId); + }, + + getAllTileIdsWithImages: (state) => { + return Array.from(state.imagesOnMap); + }, + + getImageAvailability: (state) => (tileId) => { + const meta = state.metadata.get(tileId); + if (!meta) { + return { jpg: false, png: false }; + } + return { + jpg: meta.jpg_available || false, + png: meta.png_available || false + }; + }, + + // ------------------------------------------------------------------------ + // Loading state getters + // ------------------------------------------------------------------------ + isMetadataLoading: (state) => (tileId) => { + return state.loading.get(tileId)?.metadata || false; + }, + + isMoundLoading: (state) => (tileId) => { + return state.loading.get(tileId)?.mound || false; + }, + + isImageLoading: (state) => (tileId, type) => { + return state.loading.get(tileId)?.[type] || false; + }, + + // ------------------------------------------------------------------------ + // Composite state getters + // ------------------------------------------------------------------------ + isMetadataLoaded: (state) => (tileId) => { + return state.metadata.has(tileId); + }, + + isMoundLoaded: (state) => (tileId) => { + return state.mounds.has(tileId); + }, + + isReadyToRender: (state) => (tileId) => { + return state.metadata.has(tileId) && state.mounds.has(tileId); + }, + + // ------------------------------------------------------------------------ + // Statistics + // ------------------------------------------------------------------------ + stats() { + return { + metadataCount: this.metadata.size, + moundCount: this.mounds.size, + imagesOnMapCount: this.imagesOnMap.size, + loadingCount: this.loading.size + }; + } + }, + + // ========================================================================== + // ACTIONS + // ========================================================================== + actions: { + // ------------------------------------------------------------------------ + // Metadata actions + // ------------------------------------------------------------------------ + + /** + * Fetch and cache tile metadata by coordinates + * @param {number} lat + * @param {number} lng + * @returns {Promise} Metadata or null if not found + */ + async fetchMetadataByCoords(lat, lng) { + // Check if we already have it cached + const cachedTileId = this.findTileByCoords(lat, lng); + if (cachedTileId) { + return this.metadata.get(cachedTileId); + } + + // Set loading state + const tempId = `temp-${lat}-${lng}`; + this._setLoading(tempId, 'metadata', true); + + try { + const metadata = await getTileByCoordinates(lat, lng); + + if (metadata) { + this.metadata.set(metadata.id, metadata); + return metadata; + } + + return null; + } catch (err) { + console.error('Failed to fetch metadata:', err); + throw err; + } finally { + this._setLoading(tempId, 'metadata', false); + } + }, + + /** + * Fetch and cache tile metadata by ID + * @param {string} tileId + * @returns {Promise} + */ + async fetchMetadataById(tileId) { + // Return from cache if available + if (this.metadata.has(tileId)) { + return this.metadata.get(tileId); + } + + this._setLoading(tileId, 'metadata', true); + + try { + const metadata = await getTileById(tileId); + this.metadata.set(tileId, metadata); + return metadata; + } catch (err) { + console.error('Failed to fetch metadata by ID:', err); + throw err; + } finally { + this._setLoading(tileId, 'metadata', false); + } + }, + + /** + * Manually set metadata (for cases where you already have it) + * @param {string} tileId + * @param {Object} metadata + */ + setMetadata(tileId, metadata) { + this.metadata.set(tileId, metadata); + }, + + // ------------------------------------------------------------------------ + // Mound data actions + // ------------------------------------------------------------------------ + + /** + * Fetch and cache mound data + * @param {string} tileId + * @param {Function} parseFunction - Function to parse the mound buffer + * @returns {Promise} Parsed mound data + */ + async fetchMoundData(tileId, parseFunction) { + // Return from cache if available + if (this.mounds.has(tileId)) { + return this.mounds.get(tileId); + } + + this._setLoading(tileId, 'mound', true); + + try { + const buffer = await getTileMoundData(tileId); + const moundData = parseFunction(buffer); + + this.mounds.set(tileId, moundData); + return moundData; + } catch (err) { + console.error('Failed to fetch mound data:', err); + throw err; + } finally { + this._setLoading(tileId, 'mound', false); + } + }, + + /** + * Manually set mound data (for cases where you already have it parsed) + * @param {string} tileId + * @param {Object} moundData + */ + setMoundData(tileId, moundData) { + this.mounds.set(tileId, moundData); + }, + + // ------------------------------------------------------------------------ + // Image tracking actions + // ------------------------------------------------------------------------ + + /** + * Mark images as loaded on map + * @param {string} tileId + */ + markImagesOnMap(tileId) { + this.imagesOnMap.add(tileId); + }, + + /** + * Remove images from map tracking + * @param {string} tileId + */ + removeImagesFromMap(tileId) { + this.imagesOnMap.delete(tileId); + }, + + // ------------------------------------------------------------------------ + // Tile processing request (SSE) + // ------------------------------------------------------------------------ + + /** + * Request tile processing with progress updates + * @param {number} lat + * @param {number} lng + * @param {Function} onMessage - Callback for status updates + * @param {Function} onError - Callback for errors + * @returns {EventSource} Connection (call .close() to cancel) + */ + requestTileProcessing(lat, lng, onMessage, onError) { + return requestTileProcessing(lat, lng, onMessage, onError); + }, + + // ------------------------------------------------------------------------ + // Bulk operations + // ------------------------------------------------------------------------ + + /** + * Clear all caches + */ + clearAll() { + this.metadata.clear(); + this.mounds.clear(); + this.imagesOnMap.clear(); + this.loading.clear(); + }, + + /** + * Remove a specific tile from all caches + * @param {string} tileId + */ + removeTile(tileId) { + this.metadata.delete(tileId); + this.mounds.delete(tileId); + this.imagesOnMap.delete(tileId); + this.loading.delete(tileId); + }, + + // ------------------------------------------------------------------------ + // Internal helpers + // ------------------------------------------------------------------------ + + /** + * Initialize loading state for a tile if not present + * @private + */ + _initLoadingState(tileId) { + if (!this.loading.has(tileId)) { + this.loading.set(tileId, { + metadata: false, + mound: false, + jpg: false, + png: false + }); + } + }, + + /** + * Set loading state for a specific data type + * @private + */ + _setLoading(tileId, dataType, isLoading) { + this._initLoadingState(tileId); + this.loading.get(tileId)[dataType] = isLoading; + }, + } +}); \ No newline at end of file diff --git a/ui/src/utils/api.js b/ui/src/utils/api.js index 980ede2..dc787f2 100644 --- a/ui/src/utils/api.js +++ b/ui/src/utils/api.js @@ -1,6 +1,5 @@ // ============================================================================ // API UTILITIES -// All backend API interactions for the Hopewell Road Lidar application // ============================================================================ const API_BASE = ''; // Same origin @@ -97,8 +96,6 @@ export function requestTileProcessing(lat, lng, onMessage, onError) { // ============================================================================ // TILE FILE FETCHING // ============================================================================ -// NOTE: Image URL generation is now in tileCache.js via getImageUrl() -// This keeps all cache-related logic in one place /** * Fetch tile MOUND data (binary point cloud) diff --git a/ui/src/utils/tileCache.js b/ui/src/utils/tileCache.js deleted file mode 100644 index 7d66bf4..0000000 --- a/ui/src/utils/tileCache.js +++ /dev/null @@ -1,301 +0,0 @@ -// ============================================================================ -// TILE CACHE SYSTEM -// Centralized storage and lookup for all tile-related data -// ============================================================================ - -import { ref } from 'vue'; - -// ============================================================================ -// CACHE STORAGE -// ============================================================================ - -// Metadata from API - keyed by tile ID -// Entry: { id, status, min_lat, max_lat, min_lng, max_lng, error_message, -// jpg_available, png_available, created_at, updated_at } -const metadataCache = ref(new Map()); - -// Mound data - keyed by tile ID -// Entry: { positions: Float32Array, indices: Uint32Array, bounds: {...} } -const moundCache = ref(new Map()); - -// Loading state tracker - keyed by tile ID -// Entry: { metadata: bool, mound: bool, jpg: bool, png: bool } -const loadingState = ref(new Map()); - -// Images loaded on map - keyed by tile ID -// Simple Set tracking which tiles have their images rendered -const imagesOnMap = ref(new Set()); - -// ============================================================================ -// METADATA METHODS -// ============================================================================ - -/** - * Get tile metadata by ID - * @param {string} tileId - * @returns {Object|null} Metadata object or null if not cached - */ -export function getMetadata(tileId) { - return metadataCache.value.get(tileId) || null; -} - -/** - * Store tile metadata - * @param {string} tileId - * @param {Object} metadata - API metadata object - */ -export function setMetadata(tileId, metadata) { - metadataCache.value.set(tileId, metadata); -} - -/** - * Find tile ID by coordinates (linear scan with bounds check) - * @param {number} lat - * @param {number} lng - * @returns {string|null} Tile ID or null if no tile contains these coordinates - */ -export function findTileByCoords(lat, lng) { - for (const [tileId, meta] of metadataCache.value) { - if (lat >= meta.min_lat && lat <= meta.max_lat && - lng >= meta.min_lng && lng <= meta.max_lng) { - return tileId; - } - } - return null; -} - -/** - * Check if metadata is cached for a tile - * @param {string} tileId - * @returns {boolean} - */ -export function hasMetadata(tileId) { - return metadataCache.value.has(tileId); -} - -// ============================================================================ -// MOUND DATA METHODS -// ============================================================================ - -/** - * Get mound data for a tile - * @param {string} tileId - * @returns {Object|null} Mound data object or null if not cached - */ -export function getMoundData(tileId) { - return moundCache.value.get(tileId) || null; -} - -/** - * Store mound data for a tile - * @param {string} tileId - * @param {Object} moundData - { positions, indices, bounds } - */ -export function setMoundData(tileId, moundData) { - moundCache.value.set(tileId, moundData); -} - -/** - * Check if mound data is cached for a tile - * @param {string} tileId - * @returns {boolean} - */ -export function hasMoundData(tileId) { - return moundCache.value.has(tileId); -} - -// ============================================================================ -// IMAGE METHODS -// ============================================================================ - -/** - * Get image URL for a tile - * @param {string} tileId - * @param {string} type - 'jpg' or 'png' - * @returns {string} Image URL - */ -export function getImageUrl(tileId, type) { - const API_BASE = ''; // Same origin - return `${API_BASE}/tiles/${type}/${tileId}.${type}`; -} - -/** - * Mark tile images as loaded on map - * @param {string} tileId - */ -export function setImagesOnMap(tileId) { - imagesOnMap.value.add(tileId); -} - -/** - * Check if tile images are on the map - * @param {string} tileId - * @returns {boolean} - */ -export function areImagesOnMap(tileId) { - return imagesOnMap.value.has(tileId); -} - -/** - * Remove tile images from map tracking - * @param {string} tileId - */ -export function removeImagesFromMap(tileId) { - imagesOnMap.value.delete(tileId); -} - -// ============================================================================ -// LOADING STATE METHODS -// ============================================================================ - -/** - * Initialize loading state for a tile - * @param {string} tileId - */ -function initLoadingState(tileId) { - if (!loadingState.value.has(tileId)) { - loadingState.value.set(tileId, { - metadata: false, - mound: false, - jpg: false, - png: false - }); - } -} - -/** - * Set loading state for a specific data type - * @param {string} tileId - * @param {string} dataType - 'metadata' | 'mound' | 'jpg' | 'png' - * @param {boolean} isLoading - */ -export function setLoading(tileId, dataType, isLoading) { - initLoadingState(tileId); - loadingState.value.get(tileId)[dataType] = isLoading; -} - -/** - * Check if metadata is loading - * @param {string} tileId - * @returns {boolean} - */ -export function isMetadataLoading(tileId) { - return loadingState.value.get(tileId)?.metadata || false; -} - -/** - * Check if mound data is loading - * @param {string} tileId - * @returns {boolean} - */ -export function isMoundLoading(tileId) { - return loadingState.value.get(tileId)?.mound || false; -} - -/** - * Check if image is loading - * @param {string} tileId - * @param {string} type - 'jpg' or 'png' - * @returns {boolean} - */ -export function isImageLoading(tileId, type) { - return loadingState.value.get(tileId)?.[type] || false; -} - -// ============================================================================ -// STATUS CHECK METHODS -// ============================================================================ - -/** - * Check if tile has metadata loaded - * @param {string} tileId - * @returns {boolean} - */ -export function isMetadataLoaded(tileId) { - return hasMetadata(tileId); -} - -/** - * Check if tile has mound data loaded - * @param {string} tileId - * @returns {boolean} - */ -export function isMoundLoaded(tileId) { - return hasMoundData(tileId); -} - -/** - * Check if tile is ready to render (has both metadata and mound data) - * @param {string} tileId - * @returns {boolean} - */ -export function isReadyToRender(tileId) { - return hasMetadata(tileId) && hasMoundData(tileId); -} - -/** - * Check if images are available for a tile (from metadata) - * @param {string} tileId - * @returns {{ jpg: boolean, png: boolean }} - */ -export function getImageAvailability(tileId) { - const meta = getMetadata(tileId); - if (!meta) { - return { jpg: false, png: false }; - } - return { - jpg: meta.jpg_available || false, - png: meta.png_available || false - }; -} - -// ============================================================================ -// BULK OPERATIONS -// ============================================================================ - -/** - * Get all tile IDs that have images on the map - * @returns {string[]} - */ -export function getAllTileIdsWithImages() { - return Array.from(imagesOnMap.value); -} - -/** - * Get all tile IDs that have mound data - * @returns {string[]} - */ -export function getAllTileIdsWithMounds() { - return Array.from(moundCache.value.keys()); -} - -/** - * Get all tile IDs that have metadata - * @returns {string[]} - */ -export function getAllTileIds() { - return Array.from(metadataCache.value.keys()); -} - -/** - * Clear all caches (useful for testing/debugging) - */ -export function clearAllCaches() { - metadataCache.value.clear(); - moundCache.value.clear(); - loadingState.value.clear(); - imagesOnMap.value.clear(); -} - -/** - * Get cache statistics (useful for debugging) - * @returns {Object} - */ -export function getCacheStats() { - return { - metadataCount: metadataCache.value.size, - moundCount: moundCache.value.size, - imagesOnMapCount: imagesOnMap.value.size, - loadingCount: loadingState.value.size - }; -} \ No newline at end of file