switch to pinia for state mangement
This commit is contained in:
@@ -2,10 +2,10 @@
|
|||||||
"imports": {
|
"imports": {
|
||||||
"@vitejs/plugin-vue": "npm:@vitejs/plugin-vue@^5",
|
"@vitejs/plugin-vue": "npm:@vitejs/plugin-vue@^5",
|
||||||
"maplibre-gl": "npm:maplibre-gl@^5.16.0",
|
"maplibre-gl": "npm:maplibre-gl@^5.16.0",
|
||||||
|
"pinia": "npm:pinia@^3.0.4",
|
||||||
"three": "npm:three@^0.182.0",
|
"three": "npm:three@^0.182.0",
|
||||||
"vite": "npm:vite@^7.2.1",
|
"vite": "npm:vite@^7.2.1",
|
||||||
"vue": "npm:vue@^3.5.23",
|
"vue": "npm:vue@^3.5.23",
|
||||||
"pinia": "npm:pinia@^3.0.4",
|
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"dev": "deno run -A --node-modules-dir npm:vite",
|
"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/shared"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@vue/devtools-api@7.7.7": {
|
"@vue/devtools-api@7.7.9": {
|
||||||
"integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
|
"integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@vue/devtools-kit"
|
"@vue/devtools-kit"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@vue/devtools-kit@7.7.7": {
|
"@vue/devtools-kit@7.7.9": {
|
||||||
"integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
|
"integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@vue/devtools-shared",
|
"@vue/devtools-shared",
|
||||||
"birpc",
|
"birpc",
|
||||||
@@ -416,8 +416,8 @@
|
|||||||
"superjson"
|
"superjson"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@vue/devtools-shared@7.7.7": {
|
"@vue/devtools-shared@7.7.9": {
|
||||||
"integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
|
"integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"rfdc"
|
"rfdc"
|
||||||
]
|
]
|
||||||
@@ -455,8 +455,8 @@
|
|||||||
"@vue/shared@3.5.23": {
|
"@vue/shared@3.5.23": {
|
||||||
"integrity": "sha512-0YZ1DYuC5o/YJPf6pFdt2KYxVGDxkDbH/1NYJnVJWUkzr8ituBEmFVQRNX2gCaAsFEjEDnLkWpgqlZA7htgS/g=="
|
"integrity": "sha512-0YZ1DYuC5o/YJPf6pFdt2KYxVGDxkDbH/1NYJnVJWUkzr8ituBEmFVQRNX2gCaAsFEjEDnLkWpgqlZA7htgS/g=="
|
||||||
},
|
},
|
||||||
"birpc@2.7.0": {
|
"birpc@2.9.0": {
|
||||||
"integrity": "sha512-tub/wFGH49vNCm0xraykcY3TcRgX/3JsALYq/Lwrtti+bTyFHkCUAWF5wgYoie8P41wYwig2mIKiqoocr1EkEQ=="
|
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="
|
||||||
},
|
},
|
||||||
"copy-anything@4.0.5": {
|
"copy-anything@4.0.5": {
|
||||||
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
"integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==",
|
||||||
@@ -687,8 +687,8 @@
|
|||||||
"kdbush"
|
"kdbush"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"superjson@2.2.5": {
|
"superjson@2.2.6": {
|
||||||
"integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==",
|
"integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"copy-anything"
|
"copy-anything"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -122,15 +122,7 @@ import { KNOWN_SITES } from './data/historicSites.js';
|
|||||||
// UTILITIES
|
// UTILITIES
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
import { calculateDistance, calculateBearing, extendRay } from './utils/geometry.js';
|
import { calculateDistance, calculateBearing, extendRay } from './utils/geometry.js';
|
||||||
import { getTileByCoordinates, getTileById, getTileMoundData, requestTileProcessing } from './utils/api.js';
|
import { useTilesStore } from './stores/tiles.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';
|
|
||||||
|
|
||||||
// For generating the pre-baked tiles:
|
// For generating the pre-baked tiles:
|
||||||
// import { batchRenderTiles } from './utils/batch-renderer.js';
|
// import { batchRenderTiles } from './utils/batch-renderer.js';
|
||||||
@@ -140,6 +132,8 @@ import {
|
|||||||
// CONSTANTS
|
// CONSTANTS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
const tilesStore = useTilesStore();
|
||||||
|
|
||||||
const DEFAULT_RENDER_SETTINGS = {
|
const DEFAULT_RENDER_SETTINGS = {
|
||||||
azimuth: 90,
|
azimuth: 90,
|
||||||
altitude: 60,
|
altitude: 60,
|
||||||
@@ -243,12 +237,12 @@ function webMercatorToLonLat(x, y) {
|
|||||||
|
|
||||||
// Check if tile images are loaded on map
|
// Check if tile images are loaded on map
|
||||||
function areTileImagesLoaded(tileId) {
|
function areTileImagesLoaded(tileId) {
|
||||||
return areImagesOnMap(tileId);
|
return tilesStore.areImagesOnMap(tileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if mound data is cached
|
// Check if mound data is cached
|
||||||
function isMoundDataCached(tileId) {
|
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)
|
// Determine which format to load (prefer PNG over JPG)
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
imageUrl = tileMetadata.png_available
|
imageUrl = tileMetadata.png_available
|
||||||
? getImageUrl(tileId, 'png')
|
? tilesStore.getImageUrl(tileId, 'png')
|
||||||
: getImageUrl(tileId, 'jpg');
|
: tilesStore.getImageUrl(tileId, 'jpg');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove old source/layer if overwriting
|
// Remove old source/layer if overwriting
|
||||||
@@ -356,12 +350,7 @@ async function loadTileImages(tileId, tileMetadata, imageUrl = null) {
|
|||||||
}
|
}
|
||||||
}, 'lidar-datum');
|
}, 'lidar-datum');
|
||||||
|
|
||||||
setImagesOnMap(tileId);
|
tilesStore.markImagesOnMap(tileId);
|
||||||
|
|
||||||
// Also cache the metadata if we received it
|
|
||||||
if (tileMetadata) {
|
|
||||||
setMetadata(tileId, tileMetadata);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to load images for ${tileId}:`, err);
|
console.error(`Failed to load images for ${tileId}:`, err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -374,12 +363,8 @@ async function loadInitialTiles() {
|
|||||||
|
|
||||||
for (const tileName of TILE_NAMES) {
|
for (const tileName of TILE_NAMES) {
|
||||||
try {
|
try {
|
||||||
|
// Fetch tile metadata by ID (this also caches it)
|
||||||
// Fetch tile metadata by ID
|
const tileMetadata = await tilesStore.fetchMetadataById(tileName);
|
||||||
const tileMetadata = await getTileById(tileName);
|
|
||||||
|
|
||||||
// Cache the metadata
|
|
||||||
setMetadata(tileName, tileMetadata);
|
|
||||||
|
|
||||||
// Only load if tile is ready and has images available
|
// Only load if tile is ready and has images available
|
||||||
if (tileMetadata.status === 'ready' && (tileMetadata.png_available || tileMetadata.jpg_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) {
|
async function loadMoundData(tileId) {
|
||||||
@@ -402,18 +387,12 @@ async function loadMoundData(tileId) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(tileId, 'mound', true);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buffer = await getTileMoundData(tileId);
|
await tilesStore.fetchMoundData(tileId, parseMoundBuffer);
|
||||||
const data = parseMoundBuffer(buffer);
|
|
||||||
setMoundData(tileId, data);
|
|
||||||
console.log(`Loaded mound data for ${tileId}`);
|
console.log(`Loaded mound data for ${tileId}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to load mound data for ${tileId}:`, err);
|
console.error(`Failed to load mound data for ${tileId}:`, err);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
|
||||||
setLoading(tileId, 'mound', false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,12 +420,9 @@ async function handleContextMenu(e) {
|
|||||||
|
|
||||||
// Try to fetch tile metadata from API
|
// Try to fetch tile metadata from API
|
||||||
try {
|
try {
|
||||||
const tileData = await getTileByCoordinates(lat, lng);
|
const tileData = await tilesStore.fetchMetadataByCoords(lat, lng);
|
||||||
|
|
||||||
if (tileData) {
|
if (tileData) {
|
||||||
// Cache the metadata
|
|
||||||
setMetadata(tileData.id, tileData);
|
|
||||||
|
|
||||||
contextMenu.value.tileData = tileData;
|
contextMenu.value.tileData = tileData;
|
||||||
contextMenu.value.imagesLoaded = areTileImagesLoaded(tileData.id);
|
contextMenu.value.imagesLoaded = areTileImagesLoaded(tileData.id);
|
||||||
contextMenu.value.moundLoaded = isMoundDataCached(tileData.id);
|
contextMenu.value.moundLoaded = isMoundDataCached(tileData.id);
|
||||||
@@ -476,7 +452,7 @@ async function requestTileFromContextMenu() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start SSE request
|
// Start SSE request
|
||||||
requestTileProcessing(
|
tilesStore.requestTileProcessing(
|
||||||
lat,
|
lat,
|
||||||
lng,
|
lng,
|
||||||
async (data) => {
|
async (data) => {
|
||||||
@@ -641,7 +617,7 @@ function toggleLidar() {
|
|||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
const visibility = showLidar.value ? 'visible' : 'none';
|
const visibility = showLidar.value ? 'visible' : 'none';
|
||||||
for (const tileId of getAllTileIdsWithImages()) {
|
for (const tileId of tilesStore.getAllTileIdsWithImages) {
|
||||||
const layerId = `tile-image-layer-${tileId}`;
|
const layerId = `tile-image-layer-${tileId}`;
|
||||||
if (map.getLayer(layerId)) {
|
if (map.getLayer(layerId)) {
|
||||||
map.setLayoutProperty(layerId, 'visibility', visibility);
|
map.setLayoutProperty(layerId, 'visibility', visibility);
|
||||||
@@ -653,7 +629,7 @@ function updateLidarOpacity() {
|
|||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
const opacity = lidarOpacity.value / 100;
|
const opacity = lidarOpacity.value / 100;
|
||||||
for (const tileId of getAllTileIdsWithImages()) {
|
for (const tileId of tilesStore.getAllTileIdsWithImages) {
|
||||||
const layerId = `tile-image-layer-${tileId}`;
|
const layerId = `tile-image-layer-${tileId}`;
|
||||||
if (map.getLayer(layerId)) {
|
if (map.getLayer(layerId)) {
|
||||||
map.setPaintProperty(layerId, 'raster-opacity', opacity);
|
map.setPaintProperty(layerId, 'raster-opacity', opacity);
|
||||||
@@ -669,10 +645,10 @@ function openSandbox() {
|
|||||||
sandboxVisible.value = true;
|
sandboxVisible.value = true;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const tilesWithMounds = getAllTileIdsWithMounds();
|
const tilesWithMounds = tilesStore.getAllTileIdsWithMounds;
|
||||||
if (sandboxRef.value && tilesWithMounds.length > 0) {
|
if (sandboxRef.value && tilesWithMounds.length > 0) {
|
||||||
const firstTileId = tilesWithMounds[0];
|
const firstTileId = tilesWithMounds[0];
|
||||||
const firstTile = getMoundData(firstTileId);
|
const firstTile = tilesStore.getMoundData(firstTileId);
|
||||||
if (firstTile) {
|
if (firstTile) {
|
||||||
sandboxRef.value.loadTileData(firstTile);
|
sandboxRef.value.loadTileData(firstTile);
|
||||||
}
|
}
|
||||||
@@ -688,7 +664,7 @@ async function openSandboxWithTile(tileId) {
|
|||||||
sandboxVisible.value = true;
|
sandboxVisible.value = true;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const moundData = getMoundData(tileId);
|
const moundData = tilesStore.getMoundData(tileId);
|
||||||
if (sandboxRef.value && moundData) {
|
if (sandboxRef.value && moundData) {
|
||||||
sandboxRef.value.loadTileData(moundData, tileId);
|
sandboxRef.value.loadTileData(moundData, tileId);
|
||||||
}
|
}
|
||||||
@@ -701,7 +677,7 @@ function onRenderComplete(result) {
|
|||||||
|
|
||||||
// Result should contain: { dataURL, tileId, ... }
|
// Result should contain: { dataURL, tileId, ... }
|
||||||
if (result.tileId && result.dataURL) {
|
if (result.tileId && result.dataURL) {
|
||||||
const metadata = getMetadata(result.tileId);
|
const metadata = tilesStore.getMetadata(result.tileId);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
// Load the sandbox-rendered image onto the map
|
// Load the sandbox-rendered image onto the map
|
||||||
loadTileImages(result.tileId, metadata, result.dataURL);
|
loadTileImages(result.tileId, metadata, result.dataURL);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
|
||||||
|
const pinia = createPinia();
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
app.mount('#app')
|
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
|
// API UTILITIES
|
||||||
// All backend API interactions for the Hopewell Road Lidar application
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const API_BASE = ''; // Same origin
|
const API_BASE = ''; // Same origin
|
||||||
@@ -97,8 +96,6 @@ export function requestTileProcessing(lat, lng, onMessage, onError) {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// TILE FILE FETCHING
|
// 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)
|
* 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