Skip to content

muimsd/map-gl-offline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

171 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Map GL Offline ๐Ÿ—บ๏ธ

npm version License: MIT TypeScript

Documentation | Live Demo

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.

๐ŸŽฌ Demo

Map GL Offline Demo

Download regions, load offline styles, and navigate maps without an internet connection.

โœจ Features

๐ŸŽฏ Core Offline Capabilities

  • ๐Ÿ—บ๏ธ 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 GL JS Support

  • ๐Ÿ”— 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

๐ŸŽจ Modern UI Control

  • ๐Ÿ–ผ๏ธ 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

๐Ÿ› ๏ธ Technical Excellence

  • ๐Ÿ’พ 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)

๐Ÿ“ฆ Installation

npm install map-gl-offline
# or
yarn add map-gl-offline
# or
pnpm add map-gl-offline

CDN (UMD)

For 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>

๐Ÿ”‘ Environment Setup

For development or when using Maptiler styles, create a .env file:

VITE_MAPTILER_API_KEY=your_api_key_here

Get a free API key from Maptiler.

For Mapbox styles, you will also need a Mapbox access token from Mapbox.

๐Ÿš€ Quick Start

MapLibre GL JS

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

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 init

Option 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.js
import 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 addProtocol support, 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)

Programmatic Usage

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');

Analytics & Monitoring

// 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);

Cleanup & Maintenance

// 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
});

๐Ÿ“š API Reference

OfflineMapManager

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 region
  • getStoredRegion(id: string) - Retrieve a stored region by ID
  • deleteRegion(id: string) - Delete a specific region and its resources
  • listStoredRegions() - List all stored regions with metadata
  • listRegions() - List all region options

Analytics Methods:

  • getComprehensiveStorageAnalytics() - Get detailed storage statistics
  • getRegionAnalytics() - Get aggregate analytics across all regions
  • getTileStatistics(styleId: string) - Get tile-specific statistics
  • getFontStatistics(styleId: string) - Get font statistics
  • getSpriteStatistics(styleId: string) - Get sprite statistics

Cleanup & Maintenance Methods:

  • cleanupExpiredRegions() - Remove regions past expiration date
  • performSmartCleanup(options) - Intelligent cleanup with configurable criteria
  • cleanupOldFonts(styleId?, options?) - Remove old font data
  • cleanupOldSprites(styleId?, options?) - Remove old sprite data
  • cleanupOldGlyphs(styleId?, options?) - Remove old glyph data
  • verifyAndRepairFonts(styleId, options?) - Verify font integrity
  • verifyAndRepairSprites(styleId, options?) - Verify sprite integrity
  • verifyAndRepairGlyphs(styleId, options?) - Verify glyph integrity
  • setupAutoCleanup(options) - Enable automatic periodic cleanup
  • stopAutoCleanup(cleanupId?) - Disable a specific auto-cleanup
  • stopAllAutoCleanup() - Disable all auto-cleanups
  • performCompleteMaintenance(options?) - Run comprehensive maintenance

OfflineManagerControl

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

๐Ÿ”ง Configuration Options

OfflineRegionOptions

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.)
}

๐ŸŽฏ Use Cases

  • ๐Ÿ”๏ธ 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

๐Ÿ’ก Best Practices

Performance Optimization

// 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),
};

Error Handling

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);
  }
}

Storage Management

// 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
});

๐Ÿ” Troubleshooting

Storage Quota Issues

// 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}`);
}

Performance Issues

// 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 Compatibility

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)

๐Ÿค Contributing

Contributions are welcome! Please see our Contributing Guide for details.

Development Setup

# 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 dev

Project Structure

map-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

๐Ÿ“ž Support & Links

๐Ÿ”„ Recent Updates

v0.5.2 (Latest)

  • โœ… CLI Command: npx map-gl-offline init to 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

v0.5.0

  • โœ… 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 imports in styles
  • โœ… Internationalization: English and Arabic language support with full RTL layout
  • โœ… Auto-detection: Automatically detects Mapbox vs MapLibre styles

v0.1.0

  • โœ… 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.

๐Ÿ™ Acknowledgments

๐Ÿ“„ License

MIT ยฉ Muhammad Imran Siddique


Made with โค๏ธ for the mapping community

๐Ÿ“– Documentation โ€ข ๐ŸŽฎ Live Demo โ€ข โญ Star on GitHub

About

A TypeScript npm package for MapLibre GL JS and Mapbox GL JS to enable offline tiles.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors