OpenWeather Maps SDK v0.0.0 docs
Guides

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 — a wss:// URL for the live lightning overlay; omit it and toggleLightningOverlay() is a no-op. Lightning needs its own stream key, distinct from the tile appid.
  • 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.