maptiles
v1.0.0
Published
A React component for rendering maps using PMTiles and MapLibre GL JS with OpenStreetMap support
Maintainers
Readme
maptiles
A React component for rendering maps using PMTiles and MapLibre GL JS with OpenStreetMap support.
Features
- 🗺️ 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
Installation
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.
Quick Start
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.
Props
PMTilesMapProps
| 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 |
Examples
Basic Usage
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>
);
}Using Flavors (Themes)
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>
);
}Available Flavors
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
Custom Flavor
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>
);
}Multi-language Support
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>
);
}Custom Markers/Pins
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>
);
}Custom Marker with HTML Element
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>
);
}Default Marker Icon
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>
);
}Draggable Markers
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>
);
}MarkerPin Interface
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 |
With Custom Style
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>
);
}With Event Handlers
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>
);
}With Custom Map Options
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>
);
}Creating PMTiles
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.
OpenStreetMap Data
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
CORS Configuration
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"]
}
]Development
# Install dependencies
bun install
# Build
bun run build
# Type check
bun run typecheck
# Watch mode
bun run devContributing
This project uses:
- Changesets for version management
- Commitlint for commit message linting
- Commitizen for interactive commit creation
Making Changes
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
Commit Message Format
<type>(<scope>): <subject>
<body>
<footer>Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
See CONTRIBUTING.md for detailed contribution guidelines.
Versioning and Releases
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!
License
MIT
Protomaps Flavors
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.
Related Projects
- PMTiles - PMTiles specification and tools
- Protomaps Basemaps - Basemap flavors and styling
- MapLibre GL JS - Map rendering library
- OpenStreetMap - Open map data
