switch to pinia for state mangement
This commit is contained in:
@@ -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",
|
||||
|
||||
20
ui/deno.lock
generated
20
ui/deno.lock
generated
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.use(pinia)
|
||||
app.mount('#app')
|
||||
347
ui/src/stores/tiles.js
Normal file
347
ui/src/stores/tiles.js
Normal file
@@ -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<tileId, metadata>
|
||||
metadata: new Map(),
|
||||
|
||||
// Mound data cache - Map<tileId, { positions, indices, bounds }>
|
||||
mounds: new Map(),
|
||||
|
||||
// Images on map tracker - Set<tileId>
|
||||
imagesOnMap: new Set(),
|
||||
|
||||
// Loading state - Map<tileId, { metadata, mound, jpg, png }>
|
||||
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<Object|null>} 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<Object>}
|
||||
*/
|
||||
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<Object>} 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;
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user