# map-basemap

Self-hosted [Protomaps](https://protomaps.com) vector basemap for gf.cx maps —
no API key, no subscription, and no third-party tile server at request time.

## Why (do not swap this back to `L.tileLayer`)

OSM's public tile server (`tile.openstreetmap.org`) returns **"Access blocked —
not following the tile usage policy"** on our deployed pages. A CARTO fallback
then injected a *"get an API key"* placeholder tile on the production domain —
it renders fine from a plain server and nags in a real browser, so a
`naturalWidth > 0` check **falsely passes**. Verify tiles are real content, not
a keyed placeholder.

Every free-and-good basemap is vector and either key-gated or self-hostable, so
we self-host. *Remove the coupling, not the component.*

## Live URL

```
https://assets.gf.cx/map-basemap/protomaps-leaflet.js   (vendored v5.1.0)
https://assets.gf.cx/map-basemap/map-basemap.js
```

`protomaps-leaflet` is not on cdnjs. It is vendored here rather than pulled
from jsdelivr so the runtime dependency set stays: Leaflet + our tileset + our
copy of the library.

## Use

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<script src="https://assets.gf.cx/map-basemap/protomaps-leaflet.js" data-cfasync="false"></script>
<script src="https://assets.gf.cx/map-basemap/map-basemap.js" data-cfasync="false"></script>
```

```js
const map = L.map('map').setView(flat, 14);
GfcxMapBasemap.add(map);            // shared London tileset, grayscale
```

Base-layer swap only — existing pins, rings, popups and legend are untouched.

## The tileset

```
https://media.gf.cx/maps/london.pmtiles     bbox -0.24,51.49,-0.03,51.58 @ z15
```

One artifact for every gf.cx London map. Standardised 2026-09-23 on this wider
extract; the older `london/basemap.pmtiles` stopped at `-0.10` and did not cover
the eastern further-afield entries. `media.gf.cx` already serves
`accept-ranges: bytes` and `access-control-allow-origin: *`, so cross-origin
range requests from any surface work with no CORS setup — a view pulls KB, not
the whole file.

Rebuild (go-pmtiles, `brew install pmtiles` — range-fetches the bbox, no
full-planet download):

```sh
pmtiles extract https://build.protomaps.com/<YYYYMMDD>.pmtiles out.pmtiles \
  --bbox=-0.24,51.49,-0.03,51.58 --maxzoom=15
wrangler r2 object put gf-cx-media/maps/london.pmtiles --file out.pmtiles \
  --remote --content-type application/octet-stream
```

## Gotchas

- protomaps-leaflet **v5 uses `flavor`**, not `theme`.
- `flavor:'grayscale'` is *already* monochrome — the consumer must **not** also
  set `.leaflet-tile-pane{filter:grayscale(1)}`. That double-desaturates.
- Protomaps basemap builds top out at **z15**; protomaps-leaflet overzooms to
  ~z19 for display, which is fine for a walking map.
- Screenshotting one in Playwright times out — the canvas repaints continuously.
  Verify by counting canvas tiles with real vector content via `browser_evaluate`.
