Vanilla (MapLibre) Guide
@openweather/maps-maplibre's createWeatherMap() is the fastest path to a working, framework-free
weather map: it creates a CoreMapController, mounts a MapLibre map into your container, and wires
every renderer manager (scalar/particles/waves, contours, cyclones, lightning, crop mask) to that
one controller's engine.
Constructor options
interface WeatherMapOptions {
apiKey: string;
container: string | HTMLElement;
center?: [number, number];
zoom?: number;
mapStyle?: string | maplibregl.StyleSpecification;
beforeLayerId?: string;
defaultLayer?: string;
defaultRadarModel?: string;
dataBaseUrl?: string;
satelliteParams?: Record<string, string>;
cycloneFallbackUrl?: string;
lightningStreamUrl?: string;
}
beforeLayerId— inserts weather layers below this MapLibre style layer (defaults to the style's own land/coastline outline layer, so text and outlines stay above colorized fields).lightningStreamUrl— awss://URL for the live lightning overlay; omit it andtoggleLightningOverlay()is a no-op. Lightning needs its own stream key, distinct from the tileappid.dataBaseUrl— override the Maps 3.0 tile base for a different backend/environment.
WeatherMapHandle
const map = await createWeatherMap({ apiKey: 'YOUR_KEY', container: '#map' });
| Method | Purpose |
|---|---|
setLayer(code) |
Activate a catalog layer and resolve/render it |
setDate(date) |
Align + resolve for timeline scrub/play |
setLayerModel(layerCode, modelId) |
Per-layer model picker (e.g. radar nowcast) |
setRadarModel(modelId) |
Deprecated alias for setLayerModel('radar', modelId) |
toggleParticlesOverlay(enabled) |
Unified wind/waves overlay preference |
toggleWindParticles / toggleWaves |
Deprecated aliases |
toggleCycloneOverlay(enabled) |
Storm-track GeoJSON overlay |
toggleLightningOverlay(enabled) |
WebSocket strike overlay (needs lightningStreamUrl) |
toggleContours(enabled) |
Isolines, for layers with a contours config |
updateLayerConfig(config) |
Live scalar paint tweaks (contrast, blur, ramp, …) |
updateParticleConfig(partial) |
Live particle physics/visual tweaks |
updateWavesConfig(partial) |
Live wave-glyph config tweaks |
updateRasterParams(params) |
Satellite/raster URL + opacity params |
on(event, listener) |
Subscribe to engine lifecycle events — see Events & State |
getMap() / getController() / getEngine() |
Escape hatches to the underlying MapLibre map, core controller, and engine |
getCycloneInteractiveLayerIds() |
Layer ids to exclude from your own click handlers, so cyclone popups don't fight with your own map-click logic |
destroy() |
Tear down the map, engine, and every overlay manager |
Full example
import { createWeatherMap } from '@openweather/maps-maplibre';
import 'maplibre-gl/dist/maplibre-gl.css';
const map = await createWeatherMap({
apiKey: 'YOUR_KEY',
container: '#map',
center: [10, 50],
zoom: 3,
defaultLayer: 'temperature',
lightningStreamUrl: 'wss://your-lightning-stream?apikey=YOUR_STREAM_KEY',
});
// Switch layers and toggle overlays
await map.setLayer('windSpeed');
await map.toggleParticlesOverlay(true);
await map.toggleContours(true);
// React to entitlement and auth failures
map.on('layer:locked', ({ code, entitlementKey }) => {
showUpgradePrompt(code, entitlementKey);
});
map.on('error', ({ message }) => {
showErrorBanner(message);
});
// Scrub the timeline
await map.setDate(new Date(Date.now() - 3 * 60 * 60 * 1000));
// Cleanup, e.g. on route change / component unmount
// map.destroy();
Adding the simple UI kit
Rather than hand-rolling a layer picker, legend, timeline, search box, and point-query box
yourself, hand your WeatherMapHandle to createSimpleMapUI() from @openweather/maps-ui:
import { createWeatherMap } from '@openweather/maps-maplibre';
import { createSimpleMapUI } from '@openweather/maps-ui/maplibre';
import '@openweather/maps-ui-core/styles/tokens.css';
import '@openweather/maps-ui-core/styles/simple.css';
const handle = await createWeatherMap({ apiKey: 'YOUR_KEY', container: '#map' });
const ui = createSimpleMapUI({ handle, apiKey: 'YOUR_KEY' });
// ui.destroy() alongside handle.destroy() when tearing down.
This assembles a status-text shell, a flat layer panel, a legend, overlay toggles, a timeline range
control, search, zoom, and a point-query box — plain, unstyled-by-default controls with no art
assets, no fan-out/fold layout, and no per-overlay config panels. See each control's page under
Simple UI Kit for what it renders and how to swap pieces out. For the exact wiring this
replaces, see examples/vanilla/src/main.ts in the repo — it resolves a key, calls
createWeatherMap, then calls createSimpleMapUI, plus a small "API tour" that logs the rest of
the controller surface to the console.
OpenWeather's own fuller designed UI — a fan-out layer rail, a day-axis timeline with a speed menu,
per-overlay config panels, and a One Call weather detail panel — now lives in a separate
weather-maps website repo, built on the same headless @openweather/maps-ui-core view-models.
Composing your own controls
createSimpleMapUI is convenient, but sometimes you want just a layer picker and a legend, in your
own layout, without the rest of the bundle. Every control is a standalone create*(options) => { update?, destroy() } factory — mount only the ones you need, and wire them to handle yourself.
This is a complete, runnable example: a map, a layer panel, a legend that follows the active layer,
and zoom buttons — no createSimpleMapUI involved.
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css" />
<style>
html,
body,
#map {
margin: 0;
height: 100%;
}
#controls {
position: absolute;
top: 12px;
left: 12px;
z-index: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="controls"></div>
<script type="module" src="/main.ts"></script>
</body>
</html>
import { createWeatherMap } from '@openweather/maps-maplibre';
import {
createLayerPanel,
createLegendBar,
createZoomControl,
} from '@openweather/maps-ui';
import '@openweather/maps-ui-core/styles/tokens.css';
import '@openweather/maps-ui-core/styles/simple.css';
import 'maplibre-gl/dist/maplibre-gl.css';
const apiKey = 'YOUR_KEY';
const controlsContainer = document.getElementById('controls')!;
const handle = await createWeatherMap({
apiKey,
container: '#map',
center: [10, 50],
zoom: 3,
defaultLayer: 'temperature',
});
const controller = handle.getController();
const map = handle.getMap();
// Layer panel — a flat, grouped list of the catalog's popular layers.
const layerPanel = createLayerPanel({
parent: controlsContainer,
available: controller.listLayers(),
activeCode: controller.engine.getSnapshot().activeLayerCode,
onSelect: (code) => handle.setLayer(code),
});
// Legend — follows whichever layer is active; re-render it on every layer change.
const legend = createLegendBar({
parent: controlsContainer,
settings: controller.settings,
});
legend.update(
controller.getLayerMetadata(controller.engine.getSnapshot().activeLayerCode),
);
// Zoom buttons — stateless, just call the map directly.
const zoom = createZoomControl({
parent: controlsContainer,
onZoomIn: () => map.zoomIn(),
onZoomOut: () => map.zoomOut(),
});
// Keep the layer panel's active button and the legend in sync with real layer changes
// (including ones triggered from outside this UI, e.g. `handle.setLayer()` called elsewhere).
handle.on('layerchange', ({ code }) => {
layerPanel.update({
parent: controlsContainer,
available: controller.listLayers(),
activeCode: code,
onSelect: (newCode) => handle.setLayer(newCode),
});
legend.update(controller.getLayerMetadata(code));
});
// Teardown, e.g. on route change:
// layerPanel.destroy(); legend.destroy(); zoom.destroy(); handle.destroy();
Add overlay toggles, search, or the point-query box the same way — each is documented under
Simple UI Kit with its own options table. Skip createShell() entirely if you don't need a
status-text node; nothing else in the kit depends on it.
Point query, without the point-query box
If you want raw point-query values without any on-screen UI, use getController()'s pointQuery
(a PointQueryController) directly — see @openweather/maps-core
and Architecture — Point query.