npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

maptiles

v1.0.0

Published

A React component for rendering maps using PMTiles and MapLibre GL JS with OpenStreetMap support

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