UNPKG

@chauffleet/expo-custom-map

Version:

Open source custom map library for Expo/React Native. Use your own tiles without Google Maps, Mapbox, or API keys. Created by ChaufFleet.

285 lines 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TilePreloader = void 0; const utils_1 = require("./utils"); class TilePreloader { constructor(cache) { this.downloadQueue = new Set(); this.currentDownloads = new Set(); this.maxConcurrentDownloads = 4; this.downloadDelay = 50; this.isPreloading = false; this.cache = cache; } /** * Précharge les tuiles pour une région donnée */ async preloadTilesForRegion(latitude, longitude, zoom, options = {}) { const { radius = 2, zoomLevels = [zoom], delay = this.downloadDelay, maxConcurrent = this.maxConcurrentDownloads, } = options; // Annuler le préchargement précédent this.cancelPreloading(); this.downloadDelay = delay; this.maxConcurrentDownloads = maxConcurrent; this.isPreloading = true; this.preloadAbortController = new AbortController(); const tilesToPreload = this.calculateTilesToPreload(latitude, longitude, zoomLevels, radius); const progress = { total: tilesToPreload.length, loaded: 0, errors: 0, progress: 0, }; // Filtrer les tuiles déjà en cache const tilesToDownload = tilesToPreload.filter((tileKey) => { const [z, x, y] = tileKey.replace('tile-', '').split('-').map(Number); return !this.cache.has(x, y, z); }); progress.total = tilesToDownload.length; if (tilesToDownload.length === 0) { progress.progress = 100; this.isPreloading = false; return progress; } // Ajouter les tuiles à la queue de téléchargement tilesToDownload.forEach((tileUrl) => { this.downloadQueue.add(tileUrl); }); // Démarrer le téléchargement des tuiles const downloadPromises = []; for (let i = 0; i < Math.min(this.maxConcurrentDownloads, tilesToDownload.length); i++) { downloadPromises.push(this.processDownloadQueue(progress)); } try { await Promise.all(downloadPromises); } catch (error) { console.warn('Erreur lors du préchargement des tuiles:', error); } finally { this.isPreloading = false; this.downloadQueue.clear(); this.currentDownloads.clear(); } progress.progress = Math.round((progress.loaded / progress.total) * 100); return progress; } /** * Précharge les tuiles autour d'un centre donné */ async preloadTilesAroundCenter(centerLat, centerLon, zoom, radius = 2, tileUrlTemplate) { if (!tileUrlTemplate) { throw new Error('Template d\'URL de tuile requis pour le préchargement'); } const tilesToPreload = []; const centerTile = (0, utils_1.latLonToTile)(centerLat, centerLon, zoom); // Générer les URLs des tuiles dans le rayon spécifié for (let x = centerTile.x - radius; x <= centerTile.x + radius; x++) { for (let y = centerTile.y - radius; y <= centerTile.y + radius; y++) { if (x >= 0 && y >= 0 && x < Math.pow(2, zoom) && y < Math.pow(2, zoom)) { const tileUrl = this.buildTileUrl(tileUrlTemplate, x, y, zoom); tilesToPreload.push(tileUrl); } } } // Précharger les tuiles en parallèle avec limitation const promises = []; const semaphore = this.createSemaphore(this.maxConcurrentDownloads); for (const tileUrl of tilesToPreload) { const [z, x, y] = tileUrl.replace('tile-', '').split('-').map(Number); if (!this.cache.has(x, y, z)) { promises.push(semaphore.acquire().then(async (release) => { try { await this.downloadAndCacheTile(tileUrl); } finally { release(); } })); } } await Promise.all(promises); } /** * Précharge les tuiles le long d'un itinéraire */ async preloadTilesForRoute(coordinates, zoom, corridor = 1000, // corridor en mètres tileUrlTemplate) { if (!tileUrlTemplate) { throw new Error('Template d\'URL de tuile requis pour le préchargement'); } const tilesToPreload = new Set(); // Pour chaque segment de l'itinéraire for (let i = 0; i < coordinates.length - 1; i++) { const start = coordinates[i]; const end = coordinates[i + 1]; // Calculer les points le long du segment const distance = this.calculateDistance(start, end); const steps = Math.ceil(distance / 100); // un point tous les 100 mètres for (let step = 0; step <= steps; step++) { const ratio = step / steps; const lat = start[1] + (end[1] - start[1]) * ratio; const lon = start[0] + (end[0] - start[0]) * ratio; // Calculer le rayon en tuiles basé sur le corridor const metersPerTile = (40075017 * Math.cos((lat * Math.PI) / 180)) / Math.pow(2, zoom + 8); const radiusInTiles = Math.ceil(corridor / metersPerTile); // Ajouter les tuiles dans le corridor const centerTile = (0, utils_1.latLonToTile)(lat, lon, zoom); for (let x = centerTile.x - radiusInTiles; x <= centerTile.x + radiusInTiles; x++) { for (let y = centerTile.y - radiusInTiles; y <= centerTile.y + radiusInTiles; y++) { if (x >= 0 && y >= 0 && x < Math.pow(2, zoom) && y < Math.pow(2, zoom)) { const tileUrl = this.buildTileUrl(tileUrlTemplate, x, y, zoom); tilesToPreload.add(tileUrl); } } } } } // Précharger toutes les tuiles collectées const promises = Array.from(tilesToPreload) .filter(tileUrl => { const [z, x, y] = tileUrl.replace('tile-', '').split('-').map(Number); return !this.cache.has(x, y, z); }) .map(tileUrl => this.downloadAndCacheTile(tileUrl)); await Promise.all(promises); } /** * Annule le préchargement en cours */ cancelPreloading() { if (this.preloadAbortController) { this.preloadAbortController.abort(); } this.isPreloading = false; this.downloadQueue.clear(); this.currentDownloads.clear(); } /** * Vérifie si le préchargement est en cours */ isCurrentlyPreloading() { return this.isPreloading; } /** * Obtient le nombre de tuiles en queue de téléchargement */ getQueueSize() { return this.downloadQueue.size; } // Méthodes privées calculateTilesToPreload(latitude, longitude, zoomLevels, radius) { const tiles = []; zoomLevels.forEach((zoom) => { const centerTile = (0, utils_1.latLonToTile)(latitude, longitude, zoom); for (let x = centerTile.x - radius; x <= centerTile.x + radius; x++) { for (let y = centerTile.y - radius; y <= centerTile.y + radius; y++) { if (x >= 0 && y >= 0 && x < Math.pow(2, zoom) && y < Math.pow(2, zoom)) { const tileUrl = `tile-${zoom}-${x}-${y}`; tiles.push(tileUrl); } } } }); return tiles; } async processDownloadQueue(progress) { while (this.downloadQueue.size > 0 && this.isPreloading) { const tileUrl = this.downloadQueue.values().next().value; if (!tileUrl) break; this.downloadQueue.delete(tileUrl); if (this.currentDownloads.has(tileUrl)) { continue; } this.currentDownloads.add(tileUrl); try { await this.downloadAndCacheTile(tileUrl); progress.loaded++; } catch (error) { progress.errors++; console.warn(`Erreur de téléchargement pour la tuile ${tileUrl}:`, error); } finally { this.currentDownloads.delete(tileUrl); } // Délai entre les téléchargements if (this.downloadDelay > 0) { await new Promise(resolve => setTimeout(resolve, this.downloadDelay)); } // Vérifier si le préchargement a été annulé if (this.preloadAbortController?.signal.aborted) { break; } } } async downloadAndCacheTile(tileUrl) { try { // Simuler le téléchargement d'une tuile // Dans une vraie implémentation, ceci ferait un fetch vers l'URL de la tuile const response = await fetch(tileUrl, { signal: this.preloadAbortController?.signal, }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const tileData = await response.text(); // Extraire les coordonnées de la tuile depuis l'URL const [z, x, y] = tileUrl.replace('tile-', '').split('-').map(Number); // Stocker dans le cache await this.cache.set({ x, y, z, url: tileUrl }, tileData); } catch (error) { if (error instanceof Error && error.name === 'AbortError') { return; // Opération annulée } throw error; } } buildTileUrl(template, x, y, z) { return template .replace('{x}', x.toString()) .replace('{y}', y.toString()) .replace('{z}', z.toString()) .replace('{s}', ['a', 'b', 'c'][Math.floor(Math.random() * 3)]); } calculateDistance(coord1, coord2) { const R = 6371000; // Rayon de la Terre en mètres const lat1Rad = (coord1[1] * Math.PI) / 180; const lat2Rad = (coord2[1] * Math.PI) / 180; const deltaLatRad = ((coord2[1] - coord1[1]) * Math.PI) / 180; const deltaLonRad = ((coord2[0] - coord1[0]) * Math.PI) / 180; const a = Math.sin(deltaLatRad / 2) * Math.sin(deltaLatRad / 2) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.sin(deltaLonRad / 2) * Math.sin(deltaLonRad / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } createSemaphore(maxConcurrent) { let current = 0; const queue = []; return { acquire: () => { return new Promise((resolve) => { const tryAcquire = () => { if (current < maxConcurrent) { current++; resolve(() => { current--; if (queue.length > 0) { const next = queue.shift(); next?.(); } }); } else { queue.push(tryAcquire); } }; tryAcquire(); }); }, }; } } exports.TilePreloader = TilePreloader; //# sourceMappingURL=TilePreloader.js.map