Vector Tiles avec cache IndexedDB
Cette démonstration montre l'utilisation du système de cache IndexedDB pour les calques vectoriels tuilés. Le cache permet de :
- ✅ Réduire drastiquement la bande passante
- ✅ Améliorer les performances de chargement
- ✅ Permettre une utilisation hors-ligne après premier chargement
- ✅ Gérer finement les stratégies de cache par type de ressource
Instructions :
- Ouvrez les DevTools (F12) → Application → IndexedDB
- Naviguez sur la carte pour déclencher le chargement de tuiles
- Observez les databases qui se créent :
karteis-mapviewer-tiles-layer-vector-cached - Utilisez les boutons pour gérer le cache
{
"jsLib": [
"https://karteis.sct.saas-gfi.eu/dist/mapviewer/latest/karteis-mapviewer-var.js"
]
}
<!-- Ne pas utiliser dans votre code -->
<!-- La déclaration du css n'est utile que pour l'affichage sur le site d'exemples -->
<link rel="stylesheet" href="/mapviewer/mapviewer.css">
<!-- Ne pas utiliser dans votre code -->
<!-- Dans nos exemple on utilise font-awesome pour afficher les icones de boutons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<form id="formbackground" style="margin-bottom: 5px;">
<input name="url" placeholder="URL de demande de token geoservice" autocomplete="current-url"/>
<input name="username" placeholder="Nom d'utilisateur" autocomplete="current-username"/>
<input name="password" placeholder="Mot de passe" type="password" autocomplete="current-password"/>
<button type="submit" title="Fond de plan tuilé protégé"><i class="fas fa-plus"></i></button>
</form>
<div style="margin-bottom: 5px;">
<button id="btnStats" title="Afficher les statistiques du cache">
<i class="fas fa-chart-bar"></i> Statistiques du cache
</button>
<button id="btnClearTiles" title="Vider uniquement le cache des tuiles">
<i class="fas fa-eraser"></i> Vider les tuiles
</button>
<button id="btnClearAll" title="Vider tout le cache du layer">
<i class="fas fa-trash"></i> Vider tout le cache
</button>
<button id="btnCleanup" title="Nettoyer les caches orphelins">
<i class="fas fa-broom"></i> Nettoyer orphelins
</button>
<button id="btnManageCache" title="Ouvrir la fenêtre de gestion du cache">
<i class="fas fa-cog"></i> Gérer le cache
</button>
</div>
<div id="info" style="background: #f0f0f0; padding: 10px; margin-bottom: 10px; font-family: monospace; font-size: 12px; display: none;">
<!-- Les statistiques s'afficheront ici -->
</div>
<!-- Le div qui sera utilisé pour injecter la carte -->
<div id="kmap" style="height:450px;" />
// Initialisation de la carte avec ses plugins
const kmapvInstance = (new kmapv.Mapviewer({
mapdiv: document.getElementById('kmap'),
projection: 'EPSG:3857',
defaultCenter: [260000, 5950000],
defaultZoom: 8,
minZoom: 5,
maxZoom: 18,
}))
.use(kmapv.CommonLayer({}))
.use(kmapv.BackgroundLayer({}))
// Ajout du layermanager
kmapvInstance.horizontalToolbar.addLayerManagerTool({
reordering: false
})
// Création de groupe
kmapvInstance.commonLayer.addGroup("FondDePlan", {
title: "Fonds de plan",
zIndex: 0
})
// 1. Fond OSM de base (sans cache pour comparaison)
kmapvInstance.backgroundLayer.addBackgroundLayer(
'background-osm',
'osm',
{
idGroup: "FondDePlan",
title: 'OSM (sans cache)',
zIndex: 0,
visible: true,
baseLayer: true
}
)
// 2. Vector Tile avec cache configuré (utilise un style OpenMapTiles)
const form = document.getElementById("formbackground");
form.addEventListener("submit", addProtectedBackgrounds);
async function addProtectedBackgrounds(e) {
// empêche de recharger
e.stopPropagation();
e.preventDefault();
// converti le formulaire en objet {username, password}
const formData = new FormData(e.target);
const {url,...auth} = Object.fromEntries([...formData.entries()])
await kmapvInstance.backgroundLayer.addBackgroundLayer(
'vector-cached', // identifiant
'vectorTile', // type
{
idGroup: 'FondDePlan', // groupe ou ajouter le calque
title: 'OSM Map Tile', // libellé
zIndex: 1, // z-order
visible: false,
baseLayer: true, // les calques avec baseLayer = true dans un même groupe s'affiche avec un radiobutton (un seul a la fois)
styleUrl: 'https://services.geosphere.fr/maps/tiles/styles/osm-openmaptiles/style.json',
identity: {
id:"geoservice",
type: 'url',
method:'get',
url,
auth,
apply: {
name:'jwt',
},
refreshInterval: 10000
},
cache: {
strategy: kmapv.CACHE_STRATEGY_CACHE_FIRST, // stratégie de cache par défaut pour tous les types
}
})
}
// kmapvInstance.backgroundLayer.addVectorTile(
// 'vector-cached',
// {
// idGroup: "FondDePlan",
// title: 'OpenMapTiles (avec cache)',
// styleUrl: 'https://api.maptiler.com/maps/streets/style.json?key=get_your_own_key',
// visible: true,
// baseLayer: true,
// // Configuration du cache
// cache: {
// enabled: true, // Active le cache (par défaut)
// // Valeurs par défaut (si non spécifiées) :
// // - TTL: 7 jours pour tous les types de ressources
// // - Stratégie: cache-first pour tous les types
// // - maxEntries: 1000
// // Exemple de personnalisation (optionnel) :
// ttl: {
// tiles: 14 * 24 * 60 * 60 * 1000, // 14 jours (au lieu de 7)
// },
// strategy: {
// Style: 'network-first', // Réseau en priorité pour le style (au lieu de cache-first)
// Source: 'network-first', // Réseau en priorité pour la source (au lieu de cache-first)
// },
// // Nombre maximum d'entrées dans le cache
// maxEntries: 2000,
// }
// }
// )
const btnStats = document.getElementById('btnStats')
const btnClearTiles = document.getElementById('btnClearTiles')
const btnClearAll = document.getElementById('btnClearAll')
const btnCleanup = document.getElementById('btnCleanup')
const btnManageCache = document.getElementById('btnManageCache')
const infoDiv = document.getElementById('info')
btnStats.addEventListener('click', async () => {
infoDiv.style.display = 'block'
infoDiv.innerHTML = '<i>Chargement des statistiques...</i>'
try {
const stats = await kmapvInstance.backgroundLayer.getLayerCacheStats('vector-cached')
if (stats) {
let html = `<strong>Statistiques du cache - Layer: ${stats.layerId}</strong><br>`
html += `Total d'entrées: <strong>${stats.total}</strong><br><br>`
if (stats.total > 0) {
html += '<strong>Par type de ressource:</strong><br>'
for (const [type, count] of Object.entries(stats.byType)) {
html += ` - ${type}: ${count} entrée(s)<br>`
}
if (stats.oldestEntry) {
html += `<br>Plus ancienne: ${new Date(stats.oldestEntry).toLocaleString()}<br>`
html += `Plus récente: ${new Date(stats.newestEntry).toLocaleString()}<br>`
}
} else {
html += '<em>Cache vide. Naviguez sur la carte pour charger des tuiles.</em>'
}
infoDiv.innerHTML = html
} else {
infoDiv.innerHTML = '<em>Aucun cache trouvé pour ce layer.</em>'
}
} catch (error) {
infoDiv.innerHTML = `<span style="color:red">Erreur: ${error.message}</span>`
}
})
// Vider uniquement les tuiles
btnClearTiles.addEventListener('click', async () => {
if (confirm('Voulez-vous vraiment vider le cache des tuiles ?')) {
infoDiv.style.display = 'block'
infoDiv.innerHTML = '<i>Nettoyage des tuiles en cours...</i>'
try {
await kmapvInstance.backgroundLayer.clearLayerCache('vector-cached', 'Tiles')
infoDiv.innerHTML = '<span style="color:green">✓ Cache des tuiles vidé avec succès!</span>'
setTimeout(() => { infoDiv.style.display = 'none' }, 3000)
} catch (error) {
infoDiv.innerHTML = `<span style="color:red">Erreur: ${error.message}</span>`
}
}
})
// Vider tout le cache du layer
btnClearAll.addEventListener('click', async () => {
if (confirm('Voulez-vous vraiment vider tout le cache de ce layer ?')) {
infoDiv.style.display = 'block'
infoDiv.innerHTML = '<i>Nettoyage complet en cours...</i>'
try {
await kmapvInstance.backgroundLayer.clearLayerCache('vector-cached')
infoDiv.innerHTML = '<span style="color:green">✓ Cache complet vidé avec succès!</span>'
setTimeout(() => { infoDiv.style.display = 'none' }, 3000)
} catch (error) {
infoDiv.innerHTML = `<span style="color:red">Erreur: ${error.message}</span>`
}
}
})
// Nettoyer les caches orphelins
btnCleanup.addEventListener('click', async () => {
infoDiv.style.display = 'block'
infoDiv.innerHTML = '<i>Recherche des caches orphelins...</i>'
try {
const orphans = await kmapvInstance.backgroundLayer.cleanupOrphanCaches()
if (orphans.length > 0) {
infoDiv.innerHTML = `<span style="color:green">✓ ${orphans.length} cache(s) orphelin(s) nettoyé(s):<br>${orphans.join(', ')}</span>`
} else {
infoDiv.innerHTML = '<span style="color:green">✓ Aucun cache orphelin trouvé.</span>'
}
setTimeout(() => { infoDiv.style.display = 'none' }, 5000)
} catch (error) {
infoDiv.innerHTML = `<span style="color:red">Erreur: ${error.message}</span>`
}
})
// Ouvrir la fenêtre de gestion du cache
btnManageCache.addEventListener('click', async () => {
try {
await kmapvInstance.backgroundLayer.showCacheManagerDialog('vector-cached')
// Rafraîchir les stats après fermeture de la dialog
if (infoDiv.style.display !== 'none') {
btnStats.click()
}
} catch (error) {
infoDiv.style.display = 'block'
infoDiv.innerHTML = `<span style="color:red">Erreur: ${error.message}</span>`
}
})
console.log('💡 Astuce: Ouvrez les DevTools (F12) → Application → IndexedDB pour voir les databases de cache en temps réel (ex: karteis-mapviewer-tiles-layer-vector-cached)')
#formbackground {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
#formbackground input {
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 12px;
flex: 1;
min-width: 150px;
}
#formbackground input:focus {
outline: none;
border-color: #0066cc;
box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.1);
}
button {
padding: 8px 12px;
margin-right: 5px;
border: 1px solid #ccc;
background: white;
cursor: pointer;
border-radius: 4px;
font-size: 12px;
}
button:hover {
background: #f0f0f0;
}
button i:not(:last-child) {
margin-right: 5px;
}
Notes importantes
Préconfigurations disponibles
Le module exporte des constantes pour simplifier la configuration :
// Import dans votre code
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
CACHE_STRATEGY_NETWORK_FIRST // network-first pour tous
} from '@karteis/mapviewer'
// Utilisation
kmapvInstance.backgroundLayer.addVectorTile('my-layer', {
styleUrl: 'https://...',
cache: {
ttl: CACHE_TTL_UNLIMITED, // Tous les types → 100 ans
strategy: CACHE_STRATEGY_NETWORK_FIRST, // Toujours à jour
},
})
// Ou par string (sans import)
kmapvInstance.backgroundLayer.addVectorTile('my-layer', {
styleUrl: 'https://...',
cache: {
ttl: 'CACHE_TTL_UNLIMITED', // String
strategy: 'CACHE_STRATEGY_NETWORK_FIRST', // String
},
})
// Ou par nombre de minutes (ultra-simplifié)
kmapvInstance.backgroundLayer.addVectorTile('my-layer', {
styleUrl: 'https://...',
cache: {
ttl: 60, // 60 minutes pour tous les types
},
})
Astuce : Les options ttl et strategy acceptent plusieurs formats :
ttl: objet, string ('CACHE_TTL_UNLIMITED', 'CACHE_TTL_ONE_MONTH'), ou number (minutes)strategy: objet ou string ('CACHE_STRATEGY_CACHE_FIRST', 'CACHE_STRATEGY_NETWORK_FIRST')
Structure du cache dans IndexedDB
Le cache est organisé comme suit :
- Une database par layer :
{baseDbName}-layer-{idLayer}(ex:karteis-mapviewer-tiles-layer-my-layer) - Object Store :
keyval(store par défaut d'idb-keyval) - Clés : Format
{ResourceType}:{url}(ex:Tiles:https://.../{z}/{x}/{y}.pbf)
Types de ressources cachés
- Tiles : Les tuiles vectorielles (.pbf)
- Style : Le fichier de style MapBox (.json)
- Source : Les métadonnées de source
- Sprite : Les spritesheets de symboles
- SpriteImage : Les images des sprites
- GeoJSON : Les données GeoJSON
Stratégies de cache
network-first (défaut pour tous les types)
1. Cherche dans le cache
2. Si trouvé ET non expiré → retourne depuis le cache
3. Sinon → fetch depuis le réseau
4. Met en cache pour la prochaine fois
network-first (optionnel pour données très dynamiques)
1. Fetch depuis le réseau
2. Si succès → met en cache et retourne
3. Si échec → cherche dans le cache comme fallback
4. Si pas en cache → propage l'erreur
Inspection avec DevTools
Pour inspecter le cache :
- F12 → Application (Chrome) ou Storage (Firefox)
- IndexedDB →
karteis-mapviewer-tiles - Cliquer sur
layer-vector-cached - Voir toutes les entrées avec leur contenu
Vous pouvez :
- Voir les URLs mises en cache
- Inspecter le contenu (ArrayBuffer)
- Voir les timestamps et TTL
- Supprimer manuellement des entrées
Désactiver le cache
Pour désactiver le cache sur un layer :
kmapvInstance.backgroundLayer.addVectorTile('my-layer', {
styleUrl: 'https://...',
cache: {
enabled: false, // Cache désactivé
},
})
Avantages du cache
- 📉 Réduction de bande passante : 60-90% après premier chargement
- ⚡ Performances : Chargement instantané depuis IndexedDB (~1ms)
- 🔌 Mode hors-ligne : Fonctionne sans réseau après mise en cache
- 💰 Économie de coûts : Moins de requêtes API = moins de frais
Cas d'usage
Application grand public : Les défauts (7 jours, cache-first) sont optimaux. Augmenter TTL à 30 jours si nécessaire Application métier : Override en TTL court (1h) + network-first si données très sensibles PWA/Mobile : Augmenter TTL à 90 jours, pré-chargement des zones
Voir aussi
- Cookbook : Vector Tiles avec cache - Documentation complète
- Cookbook : Backgrounds avec authentification - Combiner cache + authentification
