Calques vectoriels tuilés avec cache

Vue d'ensemble

Le mapviewer intègre un système de cache IndexedDB pour les calques vectoriels tuilés (VectorTile). Ce cache permet de réduire considérablement la bande passante utilisée et d'améliorer les performances en mode hors-ligne ou avec une connexion dégradée.

Chaque layer dispose de sa propre database IndexedDB, garantissant une isolation parfaite entre les calques.

Fonctionnalités

  • ✅ Cache par layer : Chaque calque a son propre store IndexedDB
  • ✅ Stratégies configurables : cache-first ou network-first par type de ressource
  • ✅ TTL personnalisable : Durée de vie configurable par type de ressource
  • ✅ Éviction automatique : Suppression des entrées les plus anciennes quand le cache est plein
  • ✅ Gestion identité : Compatible avec le système d'identité existant
  • ✅ Fallback automatique : Utilise le cache si le réseau est indisponible
  • ✅ API de gestion : Méthodes pour nettoyer, inspecter et gérer le cache

Configuration de base

Activer le cache (par défaut)

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  // Le cache est activé par défaut
})

Désactiver le cache

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    enabled: false, // Désactive le cache pour ce layer
  },
})

Configuration avancée

Personnaliser les durées de vie (TTL)

Par défaut, toutes les ressources ont un TTL de 7 jours. Vous pouvez personnaliser ces valeurs :

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    ttl: {
      tiles: 14 * 24 * 60 * 60 * 1000,  // 14 jours pour les tuiles (défaut: 7 jours)
      style: 30 * 60 * 1000,              // 30 minutes pour le style (défaut: 7 jours)
      source: 30 * 60 * 1000,             // 30 minutes pour la source (défaut: 7 jours)
      sprite: 1 * 60 * 60 * 1000,         // 1 heure pour les sprites (défaut: 7 jours)
      spriteImage: 1 * 60 * 60 * 1000,    // 1 heure pour les images sprite (défaut: 7 jours)
      geoJson: 30 * 60 * 1000,            // 30 minutes pour le GeoJSON (défaut: 7 jours)
    },
  },
})

Configurer les stratégies de cache

Deux stratégies disponibles :

  • cache-first : Utilise le cache en priorité, fetch le réseau si absent
  • network-first : Fetch le réseau en priorité, utilise le cache en fallback (défaut)

Par défaut, toutes les ressources utilisent network-first. Pour des données hors-ligne ou peu dynamiques, vous pouvez basculer en cache-first :

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    strategy: {
      Tiles: 'cache-first',      // Tuiles : cache en priorité pour économie bande passante
      Style: 'network-first',    // Style : réseau en priorité (défaut)
      Source: 'network-first',   // Source : réseau en priorité (défaut)
      Sprite: 'cache-first',     // Sprites : cache en priorité pour économie bande passante
      SpriteImage: 'cache-first', // Cache en priorité pour économie bande passante
      GeoJSON: 'network-first',  // GeoJSON : réseau en priorité (défaut)
    },
  },
})

Utiliser les préconfigurations

Le module exporte des constantes prédéfinies pour faciliter la configuration :

import {
  CACHE_TTL_UNLIMITED,         // Objet TTL 100 ans pour tous les types
  CACHE_TTL_ONE_MONTH,         // Objet TTL 30 jours pour tous les types
  CACHE_STRATEGY_CACHE_FIRST,  // cache-first pour tous les types
  CACHE_STRATEGY_NETWORK_FIRST // network-first pour tous les types
} from '@karteis/mapviewer'

// Exemple 1 : Cache très long terme (PWA hors-ligne)
viewer.addVectorTile('offline-layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: CACHE_TTL_UNLIMITED,  // Tous les types → 100 ans
    strategy: CACHE_STRATEGY_CACHE_FIRST,
  },
})

// Exemple 2 : Toujours réseau en priorité (données temps réel)
viewer.addVectorTile('realtime-layer', {
  styleUrl: 'https://realtime.example.com/style.json',
  cache: {
    ttl: CACHE_TTL_ONE_MONTH,  // Tous les types → 30 jours
    strategy: CACHE_STRATEGY_NETWORK_FIRST,
  },
})

// Exemple 3 : Override partiel sur les préconfigurations
viewer.addVectorTile('mixed-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    ttl: {
      ...CACHE_TTL_ONE_MONTH,       // Base : 30 jours pour tous
      style: 1 * 60 * 60 * 1000,  // Override : style → 1h seulement
    },
    strategy: CACHE_STRATEGY_CACHE_FIRST,
  },
})

Utilisation par string (simplification)

Vous pouvez aussi passer le nom de la constante en string, ce qui simplifie la configuration lorsqu'elle provient d'un fichier de configuration JSON/YAML :

// Avec string (sans import nécessaire)
viewer.addVectorTile('layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: 'CACHE_TTL_UNLIMITED',              // String au lieu de l'objet
    strategy: 'CACHE_STRATEGY_CACHE_FIRST',  // String au lieu de l'objet
  },
})

// Équivalent à :
viewer.addVectorTile('layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: CACHE_TTL_UNLIMITED,              // Import nécessaire
    strategy: CACHE_STRATEGY_CACHE_FIRST,  // Import nécessaire
  },
})

Utilisation par nombre de minutes (ultra-simplifié)

Pour une configuration rapide, vous pouvez passer directement un nombre de minutes qui sera appliqué à tous les types de ressources :

// TTL de 60 minutes (1 heure) pour tous les types
viewer.addVectorTile('layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: 60,  // 60 minutes = 1 heure pour Tiles, Style, Source, Sprite, etc.
  },
})

// TTL de 1440 minutes (24 heures / 1 jour)
viewer.addVectorTile('layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: 1440,  // 24 heures
  },
})

// TTL de 10080 minutes (7 jours) - équivalent au défaut
viewer.addVectorTile('layer', {
  styleUrl: 'https://tiles.example.com/style.json',
  cache: {
    ttl: 10080,  // 7 jours
  },
})

Formats acceptés pour ttl :

  • Objet : { tiles: ms, style: ms, ... } - Précision maximale
  • String : 'CACHE_TTL_UNLIMITED' ou 'CACHE_TTL_ONE_MONTH' - Préconfigurations
  • Number : 60 - Minutes pour tous les types (ultra-simplifié)

Formats acceptés pour strategy :

  • Objet : { Tiles: 'cache-first', Style: 'network-first', ... } - Précision maximale
  • String : 'CACHE_STRATEGY_CACHE_FIRST' ou 'CACHE_STRATEGY_NETWORK_FIRST' - Préconfigurations

Gestion automatique du quota (nouveau !)

Par défaut, le cache est illimité (maxEntries: Infinity) et s'adapte automatiquement au quota IndexedDB disponible dans le navigateur.

// Comportement par défaut : cache illimité avec éviction automatique
viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    enabled: true,  // maxEntries = Infinity par défaut
    // Le cache grandit jusqu'à 90% du quota, puis éviction automatique
  },
})

Fonctionnement :

  • Le cache vérifie le quota IndexedDB toutes les 50 écritures
  • À 90% du quota, supprime automatiquement 30% des entrées les plus anciennes
  • Optimisé pour minimiser l'impact sur les performances

Personnaliser la gestion du quota

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    quotaThreshold: 0.85,         // Déclencher l'éviction à 85% du quota (défaut: 0.9)
    quotaEvictionPercent: 0.4,    // Supprimer 40% lors de l'éviction (défaut: 0.3)
    quotaCheckInterval: 100,      // Vérifier le quota toutes les 100 écritures (défaut: 50)
  },
})

Limiter manuellement le nombre d'entrées

Si vous préférez l'ancien comportement avec limite fixe :

viewer.addVectorTile('mon-layer', {
  styleUrl: 'https://example.com/style.json',
  cache: {
    maxEntries: 5000, // Limite fixe à 5000 entrées (désactive la gestion automatique du quota)
  },
})

Exemple complet avec identité

viewer.addVectorTile('secure-layer', {
  styleUrl: 'https://secure.example.com/style.json',

  // Configuration de l'identité (comme avant)
  identity: {
    type: 'bearer',
    token: 'mon-token-secret',
  },

  // Configuration du cache
  // Note: avec les défauts (7 jours, network-first), pas besoin de tout spécifier
  cache: {
    enabled: true,
    maxEntries: 2000,
    // TTL: 7 jours par défaut pour toutes les ressources
    // strategy: network-first par défaut pour toutes les ressources
  },

// Nettoyer uniquement le style
await viewer.backgroundLayer.clearLayerCache('mon-layer', 'Style')

Obtenir les statistiques du cache

// Statistiques d'un layer spécifique
const stats = await viewer.backgroundLayer.getLayerCacheStats('mon-layer')
console.log(stats)
// {
//   layerId: 'mon-layer',
//   total: 1523,
//   maxEntries: Infinity,
//   byType: {
//     Tiles: 1500,
//     Style: 1,
//     Source: 1,
//     Sprite: 20,
//     SpriteImage: 1
//   },
//   oldestEntry: '2026-08-05T10:23:45.123Z',
//   newestEntry: '2026-08-12T14:30:12.456Z',
//   quota: {
//     usage: 45678912,              // Octets utilisés
//     quota: 1234567890,            // Quota total
//     usagePercent: 0.037,          // 3.7% du quota
//     available: 1188888978,        // Octets disponibles
//     usageFormatted: '43.56 MB',
//     quotaFormatted: '1.15 GB',
//     availableFormatted: '1.11 GB'
//   }
// }

// Statistiques de tous les layers actifs
const allStats = await viewer.backgroundLayer.getAllLayersCacheStats()
console.log(allStats)

Afficher la fenêtre de gestion du cache

Une interface graphique est disponible pour visualiser et gérer le cache d'un layer :

// Ouvre un panneau flottant avec les statistiques et un bouton pour effacer le cache
await viewer.backgroundLayer.showCacheManagerDialog('mon-layer')

Cette fenêtre affiche :

  • ✅ Nombre total d'entrées et limite configurée
  • ✅ Répartition par type de ressource (Tiles, Style, etc.)
  • ✅ Utilisation du quota IndexedDB (usage, pourcentage, disponible)
  • ✅ Plage temporelle (entrées les plus anciennes et récentes)
  • ✅ Bouton pour effacer tout le cache du layer
  • ✅ Fermeture avec le bouton de fermeture

Le panneau s'affiche en haut à droite de la carte sans masquer le reste de l'application.

Lister les layers en cache

// Liste tous les layers ayant un cache (même supprimés)
const cachedLayers = await viewer.backgroundLayer.listCachedLayers()
console.log(cachedLayers)
// ['layer-1', 'layer-2', 'old-layer-deleted']

Nettoyer les caches orphelins

// Supprime automatiquement les caches de layers qui ne sont plus sur la carte
const orphans = await viewer.backgroundLayer.cleanupOrphanCaches()
console.log(`${orphans.length} cache(s) orphelin(s) supprimé(s)`)

Inspection avec DevTools

Le cache peut être inspecté directement dans les DevTools du navigateur :

  1. Ouvrir les DevTools (F12)
  2. Aller dans l'onglet Application (Chrome) ou Storage (Firefox)
  3. Sélectionner IndexedDB
  4. Chaque database correspond à un layer : karteis-mapviewer-tiles-layer-{idLayer}

Vous pouvez :

  • ✅ Voir toutes les entrées en cache
  • ✅ Inspecter le contenu (URL, type, timestamp, données)
  • ✅ Supprimer manuellement des entrées
  • ✅ Vider un store complet

Cas d'usage recommandés

🌍 Application cartographique grand public

viewer.addVectorTile('fond-osm', {
  styleUrl: 'https://tiles.example.com/osm/style.json',
  cache: {
    // Les défauts (7 jours, cache-first) sont déjà optimaux !
    // Optionnel : augmenter le TTL et le cache
    ttl: { tiles: 30 * 24 * 60 * 60 * 1000 }, // 30 jours si besoin
    maxEntries: 10000,
  },
})

Avantages : Réduit drastiquement la bande passante, excellent pour mobile

🏗️ Application métier avec données sensibles

viewer.addVectorTile('cadastre-secure', {
  styleUrl: 'https://secure.example.com/cadastre/style.json',
  identity: { type: 'bearer', token: userToken },
  cache: {
    // Override des défauts pour données très dynamiques
    ttl: { tiles: 1 * 60 * 60 * 1000 }, // 1 heure (au lieu de 7 jours)
    strategy: { Tiles: 'network-first' }, // Toujours à jour (au lieu de cache-first)
    maxEntries: 500,
  },
})

Avantages : Cache court pour données sensibles, fallback si réseau coupé

📱 Application mobile/PWA hors-ligne

viewer.addVectorTile('offline-layer', {
  styleUrl: 'https://tiles.example.com/offline/style.json',
  cache: {
    // Les défauts sont déjà bons ! Optionnel : augmenter TTL et cache
    ttl: { tiles: 90 * 24 * 60 * 60 * 1000 }, // 90 jours (au lieu de 7)
    maxEntries: 20000, // Cache très large
  },
})

// Pré-charger les tuiles en arrière-plan
// (navigation automatique de la carte pour déclencher les requêtes)

Avantages : Fonctionne hors-ligne après premier chargement

Notes techniques

Taille du cache

  • Chaque tuile vectorielle (PBF) pèse généralement 5-50 KB
  • 1000 tuiles ≈ 10-30 MB dans IndexedDB
  • Les navigateurs allouent généralement 50-100+ MB pour IndexedDB

Performances

  • Cache hit : ~0.5-2 ms (lecture IndexedDB)
  • Cache miss : temps réseau + ~1-3 ms (écriture IndexedDB)
  • Impact négligeable sur le rendu

Compatibilité

  • ✅ Chrome/Edge 58+
  • ✅ Firefox 52+
  • ✅ Safari 11+
  • ✅ iOS Safari 11+
  • ✅ Android WebView 58+

Dépannage

Le cache ne semble pas fonctionner

  1. Vérifier dans DevTools → Network si les requêtes sont faites
  2. Vérifier dans DevTools → Application → IndexedDB si les données sont stockées
  3. Vérifier la console pour les warnings
  4. Vérifier que cache.enabled n'est pas à false

Le cache est trop volumineux

  1. Réduire cache.maxEntries
  2. Réduire les TTL
  3. Nettoyer régulièrement avec cleanupOrphanCaches()

Le cache ne se vide pas à la suppression du layer

C'est normal ! Le cache persiste pour permettre le mode hors-ligne.

Pour nettoyer :

await viewer.backgroundLayer.clearLayerCache('ancien-layer')
// ou
await viewer.backgroundLayer.cleanupOrphanCaches()

Voir aussi

Dernière mise à jour:
Contributors: David Hequet