{"id":35641426,"url":"https://github.com/muimsd/map-gl-offline","last_synced_at":"2026-05-23T11:01:03.357Z","repository":{"id":326862881,"uuid":"937531039","full_name":"muimsd/map-gl-offline","owner":"muimsd","description":"A TypeScript npm package for MapLibre GL JS and Mapbox GL JS to enable offline tiles.","archived":false,"fork":false,"pushed_at":"2026-05-22T12:22:19.000Z","size":8058,"stargazers_count":14,"open_issues_count":4,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-22T16:15:02.963Z","etag":null,"topics":["gis","mapbox-gl","maplibre-gl","mbtiles","offline","offline-first","pwa","service-worker","typescript","webgl"],"latest_commit_sha":null,"homepage":"https://map-gl-offline-demo.netlify.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/muimsd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-23T09:37:22.000Z","updated_at":"2026-05-22T12:14:30.000Z","dependencies_parsed_at":null,"dependency_job_id":"402e75e5-004f-48f9-b21c-aa482387f00b","html_url":"https://github.com/muimsd/map-gl-offline","commit_stats":null,"previous_names":["muimsd/map-gl-offline"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/muimsd/map-gl-offline","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muimsd%2Fmap-gl-offline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muimsd%2Fmap-gl-offline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muimsd%2Fmap-gl-offline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muimsd%2Fmap-gl-offline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/muimsd","download_url":"https://codeload.github.com/muimsd/map-gl-offline/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/muimsd%2Fmap-gl-offline/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33389229,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T04:15:53.637Z","status":"ssl_error","status_checked_at":"2026-05-23T04:15:53.242Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gis","mapbox-gl","maplibre-gl","mbtiles","offline","offline-first","pwa","service-worker","typescript","webgl"],"created_at":"2026-01-05T11:57:51.430Z","updated_at":"2026-05-23T11:01:03.350Z","avatar_url":"https://github.com/muimsd.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/muimsd"],"categories":["Utility Libraries","Mapbox GL JS Plugins"],"sub_categories":["JavaScript"],"readme":"# Map GL Offline 🗺️\n\n[![npm version](https://badge.fury.io/js/map-gl-offline.svg)](https://badge.fury.io/js/map-gl-offline)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/)\n\n**[📖 Documentation](https://map-gl-offline.netlify.app)** · **[🎮 Live Demo](https://map-gl-offline-demo.netlify.app)** · **[🐛 Issues](https://github.com/muimsd/map-gl-offline/issues)**\n\nTypeScript offline-map library for **MapLibre GL JS** and **Mapbox GL JS**. Download styles, tiles, sprites, glyphs, and fonts to IndexedDB; load them back with zero network. Ships with a glassmorphic UI control and a complete programmatic API.\n\n![Map GL Offline Demo](assets/map-gl-offline-demo.gif)\n\n## Features\n\n- 🗺️ **Offline regions** — polygon selection, smart tile management, extra vector/raster overlays\n- 🎨 **Full resource capture** — styles, sprites, fonts, glyphs with Unicode ranges\n- 🔗 **Mapbox GL + MapLibre GL** — auto-detection, `mapbox://` URL resolution, Standard style with 3D/terrain\n- 📊 **Analytics \u0026 cleanup** — storage reports, auto-cleanup, quota-aware downloads\n- 🎨 **UI control** — glassmorphic panel, dark/light themes, English/Arabic (RTL), polygon drawing\n- 🛠️ **Programmatic API** — `downloadRegion` with per-phase progress, full TypeScript types\n\n## Install\n\n```bash\nnpm install map-gl-offline\n```\n\nOr via CDN as the `mapgloffline` global:\n\n```html\n\u003cscript src=\"https://unpkg.com/map-gl-offline/dist/index.umd.js\"\u003e\u003c/script\u003e\n\u003clink rel=\"stylesheet\" href=\"https://unpkg.com/map-gl-offline/style.css\" /\u003e\n```\n\n## Quick Start\n\n### MapLibre GL JS\n\n```typescript\nimport maplibregl from 'maplibre-gl';\nimport { OfflineMapManager, OfflineManagerControl } from 'map-gl-offline';\nimport 'maplibre-gl/dist/maplibre-gl.css';\nimport 'map-gl-offline/style.css';\n\nconst map = new maplibregl.Map({\n  container: 'map',\n  style: 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY',\n  center: [-74.006, 40.7128],\n  zoom: 12,\n});\n\nconst offlineManager = new OfflineMapManager();\n\nmap.on('load', () =\u003e {\n  const control = new OfflineManagerControl(offlineManager, {\n    styleUrl: 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY',\n    mapLib: maplibregl, // enables idb:// protocol in web workers\n  });\n  map.addControl(control, 'top-right');\n});\n```\n\n### Mapbox GL JS\n\nMapbox GL JS v3 lacks `addProtocol`, so the library uses a Service Worker. Run **one** of:\n\n```bash\nnpx map-gl-offline init            # CLI (recommended)\n# or add to vite.config.js:\n# import { offlineSwPlugin } from 'map-gl-offline/vite-plugin';\n# plugins: [offlineSwPlugin()]\n# or manually: cp node_modules/map-gl-offline/dist/idb-offline-sw.js public/\n```\n\nThen:\n\n```typescript\nimport mapboxgl from 'mapbox-gl';\nimport { OfflineMapManager, OfflineManagerControl } from 'map-gl-offline';\nimport 'mapbox-gl/dist/mapbox-gl.css';\nimport 'map-gl-offline/style.css';\n\nmapboxgl.accessToken = 'YOUR_MAPBOX_TOKEN';\n\nconst map = new mapboxgl.Map({\n  container: 'map',\n  style: 'mapbox://styles/mapbox/standard',\n  center: [-74.006, 40.7128],\n  zoom: 12,\n});\n\nconst offlineManager = new OfflineMapManager();\nmap.on('load', () =\u003e\n  map.addControl(\n    new OfflineManagerControl(offlineManager, {\n      styleUrl: 'mapbox://styles/mapbox/standard',\n      accessToken: mapboxgl.accessToken,\n    }),\n    'top-right',\n  ),\n);\n```\n\n## Programmatic Usage\n\n`downloadRegion` runs the full pipeline (**style → sprites → glyphs → models → tiles → metadata**) with per-phase progress. `addRegion` alone only stores metadata — use `downloadRegion` to actually fetch assets.\n\n```typescript\nimport { OfflineMapManager } from 'map-gl-offline';\n\nconst offlineManager = new OfflineMapManager();\n\nawait offlineManager.downloadRegion(\n  {\n    id: 'downtown',\n    name: 'Downtown',\n    bounds: [[-74.0559, 40.7128], [-74.0059, 40.7628]],\n    minZoom: 10,\n    maxZoom: 16,\n    styleUrl: 'https://api.maptiler.com/maps/streets/style.json?key=YOUR_KEY',\n  },\n  {\n    onProgress: ({ phase, percentage, message }) =\u003e {\n      console.log(`[${phase}] ${percentage.toFixed(1)}% ${message ?? ''}`);\n    },\n  },\n);\n\n// Manage\nawait offlineManager.listStoredRegions();\nawait offlineManager.getStoredRegion('downtown');\nawait offlineManager.deleteRegion('downtown');\n\n// Analytics \u0026 cleanup\nawait offlineManager.getComprehensiveStorageAnalytics();\nawait offlineManager.cleanupExpiredRegions();\nawait offlineManager.setupAutoCleanup({ intervalHours: 24, maxAge: 30 });\n```\n\n### Multi-region downloads (global overview + city detail)\n\nFor app shipping use cases — a low-zoom basemap of the world, plus high-zoom detail in the cities your users actually visit — set `multipleRegions: true` on each region so the manager reuses the shared style/sprites/glyphs across downloads. The exported `BoundingBox` type keeps city lists from being widened to `number[][]`, so you can write the bounds inline without `as` casts.\n\n```typescript\nimport mapboxgl from 'mapbox-gl';\nimport {\n  OfflineMapManager,\n  type BoundingBox,\n  type DownloadRegionProgress,\n} from 'map-gl-offline';\n\nconst offlineManager = new OfflineMapManager();\nconst STYLE_URL = 'mapbox://styles/mapbox/standard';\n\nconst opts = {\n  accessToken: mapboxgl.accessToken, // `string | null` is accepted — no cast needed\n  onProgress: ({ phase, percentage, message }: DownloadRegionProgress) =\u003e\n    console.log(`[${phase}] ${percentage.toFixed(1)}% ${message ?? ''}`),\n};\n\n// 1) Whole planet, low zoom only (~5,500 tiles/source) — countries, major cities\nawait offlineManager.downloadRegion(\n  {\n    id: 'global-overview',\n    name: 'Global overview',\n    bounds: [[-180, -85.0511], [180, 85.0511]], // ±85.0511° = Web Mercator cutoff\n    minZoom: 0,\n    maxZoom: 6,\n    styleUrl: STYLE_URL,\n    multipleRegions: true,\n  },\n  opts,\n);\n\n// 2) High-detail per city — tight bbox per place your users actually go\nconst cities: Array\u003c{ id: string; name: string; bounds: BoundingBox }\u003e = [\n  { id: 'nyc',    name: 'New York', bounds: [[-74.05, 40.68], [-73.90, 40.82]] },\n  { id: 'london', name: 'London',   bounds: [[-0.25, 51.43],  [0.02, 51.57]]  },\n];\n\nfor (const city of cities) {\n  await offlineManager.downloadRegion(\n    {\n      id: city.id,\n      name: city.name,\n      bounds: city.bounds,\n      minZoom: 6,   // overlaps the overview's maxZoom — clean handoff, no seam\n      maxZoom: 14,  // street-level detail\n      styleUrl: STYLE_URL,\n      multipleRegions: true,\n    },\n    opts,\n  );\n}\n```\n\n\u003e **Don't download the whole globe at high zoom.** The tile count quadruples per zoom level — `minZoom: 0, maxZoom: 15` for the whole planet is ~1.4 billion tiles per source, which will blow past IndexedDB quota and may violate provider TOS. The two-tier setup above gives countries-everywhere plus streets-where-it-matters for a few thousand tiles total.\n\n### Sparse-source detection\n\nFor composite styles (e.g. Mapbox Standard) that reference sparse tilesets like `indoor-v3` or `landmark-pois-v1`, the tile downloader probes start/middle/end tiles per source and drops any that return majority-404. Disable with `tileOptions: { probeSourcesBeforeDownload: false }`.\n\nThree Mapbox Standard sub-tilesets are sparse-by-design across the planet — `mapbox.indoor-v3`, `mapbox.landmark-pois-v1`, `mapbox.procedural-buildings-v1`. Since 0.8.8 these are hard-skipped **before** the probe step so no network request is issued (and no 404s land in devtools). Opt out with `tileOptions: { skipKnownSparseSources: false }` to run them through the probe path instead.\n\n### Recovering from an incompatible DB\n\nIf another app on the same origin has created `offline-map-db` at a newer version, `dbPromise` throws a typed error. Offer a reset UX:\n\n```typescript\nimport { dbPromise, OfflineMapDBVersionError, resetOfflineMapDB } from 'map-gl-offline';\n\ntry {\n  await dbPromise;\n} catch (err) {\n  if (err instanceof OfflineMapDBVersionError) {\n    if (confirm('Offline storage is incompatible. Clear it?')) {\n      await resetOfflineMapDB(); // destructive\n      location.reload();\n    }\n  }\n}\n```\n\n\u003e **Upgrading from 0.5.x?** Read the [0.6.0 migration guide](https://map-gl-offline.netlify.app/docs/migration-0.6) — covers the rename of `ResourceService.getXxxStatistics` → `getXxxStats`, the `addRegion` vs `downloadRegion` split, and the `expiry` timestamp fix.\n\n## API at a glance\n\n- **Regions** — `downloadRegion`, `loadRegion`, `addRegion`, `getStoredRegion`, `listStoredRegions`, `listRegions`, `deleteRegion`\n- **Analytics** — `getComprehensiveStorageAnalytics`, `getRegionAnalytics`, `getTileStats`, `getFontStats`, `getSpriteStats`, `getGlyphStats`\n- **Cleanup** — `cleanupExpiredRegions`, `performSmartCleanup`, `cleanupOld{Fonts,Sprites,Glyphs}`, `verifyAndRepair{Fonts,Sprites,Glyphs}`, `setupAutoCleanup`, `performCompleteMaintenance`\n- **Import / Export** — `exportRegionAsMBTiles`, `importRegion`, `downloadExportedRegion` (binary SQLite MBTiles, QGIS/tippecanoe-compatible)\n- **Storage utilities** — `dbPromise`, `OfflineMapDBVersionError`, `resetOfflineMapDB`, `loadAllStoredRegions`, `resourceKeyBelongsToStyle`\n\nSee the **[full API reference](https://map-gl-offline.netlify.app/docs/api-reference)** and **[examples](https://map-gl-offline.netlify.app/docs/examples)** for every option and pattern.\n\n## Browser compatibility\n\nChrome 51+ · Firefox 45+ · Safari 10+ · Edge 79+ · modern mobile browsers. Requires IndexedDB and ES2015+.\n\n## Contributing\n\n```bash\ngit clone https://github.com/muimsd/map-gl-offline.git\ncd map-gl-offline \u0026\u0026 npm install\nnpm run dev          # dev harness\nnpm test             # unit tests\nnpm run build        # library\n```\n\nIssues and PRs welcome. See [CHANGELOG.md](CHANGELOG.md) for release notes.\n\n## License\n\nMIT © [Muhammad Imran Siddique](https://github.com/muimsd)\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n[📖 Docs](https://map-gl-offline.netlify.app) · [🎮 Demo](https://map-gl-offline-demo.netlify.app) · [⭐ GitHub](https://github.com/muimsd/map-gl-offline)\n\n\u003ca href=\"https://www.buymeacoffee.com/muimsd\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png\" alt=\"Buy Me A Coffee\" style=\"height: 45px;\"\u003e\u003c/a\u003e\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmuimsd%2Fmap-gl-offline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmuimsd%2Fmap-gl-offline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmuimsd%2Fmap-gl-offline/lists"}