OpenWeather Maps SDK v0.0.0 docs
Guides

Architecture & Core Concepts

Dependency graph

@openweather/maps-react
    ├── @openweather/maps-maplibre (optional peer, ./maplibre subpath only)
    │       ├── @openweather/maps-core
    │       └── @openweather/maps-gl
    └── @openweather/maps-ui-core

@openweather/maps-ui
    ├── @openweather/maps-core
    └── @openweather/maps-ui-core

@openweather/maps-ui-core is the shared, framework-agnostic view-model layer (legend, timeline, layer grouping, overlays, point-query, query-flow, pins, layout, units, visuals) behind the SDK's simple UI kit — the same modules back both @openweather/maps-ui (DOM) and @openweather/maps-react's ./ui (React), so the two plain-styled renderers can't drift from each other.

  • @openweather/maps-core has no MapLibre / React / WebGL dependency.
  • @openweather/maps-ui-core has no DOM / MapLibre / React / WebGL dependency — pure view-models, checked in CI the same way @openweather/maps-core is (pnpm verify:core-isolation).
  • @openweather/maps-gl and @openweather/maps-maplibre peer-depend on maplibre-gl ≥ 5.
  • @openweather/maps-react peer-depends on react/react-dom ≥ 18 and, only for its ./maplibre subpath, on @openweather/maps-maplibre + maplibre-gl — importing . or ./ui never pulls MapLibre into your bundle.

Rendering pipeline

App (vanilla / React)
  createWeatherMap()  or  <WeatherMap> + hooks / compounds
        │
        ▼
@openweather/maps-maplibre
  WeatherMapHandle · ScalarFlowRenderer · RasterLayerManager
  ContourLayerManager · CycloneOverlay · LightningOverlay · CropMask
  MultiTileScalarManager (internal) · PointQuerySampler (internal)
        │                              │
        ▼                              ▼
@openweather/maps-core                    @openweather/maps-gl
  WeatherEngine                    ScalarFlow plugin
  LayerRegistry + catalog          createScalarLayer
  ProviderRegistry                 createParticlesLayer
  Timeline / Search / Query        createWavesLayer
  SettingsStore (external)         contour (CPU / worker / GPU)
                                   shaders (WebGL2 + WebGL1)
        │
        ▼
maps.openweathermap.org  (Maps 3.0 tiles, radar, GeoJSON)

Design principles

  1. Core owns business state. Layer selection, date alignment, resolve/prefetch, and entitlements all live in @openweather/maps-core. UI and renderers subscribe; they never duplicate domain logic in component state.
  2. Metadata-driven layers. Each layer is a LayerMetadata record — path, format, color ramp, cadence, models, contour config, crop mask, entitlement key. The catalog is hardcoded in @openweather/maps-core (DEFAULT_LAYER_CATALOG); pass availableLayers to override it. There is no remote metadata endpoint.
  3. Providers resolve assets. tile | raster | geojson providers each return a ResolvedLayerAsset (URL, date, lock status, scalar config). The MapLibre adapter paints whatever was resolved — it never talks to a data source directly.
  4. Stores are getSnapshot() + subscribe(). Every stateful piece (engine, settings, layers registry, timeline) follows this shape, which is React.useSyncExternalStore-compatible by construction — see Events & State.

Data pipeline

Timeline / setDate / setActiveLayer
        │
        ▼
 WeatherEngine.resolveActiveLayer()
        │
        ├── ProviderRegistry (tile | raster | geojson)
        │         │
        │         ├── buildLayerUrl / buildRasterTileUrl
        │         ├── alignDateForLayer
        │         └── probeLayerAsset (HEAD walk) → URL, or LayerLockedError (403) / LayerAuthError (401)
        │
        ▼
 ResolvedLayerAsset { url, date, locked?, tileGrid?, scalarConfig? }
        │
        ▼
 MapLibre adapter renderAsset()
        ├── ScalarFlowRenderer / MultiTileScalarManager
        ├── ContourLayerManager
        ├── RasterLayerManager
        └── particle / waves / cyclone / lightning / crop mask sync

Auth & entitlements

  • The API key (appid) lives in SettingsStore and is appended to tile requests (unless a layer opts out with appendAppId: false — some satellite URLs already carry their own key).
  • 403 on a tile/probe request → LayerLockedError → the engine emits layer:locked with an optional entitlementKey; the render path does not throw, so one locked layer never breaks the rest of the map.
  • 401 → LayerAuthError (tile/probe) — the whole key is invalid, not just one layer, so it routes to the engine's generic error event rather than the per-layer locked-layer UI. See Events & State.
  • Layer catalog: hardcoded DEFAULT_LAYER_CATALOG in @openweather/maps-core. Override with availableLayers on createCoreMapController if you need a subset or custom entries.

Time alignment & prefetch

alignDateForLayer snaps the scrubber date to the layer's real data cadence (floor10m / floor30m / floorHour / hour, or an arbitrary updateCadenceMinutes). After each resolve, the engine prefetches adjacent frames so playback/scrub hits already-resolved assets; an in-flight prefetch is aborted the moment a new tick supersedes it.

Point query

PointQueryController (in @openweather/maps-core) is renderer-agnostic — it has no pixel access of its own. The MapLibre adapter injects a sampler that:

  1. Maps lng/lat to a texel in the active scalar texture (or assembled multi-tile canvas).
  2. Reads the normalized channel.
  3. Runs decodeScalarValue with the layer's value scale + range.
  4. Applies displayScale / displayUnits / displayDigits for the UI (e.g. Pa → hPa).

Low-level technology

MapLibre GL JS v5, WebGL1 + WebGL2

MapLibre v5 can hand a custom layer either a WebGLRenderingContext or a WebGL2RenderingContext. Every shader in @openweather/maps-gl therefore ships in two variants:

Variant Files Notes
WebGL2 (#version 300 es) *.vertex.glsl / *.fragment.glsl Primary path; VAOs, transform feedback
WebGL1 (GLSL ES 1.00) *.gl1.*.glsl attribute/varying, texture2D, gl_FragColor

detectCapabilities(gl) picks the program set once per onAdd. On WebGL1: attributes are re-bound each frame (no VAO isolation), scalar interpolation caps at Bicubic (no Lanczos/RBF), and the particle layer needs vertex-texture-fetch support — if MAX_VERTEX_TEXTURE_IMAGE_UNITS is 0, particles emit renderer:degraded and draw nothing rather than garbage.

Data-encoded tiles (Maps 3.0)

Unlike classic colored PNG weather tiles, Maps 3.0 serves data-encoded images. Pixel channels store normalized physical values; the client loads the image, uploads it as a WebGL texture, decodes + colorizes in the fragment shader against the layer's color ramp and value scale, and optionally runs particle/wave/contour passes from the same field. Default scalar base URL: https://maps.openweathermap.org/maps/3.0/gfs025 (radar and other products use alternate bases).

Wind particles

Two update engines, chosen once via capabilities.hasTransformFeedback:

Engine Context Position storage Notes
Texture WebGL1 (or WebGL2 fallback) RGBA8 ping-pong textures (~16-bit/axis) Fragment update via FBO; draw VS samples a position texture
Transform feedback WebGL2 float32 VBOs Update VS + RASTERIZER_DISCARD; no FBO; no 16-bit jitter

A shared screen-space trail-fade pass is identical for both engines. Particle budget scales with zoom and getDeviceTier() (high | medium | low).

Contours / isolines

  1. Decode the scalar field from the displayed image (prepareContourField).
  2. Extract isolines — CPU marching squares, optionally off the main thread in a Web Worker, or via WebGPU where available.
  3. Stitch segments into polylines, with optional smoothing and progressive assembly.
  4. Render as a MapLibre GeoJSON line + label layer, with optional H/L extrema markers.

Multi-tile assembly & radar pyramids

The global overview is typically tile 0/0/0. On zoom-in, MultiTileScalarManager fetches a grid (or pyramid), stitches it into one canvas, and feeds it to ScalarFlow as a single texture. Missing frames are found via a HEAD probe with a per-model policy (offset, step, max steps) — radar walks back in 10-minute steps, for example. Fetches are cancellation-aware and cache-shared: a superseded selection aborts its own in-flight tile fetches without breaking a still-live caller sharing the same URL, and ImageBitmap results are explicitly disposed on LRU eviction and cache clear.

Repository layout

This SDK lives in one pnpm-workspace monorepo rather than per-package repos + submodules — atomic changes across core/adapter/React, one CI pipeline, and Changesets-aligned semver across all six packages. See the repo's docs/ARCHITECTURE.md for the full rationale, including why the WebGL renderer (@openweather/maps-gl) was internalized from a formerly-standalone sibling library.