A comprehensive TypeScript library for MapLibre GL JS and Mapbox GL JS that enables complete offline map functionality with vector/raster tiles, styles, fonts, sprites, and glyphs stored in IndexedDB. Features include Mapbox Standard style support, advanced analytics, intelligent cleanup, i18n (English & Arabic with RTL), and a modern glassmorphic UI control.
Download regions, load offline styles, and navigate maps without an internet connection.
- ๐บ๏ธ Complete Offline Maps: Download and store entire map regions with polygon-based selection
- ๐ฏ Smart Tile Management: Efficient vector/raster tile downloading, caching, and retrieval with zoom-level optimization
- ๐ค Font & Glyph Support: Comprehensive font and glyph management with Unicode range support
- ๐จ Sprite Management: Handle map sprites and icons offline with multi-resolution support (@1x, @2x)
- ๐ Real-time Analytics: Detailed storage analytics, performance metrics, and optimization recommendations
- ๐ mapbox:// Protocol Resolution: Automatic resolution of
mapbox://style, source, sprite, and glyph URLs - ๐๏ธ Mapbox Standard Style: Full support including 3D models, raster-dem terrain, and import-based style resolution
- ๐ Day/Night Light Presets: Toggle between day and night lighting in Mapbox Standard style
- ๐ง๏ธ Weather Controls: Rain and snow effects for Mapbox Standard style
- ๐ Auto-detection: Automatically detects whether a style is Mapbox or MapLibre and applies the correct handling
- ๐ผ๏ธ Glassmorphic Design: Beautiful modern interface with glassmorphism effects and smooth animations
- ๐ Dark/Light Theme: Automatic theme switching with system preference detection
- ๐ Polygon Drawing: Interactive polygon tool for precise region selection
- ๐ Live Progress: Real-time download progress with detailed statistics
- ๐ฏ Region Management: Easy-to-use interface for managing multiple offline regions
- โก Responsive: Mobile-friendly design that adapts to all screen sizes
- ๐ Internationalization: English and Arabic language support with full RTL layout
- ๐พ IndexedDB Storage: Efficient browser storage with quota management and transaction safety
- ๐ง Full TypeScript: Complete type definitions, interfaces, and compile-time safety
- โก Performance Optimized: Concurrent downloads, async/await patterns, and memory-efficient operations
- ๐งน Intelligent Cleanup: Smart cleanup of expired data with customizable policies
- ๐ Robust Error Handling: Comprehensive error recovery, retry mechanisms, and graceful degradation
- ๐ Enhanced Logging: Detailed debugging with zoom-level specific logging (Z12 tracking)
npm install map-gl-offline
# or
yarn add map-gl-offline
# or
pnpm add map-gl-offlineFor use via <script> tag, the library is available as the mapgloffline global (similar to mapboxgl and maplibregl):
<script src="https://unpkg.com/map-gl-offline/dist/index.umd.js"></script>
<link rel="stylesheet" href="https://unpkg.com/map-gl-offline/style.css" />
<script>
const manager = new mapgloffline.OfflineMapManager();
const control = new mapgloffline.OfflineManagerControl(manager, {
styleUrl: 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY',
});
map.addControl(control, 'top-right');
</script>For development or when using Maptiler styles, create a .env file:
VITE_MAPTILER_API_KEY=your_api_key_hereGet a free API key from Maptiler.
For Mapbox styles, you will also need a Mapbox access token from Mapbox.
import maplibregl from 'maplibre-gl';
import { OfflineMapManager, OfflineManagerControl } from 'map-gl-offline';
import 'maplibre-gl/dist/maplibre-gl.css';
import 'map-gl-offline/style.css';
const styleUrl = 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_API_KEY';
const map = new maplibregl.Map({
container: 'map',
style: styleUrl,
center: [-74.006, 40.7128],
zoom: 12,
});
const offlineManager = new OfflineMapManager();
map.on('load', () => {
const control = new OfflineManagerControl(offlineManager, {
styleUrl,
theme: 'dark',
showBbox: true,
mapLib: maplibregl, // enables idb:// protocol in web workers
});
map.addControl(control, 'top-right');
});Mapbox GL JS v3 does not support addProtocol, so offline tile serving uses a Service Worker fallback. You need to copy idb-offline-sw.js to your project's public directory so it is served at the root (/idb-offline-sw.js).
Option 1: CLI (recommended)
npx map-gl-offline initOption 2: Vite plugin
// vite.config.js
import { offlineSwPlugin } from 'map-gl-offline/vite-plugin';
export default defineConfig({
plugins: [offlineSwPlugin()],
});Option 3: Manual copy
cp node_modules/map-gl-offline/dist/idb-offline-sw.js public/idb-offline-sw.jsimport mapboxgl from 'mapbox-gl';
import { OfflineMapManager, OfflineManagerControl } from 'map-gl-offline';
import 'mapbox-gl/dist/mapbox-gl.css';
import 'map-gl-offline/style.css';
mapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/standard',
center: [-74.006, 40.7128],
zoom: 12,
});
const offlineManager = new OfflineMapManager();
map.on('load', () => {
const control = new OfflineManagerControl(offlineManager, {
styleUrl: 'mapbox://styles/mapbox/standard',
theme: 'dark',
showBbox: true,
accessToken: mapboxgl.accessToken,
// No mapLib needed - Mapbox GL JS v3 lacks addProtocol,
// so the library auto-registers a Service Worker fallback
});
map.addControl(control, 'top-right');
});Note: MapLibre GL JS has built-in
addProtocolsupport, so it does not need the Service Worker. Only Mapbox GL JS requires this extra step.
The UI control provides:
- ๐ Polygon drawing for region selection
- ๐ Download progress tracking
- ๐๏ธ Region management (view, delete)
- ๐ Theme toggle (dark/light mode)
- ๐ Storage analytics
- ๐ Language switcher (English / Arabic with RTL)
import { OfflineMapManager } from 'map-gl-offline';
// Initialize the offline manager
const offlineManager = new OfflineMapManager();
// Download a map region for offline use
await offlineManager.addRegion({
id: 'downtown',
name: 'Downtown Area',
bounds: [
[-74.0559, 40.7128], // Southwest [lng, lat]
[-74.0059, 40.7628], // Northeast [lng, lat]
],
minZoom: 10,
maxZoom: 16,
styleUrl: 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY',
onProgress: progress => {
console.log(`Progress: ${progress.percentage}%`);
console.log(`Current: ${progress.message}`);
},
});
// Retrieve a stored region
const region = await offlineManager.getStoredRegion('downtown');
if (region) {
console.log(`Region: ${region.name}, created: ${new Date(region.created).toLocaleDateString()}`);
}
// List all regions
const regions = await offlineManager.listStoredRegions();
console.log(`Stored regions:`, regions);
// Delete a region
await offlineManager.deleteRegion('downtown');// Get comprehensive storage analytics
const analytics = await offlineManager.getComprehensiveStorageAnalytics();
console.log(`Total storage: ${analytics.totalStorageSize} bytes`);
console.log(`Tiles: ${analytics.tiles.count} (${analytics.tiles.totalSize} bytes)`);
console.log(`Fonts: ${analytics.fonts.count} (${analytics.fonts.totalSize} bytes)`);
console.log(`Sprites: ${analytics.sprites.count} (${analytics.sprites.totalSize} bytes)`);
console.log(`Recommendations:`, analytics.recommendations);// Clean up expired regions
const deletedCount = await offlineManager.cleanupExpiredRegions();
console.log(`Cleaned ${deletedCount} expired regions`);
// Verify and repair fonts
const verification = await offlineManager.verifyAndRepairFonts('style_123', { removeCorrupted: true });
console.log(`Verified: ${verification.verified}, Repaired: ${verification.repaired}, Removed: ${verification.removed}`);
// Set up automatic cleanup (runs every 24 hours)
const cleanupId = await offlineManager.setupAutoCleanup({
intervalHours: 24,
maxAge: 30, // days
});Main class for managing offline maps.
Constructor:
const manager = new OfflineMapManager(overrides?: OfflineManagerServiceOverrides);The constructor accepts optional service overrides for dependency injection (advanced usage). For most cases, use the default: new OfflineMapManager().
Core Methods:
addRegion(options: OfflineRegionOptions)- Download and store a map regiongetStoredRegion(id: string)- Retrieve a stored region by IDdeleteRegion(id: string)- Delete a specific region and its resourceslistStoredRegions()- List all stored regions with metadatalistRegions()- List all region options
Analytics Methods:
getComprehensiveStorageAnalytics()- Get detailed storage statisticsgetRegionAnalytics()- Get aggregate analytics across all regionsgetTileStatistics(styleId: string)- Get tile-specific statisticsgetFontStatistics(styleId: string)- Get font statisticsgetSpriteStatistics(styleId: string)- Get sprite statistics
Cleanup & Maintenance Methods:
cleanupExpiredRegions()- Remove regions past expiration dateperformSmartCleanup(options)- Intelligent cleanup with configurable criteriacleanupOldFonts(styleId?, options?)- Remove old font datacleanupOldSprites(styleId?, options?)- Remove old sprite datacleanupOldGlyphs(styleId?, options?)- Remove old glyph dataverifyAndRepairFonts(styleId, options?)- Verify font integrityverifyAndRepairSprites(styleId, options?)- Verify sprite integrityverifyAndRepairGlyphs(styleId, options?)- Verify glyph integritysetupAutoCleanup(options)- Enable automatic periodic cleanupstopAutoCleanup(cleanupId?)- Disable a specific auto-cleanupstopAllAutoCleanup()- Disable all auto-cleanupsperformCompleteMaintenance(options?)- Run comprehensive maintenance
UI control for MapLibre GL JS and Mapbox GL JS with glassmorphic design.
Constructor:
const offlineManager = new OfflineMapManager();
const control = new OfflineManagerControl(offlineManager, {
styleUrl: 'https://example.com/style.json', // Map style URL (required)
theme?: 'light' | 'dark', // UI theme (default: 'dark')
showBbox?: boolean, // Show region bounding boxes (default: false)
accessToken?: string, // Mapbox access token (for mapbox:// URLs)
mapLib?: MapLibProtocol, // Map library module (e.g. maplibregl) for idb:// protocol
});Features:
- Interactive polygon drawing for region selection
- Real-time download progress tracking
- Region management (view, delete)
- Theme toggle (dark/light mode)
- Storage analytics display
- Language switcher (English / Arabic with RTL support)
- Responsive mobile-friendly design
interface OfflineRegionOptions {
id: string; // Unique region identifier
name: string; // Human-readable name (required)
bounds: [[number, number], [number, number]]; // [[lng, lat], [lng, lat]]
minZoom: number; // Minimum zoom level (e.g., 10)
maxZoom: number; // Maximum zoom level (e.g., 16)
styleUrl?: string; // Map style URL
expiry?: number; // Expiration timestamp (ms since epoch)
deleteOnExpiry?: boolean; // Auto-delete on expiration
multipleRegions?: boolean; // Part of a multi-region download
tileExtension?: string; // Tile extension (pbf, mvt, png, etc.)
}- ๐๏ธ Outdoor & Recreation Apps: Hiking, camping, and adventure apps with offline trail maps
- ๐ฑ Field Data Collection: Survey and data collection in remote areas
- ๐จ Emergency Response: Critical map access during network outages
โ๏ธ Travel Apps: Tourist apps with offline city maps- ๐ Fleet Management: Vehicle tracking with offline map fallback
- ๐ Asset Management: Field service apps with offline capability
- ๐ Educational Apps: Geography and learning apps with downloadable maps
- ๐๏ธ Construction & Engineering: Site management with offline blueprints
- ๐พ Bandwidth Optimization: Reduce data costs by pre-downloading maps
// Balance quality vs storage with appropriate zoom levels
const region = {
minZoom: 10, // Don't go too low (tile count grows exponentially)
maxZoom: 16, // Don't go too high (diminishing returns)
bounds: [
/* ... */
],
};
// Monitor storage usage
const analytics = await manager.getComprehensiveStorageAnalytics();
if (analytics.totalStorageSize > 500 * 1024 * 1024) {
console.warn('High storage usage detected');
await manager.performSmartCleanup({ maxStorageSize: 500 * 1024 * 1024 });
}
// Use progressive loading for better UX
const progressiveDownload = {
priorityZoomLevels: [12, 13, 11, 14, 10, 15, 16],
onProgress: p => updateUI(p),
};try {
await manager.addRegion(regionOptions);
} catch (error) {
if (error.message.includes('quota')) {
console.error('Storage quota exceeded');
await manager.cleanupExpiredRegions();
} else if (error.message.includes('network')) {
console.error('Network error. Retrying...');
} else {
console.error('Unexpected error:', error);
}
}// Check available storage
if ('storage' in navigator && 'estimate' in navigator.storage) {
const { usage, quota } = await navigator.storage.estimate();
console.log(`Used: ${usage} / ${quota} bytes`);
}
// Regular cleanup
await manager.cleanupExpiredRegions();
// Auto-cleanup on startup
await manager.setupAutoCleanup({
intervalHours: 24, // Daily
maxAge: 30, // 30 days
});// Check quota
const { usage, quota } = await navigator.storage.estimate();
if (usage / quota > 0.9) {
await manager.cleanupExpiredRegions();
}
// Request persistent storage
if (navigator.storage?.persist) {
const isPersisted = await navigator.storage.persist();
console.log(`Persistent storage: ${isPersisted}`);
}// Reduce concurrency for slower devices
const lightOptions = {
maxConcurrency: 2,
batchSize: 10,
timeout: 30000,
};
// Use smaller regions
const smallerRegion = {
minZoom: 11, // Start at higher zoom
maxZoom: 15, // End at lower zoom
};| Browser | Version | Support |
|---|---|---|
| Chrome | 51+ | โ |
| Firefox | 45+ | โ |
| Safari | 10+ | โ |
| Edge | 79+ | โ |
| Mobile | Modern | โ |
Requirements:
- IndexedDB support
- ES2015+ JavaScript
- Async/await support
- Web Workers (optional, for background tasks)
Contributions are welcome! Please see our Contributing Guide for details.
# Clone repository
git clone https://github.com/muimsd/map-gl-offline.git
cd map-gl-offline
# Install dependencies
npm install
# Run development server
npm run dev
# Run tests
npm test
# Build library
npm run build
# Run MapLibre example app
cd examples/maplibre
npm install
npm run devmap-gl-offline/
โโโ src/
โ โโโ managers/ # Core offline manager
โ โโโ services/ # Tile, font, sprite services
โ โโโ storage/ # IndexedDB management
โ โโโ ui/ # UI components & controls
โ โ โโโ translations/ # i18n (English, Arabic)
โ โโโ utils/ # Utilities & helpers
โ โโโ types/ # TypeScript definitions
โโโ bin/ # CLI (map-gl-offline init) & Vite plugin
โโโ examples/
โ โโโ maplibre/ # MapLibre GL JS example app
โ โโโ mapbox-gl/ # Mapbox GL JS example app
โโโ docs/ # Docusaurus documentation site
โโโ tests/ # Test suites
- ๐ Documentation
- ๐ฎ Live Demo
- ๐ Report Issues
- ๐ฌ Discussions
- โญ Feature Requests
- ๐ Star on GitHub
- โ
CLI Command:
npx map-gl-offline initto copy the Service Worker into your project - โ
Vite Plugin:
offlineSwPlugin()to auto-copy the Service Worker on each build - โ Mapbox GL Example: Full React + Vite example app for Mapbox GL JS
- โ
Mapbox GL JS Support: Full support for Mapbox styles, including
mapbox://protocol URL resolution - โ Mapbox Standard Style: 3D models, raster-dem terrain, and import-based style resolution
- โ Day/Night Light Presets: Toggle between day and night lighting for Mapbox Standard
- โ Rain & Snow Weather: Weather effect controls for Mapbox Standard style
- โ
Import Resolver: Automatic resolution of Mapbox Standard
importsin styles - โ Internationalization: English and Arabic language support with full RTL layout
- โ Auto-detection: Automatically detects Mapbox vs MapLibre styles
- โ Fractional Zoom Fix: Fixed tile loading at fractional zoom levels
- โ Modern UI: Glassmorphic design with dark/light theme
- โ Polygon Drawing: Interactive region selection tool
- โ Enhanced Analytics: Comprehensive storage insights
- โ Performance: Optimized downloads and memory usage
- โ TypeScript: Full type safety throughout
See CHANGELOG.md for complete version history.
- MapLibre GL JS - Open-source map rendering engine
- Mapbox GL JS - Commercial map rendering engine
- IndexedDB - Browser storage API
- Tilebelt - Tile coordinate utilities
- Tailwind CSS - Utility-first CSS framework
MIT ยฉ Muhammad Imran Siddique
Made with โค๏ธ for the mapping community
๐ Documentation โข ๐ฎ Live Demo โข โญ Star on GitHub
