Skip to content
Quickstart

Parcels on a map in five steps.

No SDK, no build plugin, no account required to read this page. If you have written MapLibre before, steps 2 and 3 are the only unfamiliar part — and only barely.

Tile access is issued per account while the platform is in early access. Request access and we will set your keys up with you.
01

Install the two packages

MapLibre renders the map; the PMTiles library teaches it how to read a single-file archive over HTTP range requests. There is nothing else to install.

terminalbash
npm install maplibre-gl pmtiles
02

Register the pmtiles:// protocol

This has to happen once, before any map is constructed. It teaches MapLibre to resolve pmtiles:// URLs by issuing range requests instead of fetching whole tiles.

map.jsjavascript
import maplibregl from 'maplibre-gl'
import { Protocol } from 'pmtiles'
import 'maplibre-gl/dist/maplibre-gl.css'

const protocol = new Protocol()
maplibregl.addProtocol('pmtiles', protocol.tile)
03

Create the map with a basemap source

The basemap is one planet-wide archive. Point at it the same way you would any vector source — the only difference is the pmtiles:// prefix on the URL.

map.jsjavascript
const TILES = 'https://tiles.mapsfordevelopers.com'

const map = new maplibregl.Map({
  container: 'map',
  center: [-95.3698, 29.7604],   // Houston, TX
  zoom: 14,
  style: {
    version: 8,
    glyphs: 'https://protomaps.github.io/basemaps-assets'
          + '/fonts/{fontstack}/{range}.pbf',
    sources: {
      basemap: {
        type: 'vector',
        url: `pmtiles://${TILES}/basemap/protomaps_planet.pmtiles`,
      },
    },
    layers: [
      { id: 'bg', type: 'background',
        paint: { 'background-color': '#0F141C' } },
    ],
  },
})
04

Add the parcel layer

Parcels are ordinary vector tiles, so every MapLibre paint property, filter expression and interaction handler works exactly as documented.

map.jsjavascript
map.on('load', () => {
  map.addSource('parcels', {
    type: 'vector',
    url: `pmtiles://${TILES}/parcels/parcels_tx_harris.pmtiles`,
  })

  map.addLayer({
    id: 'parcel-fill',
    type: 'fill',
    source: 'parcels',
    'source-layer': 'parcels',
    paint: { 'fill-color': '#FF6B35', 'fill-opacity': 0.12 },
  })

  map.addLayer({
    id: 'parcel-line',
    type: 'line',
    source: 'parcels',
    'source-layer': 'parcels',
    paint: { 'line-color': '#FF8A4C', 'line-width': 0.6 },
  })
})
05

Read the attributes on click

Whatever fields the county publishes come through on the feature properties — owner, assessed value, legal description, acreage. The exact set varies by source.

map.jsjavascript
map.on('click', 'parcel-fill', (e) => {
  const parcel = e.features?.[0]
  if (!parcel) return

  new maplibregl.Popup()
    .setLngLat(e.lngLat)
    .setHTML(`<pre>${JSON.stringify(parcel.properties, null, 2)}</pre>`)
    .addTo(map)
})

map.on('mouseenter', 'parcel-fill', () => {
  map.getCanvas().style.cursor = 'pointer'
})

Stuck on something?

The people who built the pipeline answer the contact form. Send the code that is not working.