/ src / lib / maps / utils / tile-loader.ts
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  }