Skip to content
FlorentinoG9Public

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

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 pmtiles

Note: 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 icons
  • white - Minimal white theme for data visualization
  • grayscale - Grayscale theme for data visualization
  • black - 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.pmtiles

For more information on creating PMTiles, see the PMTiles documentation.

OpenStreetMap Data

This package is designed to work with OpenStreetMap data. You can:

  1. Download OSM data from OpenStreetMap
  2. Convert to PMTiles using tools like tippecanoe
  3. Host the PMTiles file on static storage (S3, Cloudflare R2, etc.)
  4. Use the URL in the pmtilesUrl prop

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 dev

Contributing

This project uses:

Making Changes

Easiest way (single command):

# After making your changes, run:
bun run save

This will automatically:

  • Stage all changes
  • Prompt to add a changeset if none exists
  • Commit using Commitizen

Or step-by-step:

  1. Create a branch and make your changes
  2. Stage your changes:
    git add .
  3. Add a changeset:
    # Shortcut (recommended)
    bun run cs
    
    # Or full command
    bun run changeset:add
  4. Commit using Commitizen:
    # Shortcut (recommended)
    bun run c
    
    # Or full command
    bun run commit
    Or manually follow the Conventional Commits format

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:

  1. Adding Changes: When you make changes, add a changeset with bun run cs (or bun run changeset:add)
  2. Automated Versioning: GitHub Actions automatically creates version PRs when changesets are merged
  3. 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

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages