tile-loader.ts
1 /** 2 * Módulo para gestión de tiles de mapa offline 3 * Permite descargar, almacenar y cargar tiles para su uso sin conexión 4 */ 5 import { browser } from '$app/environment'; 6 7 // Interfaces 8 export interface TileIndex { 9 version: string; 10 lastUpdated: number; 11 tileCount: number; 12 regions: { 13 [regionName: string]: { 14 bounds: { 15 north: number; 16 east: number; 17 south: number; 18 west: number; 19 }; 20 minZoom: number; 21 maxZoom: number; 22 tileCount: number; 23 lastUpdated: number; 24 } 25 }; 26 } 27 28 // Índice de tiles por defecto 29 const initialTileIndex: TileIndex = { 30 version: '1.0', 31 lastUpdated: Date.now(), 32 tileCount: 0, 33 regions: {} 34 }; 35 36 // Cargar índice de tiles 37 export async function loadOfflineTiles(): Promise<TileIndex> { 38 if (!browser) { 39 return initialTileIndex; 40 } 41 42 try { 43 // Cargar desde IndexedDB 44 // TODO: Implementar carga desde almacenamiento persistente 45 return initialTileIndex; 46 } catch (error) { 47 console.error('[tile-loader] Error al cargar índice de tiles:', error); 48 return initialTileIndex; 49 } 50 } 51 52 // Descargar tiles para una región específica 53 export async function downloadTilesForRegion( 54 bounds: any, 55 minZoom: number, 56 maxZoom: number, 57 regionName: string, 58 progressCallback?: (progress: number) => void 59 ): Promise<void> { 60 if (!browser) { 61 throw new Error('Solo disponible en el navegador'); 62 } 63 64 try { 65 // Validar parámetros 66 if (minZoom < 0 || maxZoom > 18 || minZoom > maxZoom) { 67 throw new Error('Niveles de zoom inválidos'); 68 } 69 70 // Calcular tiles necesarios (estimación real) 71 const tilesToDownload = estimateTileCount(bounds, minZoom, maxZoom); 72 73 console.log(`[tile-loader] Descargando ${tilesToDownload} tiles para la región "${regionName}"`); 74 75 // TODO: Implementar descarga real de tiles 76 // Esta es una simulación temporal 77 for (let progress = 0; progress <= 100; progress += 5) { 78 if (progressCallback) { 79 progressCallback(progress); 80 } 81 await new Promise(resolve => setTimeout(resolve, 100)); 82 } 83 84 // Actualizar índice con la nueva región (simulado) 85 const tileIndex = await loadOfflineTiles(); 86 tileIndex.regions[regionName] = { 87 bounds: { 88 north: bounds.getNorth ? bounds.getNorth() : bounds.north, 89 east: bounds.getEast ? bounds.getEast() : bounds.east, 90 south: bounds.getSouth ? bounds.getSouth() : bounds.south, 91 west: bounds.getWest ? bounds.getWest() : bounds.west 92 }, 93 minZoom, 94 maxZoom, 95 tileCount: tilesToDownload, 96 lastUpdated: Date.now() 97 }; 98 99 console.log(`[tile-loader] Descarga simulada completada para la región "${regionName}"`); 100 101 // Notificar éxito 102 if (progressCallback) { 103 progressCallback(100); 104 } 105 } catch (error) { 106 console.error('[tile-loader] Error al descargar tiles:', error); 107 throw error; 108 } 109 } 110 111 // Calcular rango de tiles para un área y nivel de zoom 112 export function getTileRange(bounds: any, zoom: number): { xMin: number, xMax: number, yMin: number, yMax: number } { 113 const northEast = { 114 lat: bounds.getNorth ? bounds.getNorth() : bounds.north, 115 lng: bounds.getEast ? bounds.getEast() : bounds.east 116 }; 117 118 const southWest = { 119 lat: bounds.getSouth ? bounds.getSouth() : bounds.south, 120 lng: bounds.getWest ? bounds.getWest() : bounds.west 121 }; 122 123 // Convertir coordenadas a tiles 124 const neTile = latLngToTile(northEast.lat, northEast.lng, zoom); 125 const swTile = latLngToTile(southWest.lat, southWest.lng, zoom); 126 127 return { 128 xMin: Math.min(swTile.x, neTile.x), 129 xMax: Math.max(swTile.x, neTile.x), 130 yMin: Math.min(swTile.y, neTile.y), 131 yMax: Math.max(swTile.y, neTile.y) 132 }; 133 } 134 135 // Convertir lat/lng a coordenadas de tile 136 export function latLngToTile(lat: number, lng: number, zoom: number): { x: number, y: number } { 137 // Algoritmo estándar para convertir lat/lng a tiles 138 const n = Math.pow(2, zoom); 139 const x = Math.floor((lng + 180) / 360 * n); 140 141 const latRad = lat * Math.PI / 180; 142 const y = Math.floor((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * n); 143 144 return { x, y }; 145 } 146 147 // Estimar número de tiles para un área y rango de zoom 148 export function estimateTileCount(bounds: any, minZoom: number, maxZoom: number): number { 149 let totalTiles = 0; 150 151 for (let z = minZoom; z <= maxZoom; z++) { 152 const range = getTileRange(bounds, z); 153 const tilesAtZoom = (range.xMax - range.xMin + 1) * (range.yMax - range.yMin + 1); 154 totalTiles += tilesAtZoom; 155 } 156 157 return totalTiles; 158 }