@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.
295 lines • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TileCache = void 0;
// Import conditionnel d'AsyncStorage
let AsyncStorage;
try {
AsyncStorage = require('@react-native-async-storage/async-storage').default;
}
catch (error) {
// AsyncStorage n'est pas disponible (environnement Node.js, etc.)
console.warn('AsyncStorage not available, cache will be memory-only');
AsyncStorage = undefined;
}
class TileCache {
constructor(maxSizeMB = 100, maxAge = 7 * 24 * 60 * 60 * 1000, persistent = true) {
this.cache = new Map();
this.storagePrefix = '@expo-custom-map/tiles/';
this.maxSizeMB = maxSizeMB;
this.maxAge = maxAge;
// Vérifier si nous sommes dans un environnement React Native
const isReactNativeEnvironment = typeof window !== 'undefined' &&
window.navigator?.product === 'ReactNative';
this.persistent = persistent && isReactNativeEnvironment;
if (this.persistent) {
this.loadFromStorage().catch((error) => {
console.warn('Erreur lors du chargement du cache de tuiles:', error);
// En cas d'erreur, désactiver la persistance pour cette session
this.persistent = false;
});
}
}
/**
* Créer un cache en mode mémoire uniquement (sans persistance)
* Utile pour les environnements de test ou non-React Native
*/
static createMemoryOnlyCache(maxSizeMB = 100, maxAge = 7 * 24 * 60 * 60 * 1000) {
return new TileCache(maxSizeMB, maxAge, false);
}
/**
* Générer une clé de cache pour une tuile
*/
getTileKey(x, y, z) {
return `${z}-${x}-${y}`;
}
/**
* Calculer la taille actuelle du cache en MB
*/
getCurrentSizeMB() {
let totalSize = 0;
for (const tile of this.cache.values()) {
totalSize += tile.size;
}
return totalSize / (1024 * 1024);
}
/**
* Nettoyer le cache selon la politique LRU
*/
cleanup() {
const now = Date.now();
const maxSizeBytes = this.maxSizeMB * 1024 * 1024;
// Supprimer les tuiles expirées
for (const [key, tile] of this.cache.entries()) {
if (now - tile.timestamp > this.maxAge) {
this.cache.delete(key);
if (this.persistent) {
AsyncStorage.removeItem(this.storagePrefix + key).catch(() => { });
}
}
}
// Si toujours trop gros, supprimer les moins récemment utilisées
let currentSize = 0;
for (const tile of this.cache.values()) {
currentSize += tile.size;
}
if (currentSize > maxSizeBytes) {
const sortedTiles = Array.from(this.cache.entries()).sort(([, a], [, b]) => a.lastAccess - b.lastAccess);
for (const [key, tile] of sortedTiles) {
this.cache.delete(key);
currentSize -= tile.size;
if (this.persistent) {
AsyncStorage.removeItem(this.storagePrefix + key).catch(() => { });
}
if (currentSize <= maxSizeBytes * 0.8) { // Nettoyer jusqu'à 80% pour éviter le thrashing
break;
}
}
}
}
/**
* Charger le cache depuis le stockage persistant
*/
async loadFromStorage() {
if (!this.persistent) {
return;
}
try {
// Vérifier si AsyncStorage est disponible
if (typeof AsyncStorage === 'undefined') {
throw new Error('AsyncStorage is not available');
}
const keys = await AsyncStorage.getAllKeys();
const tileKeys = keys.filter((key) => key.startsWith(this.storagePrefix));
const items = await AsyncStorage.multiGet(tileKeys);
for (const [key, value] of items) {
if (value) {
try {
const cachedTile = JSON.parse(value);
const tileKey = key.replace(this.storagePrefix, '');
// Vérifier si la tuile n'est pas expirée
if (Date.now() - cachedTile.timestamp <= this.maxAge) {
this.cache.set(tileKey, cachedTile);
}
else {
// Supprimer la tuile expirée
AsyncStorage.removeItem(key).catch(() => { });
}
}
catch (error) {
// Supprimer les données corrompues
AsyncStorage.removeItem(key).catch(() => { });
}
}
}
}
catch (error) {
console.warn('Erreur lors du chargement du cache de tuiles:', error);
}
}
/**
* Sauvegarder une tuile dans le stockage persistant
*/
async saveToStorage(key, tile) {
if (!this.persistent)
return;
try {
// Vérifier si AsyncStorage est disponible
if (typeof AsyncStorage === 'undefined') {
return;
}
await AsyncStorage.setItem(this.storagePrefix + key, JSON.stringify(tile));
}
catch (error) {
console.warn('Erreur lors de la sauvegarde de la tuile:', error);
}
}
/**
* Ajouter une tuile au cache
*/
async set(tile, data) {
const key = this.getTileKey(tile.x, tile.y, tile.z);
const size = data.length;
const now = Date.now();
const cachedTile = {
url: tile.url,
data,
size,
timestamp: now,
accessCount: 1,
lastAccess: now,
};
this.cache.set(key, cachedTile);
// Sauvegarder de manière asynchrone
if (this.persistent) {
this.saveToStorage(key, cachedTile).catch(() => { });
}
// Nettoyer si nécessaire
this.cleanup();
}
/**
* Récupérer une tuile du cache
*/
get(x, y, z) {
const key = this.getTileKey(x, y, z);
const tile = this.cache.get(key);
if (tile) {
// Mettre à jour les statistiques d'accès
tile.accessCount++;
tile.lastAccess = Date.now();
return tile.data;
}
return null;
}
/**
* Vérifier si une tuile existe dans le cache
*/
has(x, y, z) {
const key = this.getTileKey(x, y, z);
return this.cache.has(key);
}
/**
* Supprimer une tuile du cache
*/
async delete(x, y, z) {
const key = this.getTileKey(x, y, z);
this.cache.delete(key);
if (this.persistent) {
try {
await AsyncStorage.removeItem(this.storagePrefix + key);
}
catch (error) {
console.warn('Erreur lors de la suppression de la tuile:', error);
}
}
}
/**
* Vider tout le cache
*/
async clear() {
this.cache.clear();
if (this.persistent) {
try {
const keys = await AsyncStorage.getAllKeys();
const tileKeys = keys.filter((key) => key.startsWith(this.storagePrefix));
await AsyncStorage.multiRemove(tileKeys);
}
catch (error) {
console.warn('Erreur lors de la suppression du cache:', error);
}
}
}
/**
* Obtenir les statistiques du cache
*/
getStats() {
const currentSizeMB = this.getCurrentSizeMB();
let totalRequests = 0;
let totalHits = 0;
for (const tile of this.cache.values()) {
totalRequests += tile.accessCount;
totalHits += tile.accessCount;
}
return {
size: this.cache.size,
currentSizeMB: Math.round(currentSizeMB * 100) / 100,
maxSizeMB: this.maxSizeMB,
hitRate: totalRequests > 0 ? totalHits / totalRequests : 0,
};
}
/**
* Définir la taille maximale du cache
*/
setMaxSize(maxSizeMB) {
this.maxSizeMB = maxSizeMB;
this.cleanup();
}
/**
* Définir l'âge maximum des tuiles
*/
setMaxAge(maxAge) {
this.maxAge = maxAge;
this.cleanup();
}
/**
* Précharger une zone de tuiles
*/
async preloadArea(bounds, urlTemplate, onProgress) {
const { minX, maxX, minY, maxY, z } = bounds;
const total = (maxX - minX + 1) * (maxY - minY + 1);
let loaded = 0;
const promises = [];
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
if (!this.has(x, y, z)) {
const url = urlTemplate
.replace('{x}', x.toString())
.replace('{y}', y.toString())
.replace('{z}', z.toString());
const promise = fetch(url)
.then(response => response.text())
.then(data => {
const tile = { x, y, z, url };
return this.set(tile, data);
})
.then(() => {
loaded++;
onProgress?.(loaded, total);
})
.catch(error => {
console.warn(`Erreur lors du préchargement de la tuile ${x},${y},${z}:`, error);
loaded++;
onProgress?.(loaded, total);
});
promises.push(promise);
}
else {
loaded++;
onProgress?.(loaded, total);
}
}
}
await Promise.all(promises);
}
}
exports.TileCache = TileCache;
//# sourceMappingURL=TileCache.js.map