BeMapAPI
BeMap 4.1.0

BeNomad Tiles

Styles & BeMaputnik#

A BeNomad Tiles map paints a tiny bundled fallback instantly, then swaps in the live cartographic charte published on the worker, so the map you author in BeMaputnik reaches every application without a single redeploy.

The bundled style is not the source of truth. bemap.fallbackStyle exists only to fill the canvas during the first few hundred milliseconds and to survive an offline start. The real style is fetched from the worker after login (/api/default-style → /styles/<name>). Change the charte server-side and every app picks it up on its next load, no library rebuild, no client release.

At a glance#

  • Four shapes of the style option: omitted / 'default', a bare name, a full URL, an inline object.
  • All four flow through bemap.TilesStyle.fetch() → resolvePlaceholders() → hardenSymbolCollisions() before MapLibre sees them.
  • bemap.fallbackStyle: "BeMap Fallback (charte 2026, no fonts)": fill/line only, no symbol layers, no glyphs, so it never requests a font.
  • Discovery: map.fetchAvailableStyles() → { styles, defaultStyle }; runtime swap: map.setStyle('<name>').
  • Two placeholders: TILES_SOURCE (rewritten to pmtiles://<tilesHost>/<tilesFile>) and __BILINGUAL_PLACE__ (a MapLibre format expression).
  • Localized names are name_fr / name_en: underscore, not the name:fr OpenMapTiles convention.
  • The library ships no fonts. A root-relative glyphs is absolutised against the tiles host; override with ctx.glyphsUrl.
  • setStyle() is overlay-preserving: bemap.TilesOverlayCatalog replays your shapes, including on the 401 → refresh → reload path.
  • Styles are authored in BeMaputnik, the BeNomad style editor at https://bemaputnik.benomad.net.

Usage#

The default path: pin nothing, and the map tracks whatever the worker publishes:

js
var ctx = new bemap.Context({
    login:     'YOUR_LOGIN',
    password:  'YOUR_PASSWORD',
    secure:    true,
    host:      'bemap.benomad.com',
    tilesHost: 'mptiles-api.benomad.net'
});

// No `style` option → tiny fallback now, live server charte right after login.
var map = new bemap.MapLibreMap(ctx, 'map').move(2.35, 48.85, 12);
// Style-load failures are reported in the browser console - 2.0.2 has no
// public error subscription. See "Map errors" on the Events page.

List what the worker allows for your account, then swap at runtime:

js
map.fetchAvailableStyles().then(function (cfg) {
    // cfg.styles      → ['openfreemap_graylevel', 'charte_2026', …]
    // cfg.defaultStyle → the name applied when you pin none
    cfg.styles.forEach(function (name) { console.log(name, name === cfg.defaultStyle); });
});

map.setStyle('openfreemap_graylevel');   // bare name → <tilesHost>/styles/openfreemap_graylevel

Pin a style explicitly when you do not want the server default: a name, a URL on your tiles host, or an inline object:

js
new bemap.MapLibreMap(ctx, 'map', { style: 'openfreemap_graylevel' });
new bemap.MapLibreMap(ctx, 'map', { style: 'https://mptiles-api.benomad.net/styles/charte_2026.json' });
new bemap.MapLibreMap(ctx, 'map', {
    style: {
        version: 8,
        metadata: { source_placeholder: 'TILES_SOURCE' },
        sources: { 'TILES_SOURCE': { type: 'vector', url: '' } },
        layers: [ { id: 'bg', type: 'background', paint: { 'background-color': '#f4f1ea' } } ]
    }
});

Point the font server somewhere other than the tiles host: this overrides every style's glyphs:

js
var ctx = new bemap.Context({
    tilesHost: 'mptiles-api.benomad.net',
    glyphsUrl: 'https://my-host.example.com/fonts/{fontstack}/{range}.pbf'
});

Reference#

Style resolution precedence#

style optionResolution
omitted or 'default'bemap.fallbackStyle paints immediately (no network, no fonts), then (once login completes) GET /api/default-style names the live style and GET /styles/<name> loads it through an overlay-preserving setStyle(). A failure is swallowed: the fallback simply stays, never a blank canvas.
'<name>'Fallback first, then <tilesHost>/styles/<name>. A value already carrying a path ('styles/foo.json', as returned by /api/styles) resolves against the host root instead of being re-nested.
'<url>'Fetched as-is. Auth is injected only when the URL contains ctx.tilesHost: session cookie by default, X-Session-Token header or ?token= per tilesAuth. Other origins get a plain fetch, so nothing leaks cross-origin.
{ …json… }Deep-cloned and used as-is. Placeholders are still resolved, so an inline style can carry TILES_SOURCE.

Map methods#

MemberReturnsBehaviour
fetchAvailableStyles()Promise<Object>GET /api/styles → { styles, defaultStyle }. Waits for login. Rejects with MAPLIBRE_ONLY on Leaflet / OpenLayers
fetchDefaultStyle()Promise<Object>GET /api/default-style → the name of the style applied when you pin none
setStyle(urlOrObject, [options])thisName, URL or inline object. Returns synchronously for chaining; the URL path resolves asynchronously and reports failures as STYLE_LOAD_FAILED on the map error channel. Marks the map as explicitly styled
getStyle()ObjectThe resolved spec the library applied: placeholders substituted, glyphs absolutised
setPaintProperty / setLayoutProperty / setFilter / setLayerZoomRangethisSurgical passthroughs for one-off tweaks; no-ops (warn once) on Leaflet and OpenLayers

Context URL helpers#

MemberResult
ctx.getTilesBaseUrl()https://<tilesHost><tilesPath>
ctx.getTilesStylesUrl()<base>/api/styles: the allow-listed style names for this environment
ctx.getTilesDefaultStyleUrl()<base>/api/default-style: the default-style pointer
ctx.getTilesStyleUrl(name)<base>/styles/<name>, or <base>/<path> when name already contains a /. null without a tilesHost

bemap.TilesStyle#

Pure functions, unit-testable without instantiating MapLibre. You rarely call them yourself: setStyle() and the constructor already do.

MemberSignaturePurpose
SOURCE_PLACEHOLDERString'TILES_SOURCE': override per style via metadata.source_placeholder
PLACE_LABEL_PLACEHOLDERString'__BILINGUAL_PLACE__': override via metadata.place_label_placeholder
fetch(ctx, urlOrObject, [options])Promise<Object>Coerces input to a deep-cloned style object. Objects resolve immediately; strings are fetched with the tiles auth applied when the URL matches ctx.tilesHost. options.getToken supplies the JWT for header / query modes
resolvePlaceholders(rawStyle, ctx, [tilesFile])ObjectAbsolutises glyphs, rewrites the source to pmtiles://<tilesHost>/<tilesFile> under the key tiles, repoints every layer, and substitutes the bilingual text-field. Mutates and returns the input
buildBilingualExpr()ArrayEmits the MapLibre format expression used for place labels
hardenSymbolCollisions(styleObject)ObjectSets symbol-avoid-edges on every symbol layer, plus text-padding: 200 and text-optional: true on layers whose id starts with road_shield, so the OSM tileset stops fighting itself at low zoom

Placeholders#

PlaceholderWhereReplaced with
TILES_SOURCEA source key, and source: on each layerA vector source named tiles with url: 'pmtiles://<tilesHost>/<tilesFile>'. The style's own url / tiles template is deliberately discarded, so every BeNomad style routes through the worker for auth, gating and edge cache. Only the placeholder key or tiles is rewritten: a source keyed anything else is left untouched
__BILINGUAL_PLACE__layout['text-field']A format expression: coalesce(name_<browserLang>, name_en, name) in Noto Sans Bold, and (only when it differs) the local name on a second line at font-scale 0.75 in Noto Sans Regular

BeMaputnik: the style editor#

ItemValue
URLhttps://bemaputnik.benomad.net
What it isA MapLibre/Maputnik-based visual style editor, hosted by BeNomad, previewing live against BeNomad Tiles
AuthThe same tiles session: it logs in and carries the JWT on the style, glyph and PMTiles requests
Style markersBeNomad styles carry "maputnik:renderer": "mlgljs" and metadata.source_placeholder (plus place_label_placeholder on label-bearing styles)
No service workerEvery reload re-fetches, so an edit is visible immediately

Workflow. Open a style in BeMaputnik and edit it against a live preview → the reviewed style is published server-side into the worker's allow-list → applications pick it up automatically through /api/default-style, or explicitly with map.setStyle('<name>'). Keep the placeholders intact when saving: strip TILES_SOURCE and the style stops routing through the worker; strip __BILINGUAL_PLACE__ and labels fall back to the raw local name.

Notes#

Why the fallback-then-live dance matters. It is the single most important idea on this page. Shipping the charte inside the bundle would mean a library rebuild and a customer redeploy for every cartographic change. Instead the bundle carries only a ~10-layer fill/line style for the first frame, and the authoritative charte lives on the worker: versioned, cached at the edge, and swapped in a few hundred milliseconds later. The swap is skipped if you call setStyle() yourself during that async gap, so an explicit choice always wins.

bemap.fallbackStyle. Frozen object, name "BeMap Fallback (charte 2026, no fonts)": background, earth, landcover, water, river lines and major roads / highways, with colours lifted from the charte so the transition is barely visible. No symbol layers and no glyphs key at all: it can never trigger a font request or a font 404. bemap.defaultStyle is a deprecated back-compat alias of the same object.

Glyphs. The library ships no fonts and there is no dist/fonts/ to deploy. Server styles declare their own glyphs; a root-relative one such as /fonts/{fontstack}/{range}.pbf is absolutised against the tiles host, because MapLibre resolves a relative glyphs in an object style against the page origin, which 404s and blanks every label. An absolute (https://…) or protocol-relative (//host/…) value is left alone. ctx.glyphsUrl overrides all of them.

Overlay preservation. Native setStyle() wipes user-added sources and layers. bemap.TilesOverlayCatalog stores a factory per overlay and replays them once MapLibre fires 'idle': polylines, polygons, circles, raster layers, heatmaps and 3D buildings. This covers the internal 401 → refresh → setStyle renewal path, so a session timeout cannot destroy a customer's overlays. Markers, multimarkers and popups are DOM overlays that live outside the style: setStyle() never touches them, and they are deliberately excluded from the replay set (replaying would duplicate them).

Projection. The SDK owns the root projection: your setProjection('globe') choice is remembered and re-asserted on every subsequent style swap, and a server style shipping "projection": {"type": "globe"} is overridden back to the SDK value with a one-time console.info. Opt in explicitly if you want a globe.

Gotchas#

  • Localized names use an underscore: name_fr, name_en, not name:fr. BeNomad OSM PMTiles do not follow the OpenMapTiles colon convention. A ['get', 'name:fr'] in a hand-written expression silently misses on every feature and collapses the label to the local name, or to nothing. Verified in bemap-tiles-style.js, buildBilingualExpr().
  • An unreachable glyph server makes labels vanish silently. No exception, no error event, just an empty-looking map. If a label-heavy style renders as bare geometry, check the fonts/{fontstack}/{range}.pbf requests in the network panel before anything else.
  • Custom MapLibre layers added straight to map.native are wiped by every style swap. They are not in the catalogue. Re-add them from a map.native.on('style.load', …) handler so they survive the server-default swap and any token-renewal reload.
  • Cluster layers created with addClusterPoints() are not replayed either: unlike polylines and polygons, they are not registered in the overlay catalogue. Re-add them after a style change.
  • The style's own tile source is ignored, by design. Some third-party styles ship a hardcoded archive URL that would 404; the SDK always repoints the conventional source at your configured tileset on the worker.
  • Calling setStyle() during startup cancels the server-default swap. That is intentional, but it means a style you set in a load handler can look like "the default never arrived".
  • resolvePlaceholders() mutates its argument. Pass a clone if you keep a reference to the raw style.
  • fetchAvailableStyles() is MapLibre-only. On Leaflet and OpenLayers it rejects with MAPLIBRE_ONLY: those engines render WMS raster, where setStyle('<name>') re-applies a WMS STYLES value instead.

See also#

  • Overview & access: the BeNomad Tiles chapter, environments and integration paths
  • Web: PMTiles: how the pmtiles:// source the placeholder produces is actually served
  • Sessions & tokens: the auth injected into style, glyph and tile requests
  • Cache & slices: why a style swap is cheap on a warm cache
  • Troubleshooting: blank map, missing labels, 401 / 403 symptoms
  • MapLibre maps: the engine, setStyle() in context, projection and camera