M Motionworks Population Intelligence

Rendering Anytime tiles in MapLibre GL

Fetch the grant's TileJSON and hand it to MapLibre as a vector source. The TileJSON covers every tileset the grant authorizes; renderer metadata for styling lives in its x-mw.extra_metadata block.

Minimal setup

const map = new maplibregl.Map({ /* ... */ });

map.on('load', async () => {
  // tileJsonUrl is the capability URL returned when you minted the grant
  map.addSource('anytime', { type: 'vector', url: tileJsonUrl });
  map.addLayer({
    id: 'anytime-fill',
    type: 'fill',
    source: 'anytime',
    'source-layer': '<layer name from the manifest vector_layers>',
    paint: { 'fill-color': rampExpression, 'fill-outline-color': 'rgb(18,19,46)' },
  });
});

Your front-end calls this with no auth header: the grant URL is the credential, pinned by the grant's allowed_origins. See Minting an Anytime tile grant.

Reading x-mw.extra_metadata

Each TileJSON carries x-mw.extra_metadata, keyed by tileset slug, with one entry for every tileset the TileJSON serves. Each entry is a faithful pass-through of the publisher's styling metadata, stamped with three provenance fields (refreshed_at, extra_metadata_verified, source). Served entries always carry extra_metadata_verified: true; a tileset whose metadata is not available is left out of both tiles[] and extra_metadata rather than served with a degraded entry. Illustrative shape (long arrays abbreviated, not a parseable payload):

{
  "tiles_anytime_v2_3_21d529097c81af04_202501_202512_bg_v1": {
    "breakpoints": {
      "d": { "ST": [[...12 numbers...], [...12 numbers...]], "BG": [[...], [...]], "...": "..." },
      "m": ["density", "occupancy"],
      "p": [0, 1, 5, 25, 33, 50, 66, 75, 90, 95, 99, 100],
      "v": 2
    },
    "breakpoints_spec": {},
    "derived_grains": { "TRCT": "block group id less trailing digit; ST_UNION_AGG geometry; SUM occupancy; reconciliation to BG asserted at build time" },
    "zoom_bands": {},
    "refreshed_at": "2026-08-08T12:00:00Z",
    "extra_metadata_verified": true,
    "source": "bigquery-extra-metadata-kv"
  }
}

refreshed_at is when the styling metadata was last refreshed; extra_metadata_verified is always true on a served entry; source is informational provenance.

How to build a ramp from it:

  • breakpoints.m lists measures; m[0] is "density" and m[1] is "occupancy" on the current family.
  • breakpoints.p lists the percentile ranks the arrays are sampled at (12 values on the current family: 0, 1, 5, 25, 33, 50, 66, 75, 90, 95, 99, 100).
  • d is a record keyed by geography type (ST, STCO, TRCT, BG, and whatever future grains the upstream publishes); the entry's derived_grains sub-object lists the grains it carries. Each d[grain] value is an array of per-measure arrays: d[grain][0] is the density breakpoints at ranks p, d[grain][1] is occupancy.
  • zoom_bands maps grains to the zoom ranges where each grain should be styled.

Two rules that prevent rendering bugs:

  1. Do not hard-code the grain key set. breakpoints.d is keyed by the geography type (ST, STCO, TRCT, BG, and whatever future grains the upstream publishes). The tileset's derived_grains sub-object (a record of grain name to derivation note, exact object shape) lists which grains the entry carries; a renderer that assumes exactly ['ST','STCO','TRCT','BG'] will silently mis-style a tileset with a fifth grain.
  2. Derive your own ramp from the breakpoints you receive. Pick a measure index from m, take d[grain][measureIndex], and pair the values with your color stops at the matching ranks from p. Example (density, one grain):
const meta = tilejson['x-mw'].extra_metadata[slug];
const mi = meta.breakpoints.m.indexOf('density');           // measure index
const stops = meta.breakpoints.d[grain][mi];                // 12 numbers
const ranks = meta.breakpoints.p;                           // 12 ranks
const colors = ['#12132e', '#1e1f53', '#3d5fa0', '#25c393', '#f3fffb'];
const fill = [
  'interpolate', ['linear'], ['get', 'density'],
  ...stops.flatMap((v, i) => [v, colors[Math.min(i * colors.length / stops.length | 0, colors.length - 1)]])
];

Token refresh

The tile-token embedded in tiles[] expires 24h after each TileJSON fetch. Re-fetch the TileJSON after x-mw.refresh_after seconds (82,800 = 23h) for a fresh token; MapLibre picks the new tiles[] URL up automatically when you re-set the source.