A React component for rendering maps using PMTiles and MapLibre GL JS with OpenStreetMap support.
- 🗺️ PMTiles Integration - Render maps from PMTiles archives hosted on static storage
- 🎨 MapLibre GL JS - Powered by MapLibre GL JS for smooth, interactive maps
- 🌍 OpenStreetMap Support - Built-in support for OpenStreetMap data
- 🎨 Protomaps Flavors - Easy theme switching with built-in flavors (light, dark, white, grayscale, black)
- 🌐 Multi-language Support - Configure label language for internationalization
- 📍 Custom Markers/Pins - Add custom markers with icons, popups, and event handlers
- 📦 Zero Dependencies - Minimal peer dependencies (React only)
- 🎯 TypeScript - Fully typed with TypeScript
- ⚡ Serverless Ready - Works with static hosting and CDNs
bun add maptiles maplibre-gl pmtiles
# or
npm install maptiles maplibre-gl pmtiles
# or
yarn add maptiles maplibre-gl pmtiles
# or
pnpm add maptiles maplibre-gl pmtilesNote: The package includes @protomaps/basemaps as a dependency, so you don't need to install it separately. However, if you want to use custom flavors, you can import namedFlavor and other utilities directly from @protomaps/basemaps.
import { PMTilesMap } from 'maptiles';
import 'maptiles/styles';
// MapLibre GL CSS is automatically included, no need to import separately
function App() {
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[-122.4194, 37.7749]} // San Francisco
zoom={12}
/>
</div>
);
}Note: The component automatically imports MapLibre GL CSS. You only need to import maptiles/styles for the component's custom styles.
| Prop | Type | Default | Description |
|---|---|---|---|
pmtilesUrl |
string |
required | URL to the PMTiles archive file |
flavor |
'light' | 'dark' | 'white' | 'grayscale' | 'black' | Flavor |
'light' |
Basemap theme/flavor to use (see Protomaps Flavors) |
language |
string |
'en' |
Language code for labels (e.g., 'en', 'es', 'fr', 'de') |
center |
[number, number] |
[0, 0] |
Initial center coordinates [longitude, latitude] |
zoom |
number |
2 |
Initial zoom level |
minZoom |
number |
0 |
Minimum zoom level |
maxZoom |
number |
22 |
Maximum zoom level |
style |
StyleSpecification |
undefined |
Custom MapLibre GL style (uses Protomaps basemap style with flavor if not provided) |
mapOptions |
MapOptions |
{} |
Additional MapLibre GL options |
className |
string |
'' |
Container className |
containerStyle |
React.CSSProperties |
undefined |
Container inline styles |
onLoad |
(map: Map) => void |
undefined |
Callback when map is loaded |
onError |
(error: Error) => void |
undefined |
Callback when map encounters an error |
showControls |
boolean |
true |
Enable/disable map controls (navigation, scale) |
showAttribution |
boolean |
true |
Enable/disable attribution |
markers |
MarkerPin[] |
[] |
Array of markers/pins to display on the map |
defaultMarkerIcon |
string |
undefined |
Default marker icon URL (used when marker doesn't specify icon) |
defaultMarkerSize |
number |
40 |
Default marker size in pixels |
import { PMTilesMap } from 'maptiles';
import 'maptiles/styles';
function BasicMap() {
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[-74.006, 40.7128]} // New York
zoom={10}
/>
</div>
);
}The package supports Protomaps basemap flavors for easy theme switching:
import { PMTilesMap } from 'maptiles';
import 'maptiles/styles';
function ThemedMaps() {
return (
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
{/* Light theme (default) */}
<div style={{ height: '400px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor="light"
center={[2.3522, 48.8566]} // Paris
zoom={10}
/>
</div>
{/* Dark theme */}
<div style={{ height: '400px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor="dark"
center={[2.3522, 48.8566]} // Paris
zoom={10}
/>
</div>
{/* Grayscale theme for data visualization */}
<div style={{ height: '400px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor="grayscale"
center={[2.3522, 48.8566]} // Paris
zoom={10}
/>
</div>
{/* White theme for data visualization */}
<div style={{ height: '400px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor="white"
center={[2.3522, 48.8566]} // Paris
zoom={10}
/>
</div>
</div>
);
}light- General-purpose basemap with icons (default)dark- Dark theme basemap with iconswhite- Minimal white theme for data visualizationgrayscale- Grayscale theme for data visualizationblack- Black theme for data visualization
You can also use a custom flavor object from @protomaps/basemaps:
import { PMTilesMap } from 'maptiles';
import { namedFlavor } from '@protomaps/basemaps';
import 'maptiles/styles';
function CustomFlavorMap() {
// Override specific colors in a flavor
const customFlavor = {
...namedFlavor('light'),
buildings: '#ff0000', // Red buildings
water: '#0066cc' // Blue water
};
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor={customFlavor}
center={[0, 0]}
zoom={2}
/>
</div>
);
}import { PMTilesMap } from 'maptiles';
import 'maptiles/styles';
function MultiLanguageMap() {
return (
<div style={{ width: '100%', height: '600px' }}>
{/* Spanish labels */}
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
flavor="light"
language="es"
center={[-3.7038, 40.4168]} // Madrid
zoom={12}
/>
</div>
);
}Add custom markers to your map with icons, popups, and event handlers:
import { PMTilesMap } from 'maptiles';
import type { MarkerPin } from 'maptiles';
import 'maptiles/styles';
function MapWithMarkers() {
const markers: MarkerPin[] = [
{
id: 'marker-1',
position: [-122.4194, 37.7749], // San Francisco
icon: 'https://example.com/pin-icon.png',
size: 50,
popup: '<h3>San Francisco</h3><p>Welcome to the Golden City!</p>',
popupOpen: false,
onClick: (marker, event) => {
console.log('Marker clicked!', marker.getLngLat());
marker.togglePopup();
}
},
{
id: 'marker-2',
position: [-74.006, 40.7128], // New York
icon: 'https://example.com/custom-pin.svg',
size: 45,
anchor: 'bottom',
popup: '<h3>New York</h3><p>The Big Apple</p>',
draggable: true,
onDragEnd: (marker, event) => {
console.log('Marker moved to:', marker.getLngLat());
}
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[-98.5795, 39.8283]} // Center of USA
zoom={4}
markers={markers}
/>
</div>
);
}You can use a custom HTML element as a marker icon:
import { PMTilesMap } from 'maptiles';
import type { MarkerPin } from 'maptiles';
import 'maptiles/styles';
function CustomHTMLMarker() {
// Create a custom HTML element for the marker
const customIcon = document.createElement('div');
customIcon.innerHTML = '📍';
customIcon.style.fontSize = '40px';
customIcon.style.cursor = 'pointer';
const markers: MarkerPin[] = [
{
id: 'custom-html-marker',
position: [2.3522, 48.8566], // Paris
icon: customIcon,
popup: '<h3>Paris</h3><p>City of Light</p>',
onClick: (marker) => {
alert('Custom marker clicked!');
}
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[2.3522, 48.8566]}
zoom={12}
markers={markers}
/>
</div>
);
}Set a default icon for all markers:
import { PMTilesMap } from 'maptiles';
import type { MarkerPin } from 'maptiles';
import 'maptiles/styles';
function MapWithDefaultIcon() {
const markers: MarkerPin[] = [
{
id: 'marker-1',
position: [-122.4194, 37.7749]
// Will use defaultMarkerIcon
},
{
id: 'marker-2',
position: [-74.006, 40.7128],
icon: 'https://example.com/special-icon.png' // Overrides default
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[-98.5795, 39.8283]}
zoom={4}
markers={markers}
defaultMarkerIcon="https://example.com/default-pin.png"
defaultMarkerSize={35}
/>
</div>
);
}Create draggable markers with drag event handlers:
import { PMTilesMap } from 'maptiles';
import type { MarkerPin } from 'maptiles';
import 'maptiles/styles';
import { useState } from 'react';
function DraggableMarkers() {
const [markerPosition, setMarkerPosition] = useState<[number, number]>([0, 0]);
const markers: MarkerPin[] = [
{
id: 'draggable-marker',
position: markerPosition,
draggable: true,
icon: 'https://example.com/drag-pin.png',
onDrag: (marker) => {
const lngLat = marker.getLngLat();
console.log('Dragging:', lngLat);
},
onDragEnd: (marker) => {
const lngLat = marker.getLngLat();
setMarkerPosition([lngLat.lng, lngLat.lat]);
console.log('New position:', lngLat);
}
}
];
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={markerPosition}
zoom={10}
markers={markers}
/>
</div>
);
}The MarkerPin interface supports the following properties:
| Property | Type | Default | Description |
|---|---|---|---|
id |
string |
required | Unique identifier for the marker |
position |
[number, number] |
required | Marker position [longitude, latitude] |
icon |
string | HTMLElement |
undefined |
Custom icon/image URL or HTML element |
size |
number |
40 |
Icon size in pixels |
anchor |
string |
'bottom' |
Icon anchor point (center, top, bottom, left, right, etc.) |
popup |
string | HTMLElement |
undefined |
Popup content (HTML string or HTMLElement) |
popupOpen |
boolean |
false |
Whether popup should be open by default |
className |
string |
undefined |
Custom CSS class for the marker element |
style |
CSSProperties |
undefined |
Custom CSS styles for the marker element |
draggable |
boolean |
false |
Whether marker is draggable |
onClick |
function |
undefined |
Callback when marker is clicked |
onDrag |
function |
undefined |
Callback when marker is dragged |
onDragEnd |
function |
undefined |
Callback when marker drag ends |
onMouseEnter |
function |
undefined |
Callback when marker is hovered |
onMouseLeave |
function |
undefined |
Callback when marker hover ends |
import { PMTilesMap } from 'maptiles';
import type { StyleSpecification } from 'maplibre-gl';
import 'maptiles/styles';
function CustomStyledMap() {
const customStyle: StyleSpecification = {
version: 8,
sources: {
'pmtiles-source': {
type: 'vector',
url: 'pmtiles://https://example.com/map.pmtiles'
}
},
layers: [
// Your custom layers
]
};
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
style={customStyle}
center={[0, 0]}
zoom={2}
/>
</div>
);
}import { PMTilesMap } from 'maptiles';
import type { Map } from 'maplibre-gl';
import 'maptiles/styles';
function MapWithHandlers() {
const handleMapLoad = (map: Map) => {
console.log('Map loaded!', map);
// Add custom layers, markers, etc.
};
const handleMapError = (error: Error) => {
console.error('Map error:', error);
};
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[2.3522, 48.8566]} // Paris
zoom={12}
onLoad={handleMapLoad}
onError={handleMapError}
/>
</div>
);
}import { PMTilesMap } from 'maptiles';
import 'maptiles/styles';
function MapWithOptions() {
return (
<div style={{ width: '100%', height: '600px' }}>
<PMTilesMap
pmtilesUrl="https://example.com/map.pmtiles"
center={[139.6917, 35.6895]} // Tokyo
zoom={10}
mapOptions={{
pitch: 45,
bearing: 30,
antialias: true
}}
/>
</div>
);
}To create PMTiles from your data, you can use the pmtiles CLI tool:
# Install pmtiles CLI
# Download from https://github.com/protomaps/go-pmtiles/releases
# Convert MBTiles to PMTiles
pmtiles convert input.mbtiles output.pmtiles
# Upload to S3 or other storage
pmtiles upload output.pmtiles s3://my-bucket/map.pmtilesFor more information on creating PMTiles, see the PMTiles documentation.
This package is designed to work with OpenStreetMap data. You can:
- Download OSM data from OpenStreetMap
- Convert to PMTiles using tools like tippecanoe
- Host the PMTiles file on static storage (S3, Cloudflare R2, etc.)
- Use the URL in the
pmtilesUrlprop
When hosting PMTiles files on cloud storage, ensure CORS is properly configured:
- AWS S3: Add CORS policy allowing your domain
- Cloudflare R2: Configure CORS settings in the dashboard
- Other providers: Check their CORS documentation
Example S3 CORS configuration:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": ["Content-Length", "Content-Range", "ETag"]
}
]# Install dependencies
bun install
# Build
bun run build
# Type check
bun run typecheck
# Watch mode
bun run devThis project uses:
- Changesets for version management
- Commitlint for commit message linting
- Commitizen for interactive commit creation
Easiest way (single command):
# After making your changes, run:
bun run saveThis will automatically:
- Stage all changes
- Prompt to add a changeset if none exists
- Commit using Commitizen
Or step-by-step:
- Create a branch and make your changes
- Stage your changes:
git add . - Add a changeset:
# Shortcut (recommended) bun run cs # Or full command bun run changeset:add
- Commit using Commitizen:
Or manually follow the Conventional Commits format
# Shortcut (recommended) bun run c # Or full command bun run commit
<type>(<scope>): <subject>
<body>
<footer>
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
See CONTRIBUTING.md for detailed contribution guidelines.
This project uses Changesets for automated versioning and releases:
- Adding Changes: When you make changes, add a changeset with
bun run cs(orbun run changeset:add) - Automated Versioning: GitHub Actions automatically creates version PRs when changesets are merged
- Automated Publishing: When version PRs are merged, packages are automatically published to npm
The versioning workflow is fully automated via GitHub Actions - no manual version bumps needed!
MIT
This package integrates with Protomaps Basemaps to provide beautiful, customizable map themes. Flavors are like color schemes for your map - you can use the built-in themes or create custom ones.
For more information on flavors and customization, see the Protomaps Flavors documentation.
- PMTiles - PMTiles specification and tools
- Protomaps Basemaps - Basemap flavors and styling
- MapLibre GL JS - Map rendering library
- OpenStreetMap - Open map data