Skip to content
MCP ThesaurusMCP Thesaurus

Mapsgl

CommunityExcellent87/100Claim

MITupdated 7d ago

The MapsGL JavaScript SDK (@xweather/mapsgl) renders weather and custom map data client-side in WebGL, layered on top of Mapbox GL, MapLibre GL, Google Maps, or Leaflet. It requires an active Xweather account with Weather API + Maps access (client id + secret).

SourceWebsiteDocs2

What can you do with Mapsgl?


name: mapsgl description: This skill should be used when working with the Xweather MapsGL JavaScript SDK for the web (@xweather/mapsgl) — setting up a MapsGL map controller for Mapbox GL, MapLibre GL, Google Maps, or Leaflet, and adding, removing, styling, filtering, masking, or animating MapsGL weather layers and custom data layers. Use it whenever a task mentions MapsGL, aerisweather.mapsgl, addWeatherLayer, weather map layers, or client-side WebGL weather rendering. Also use it for questions about how MapsGL usage or cost is measured — sessions, the 5-minute clock intervals, the 150x access multiplier, or how many accesses a MapsGL map consumes. Also covers Xweather's attribution requirement — the 'Powered by Vaisala Xweather' credit and logo rules that apply wherever Xweather data or imagery is displayed. license: MIT metadata: author: Vaisala Xweather version: "0.14.0"

MapsGL JavaScript SDK

The MapsGL JavaScript SDK (@xweather/mapsgl) renders weather and custom map data client-side in WebGL, layered on top of Mapbox GL, MapLibre GL, Google Maps, or Leaflet. It requires an active Xweather account with Weather API + Maps access (client id + secret).

This skill is the web-based JavaScript SDK. For a native Swift app on iOS, iPadOS, Mac Catalyst, or visionOS, use the mapsgl-apple skill instead — a separate SDK with a Swift API (MapboxMapController, WeatherService.LayerCode), its own install channels, and a smaller layer set. The concepts below transfer; none of the code does. MapsGL ships for Android as well.

Always qualify the name as the MapsGL JavaScript SDK. Xweather ships an unrelated product simply called the JavaScript SDK (https://www.xweather.com/docs/javascript-sdk), so the bare term is ambiguous. Xweather's own naming for the family is MapsGL, MapsGL iOS, and MapsGL Android; "MapsGL JavaScript SDK" is the unambiguous way to name this one.

How to write MapsGL code examples

Default to a single self-contained HTML file using vanilla JavaScript and the CDN build. One file the user can save and open in a browser — CDN <script>/<link> tags, a <div> for the map, and a plain <script> block. No build step, no bundler, no package installs, no framework.

In that form, MapsGL lives on the global aerisweather.mapsgl namespace:

const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });

Look up the current released version before writing any example. Don't reuse a version from memory or from an earlier turn — MapsGL ships often, and a stale pin is the most common thing to go wrong in otherwise-correct example code:

curl -s https://www.xweather.com/docs/api/releases/versions \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["products"]["mapsgl"]["version"])'

That endpoint is the release source of truth for every Xweather product, keyed by product id — mapsgl here, alongside weather-api, maps, maps-ui-sdk, mapsgl-apple-sdk, mapsgl-android-sdk, mcp-server, phrases-api. It's a small public JSON document, no auth needed.

MapsGL's own script and stylesheet always come from cdn.aerisapi.com, with that version substituted:

<link href="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css" rel="stylesheet" />
<script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js"></script>

1.9.4 above is the version at the time this file was last regenerated — use it only as a fallback if the endpoint can't be reached, and say so when you do.

Note that npm may be ahead of the released version. @xweather/mapsgl on npm has carried a higher version than the releases endpoint reports, and cdn.aerisapi.com serves those newer paths too — so a version that resolves is not evidence it's the current release. Trust the releases endpoint, not npm and not a 200 from the CDN.

Never substitute unpkg.com, cdn.jsdelivr.net, or another npm mirror for those two tags — the npm build exposes a different global and the page breaks at runtime even though the files load. See "MapsGL's own assets come from cdn.aerisapi.com" under Setup for why.

Every generated example must include the Xweather attribution — a link to https://www.xweather.com/ reading "Powered by Vaisala Xweather", or the logo. It's a requirement of using the product, not a nicety, so build it into the markup rather than mentioning it afterwards. The complete example below shows it positioned over the map. See "Attribution is required" for the rules.

Only produce npm / ES-module / bundler code when the user explicitly asks for it, or when they're plainly already working in such a project — an existing package.json, a src/ tree with imports, or they name a bundler. Same for React or any other framework: only on explicit request. Don't offer a framework version alongside the vanilla one "in case", and don't reach for a framework because the task looks app-shaped.

When the user is in an npm project, the translation is mechanical: swap the CDN tags for import * as mapsgl from '@xweather/mapsgl' plus the map library's own import, and replace aerisweather.mapsgl. with mapsgl.. Everything else — the controller API, layer codes, paint objects, expressions — is identical, so the rest of this skill applies unchanged.

Core concepts

Concept What it is
Account Wraps client id/secret credentials used for all data requests
MapController Adapter between the underlying map instance (Mapbox/MapLibre/Google/Leaflet) and MapsGL — the object almost everything below is called on
DataSource Where layer data comes from: raster, vector, geojson, or encoded (RGBA-packed weather grids)
WebGLLayer A visual rendering of a source: raster, fill, line, circle, sample, grid, contour, particle, heatmap, symbol
paint Per-layer style config (colors, radii, colorscales, etc.), keyed by render type — see references/styles.md
Expressions ['operator', ...args] arrays for data-driven paint values and filters — see references/expressions.md
ColorScale Maps a continuous data range to a color gradient/steps, used by paint.sample/paint.heatmap and gradient legends — see references/color-scales.md
Legend (LegendControl) An on-map UI element showing what a layer's colors/symbols mean; categorical (points) or gradient (bar) — see references/legends.md
DataInspectorControl An on-map UI element that shows raw layer values at the clicked/hovered point
timeline (Timeline) Drives time-based animation (play/pause/scrub) across every animated layer on a controller at once — see references/timeline.md

Built-in weather layers are pre-wired combinations of an encoded source + one or more styled layers, addressed by a single string code (e.g. 'temperatures', 'radar', 'alerts'). Prefer these over hand-building sources/layers unless visualizing custom or non-weather data.

Usage is measured in sessions

MapsGL bills in sessions, not tiles, layers, or requests. A session is a continuous interaction with a MapsGL map for up to 5 minutes, starting when any weather layer is added. On a Weather API and Maps subscription, 1 session = 150 accesses (a 150× multiplier).

Three rules produce every answer:

  1. Sessions align to the wall clock — boundaries at :00, :05, :10, :15. Not a rolling window from first interaction. What matters is how many 5-minute buckets the viewing touches, not how long it lasted.
  2. At least one session per data request. No proration — 150 accesses is the floor.
  3. Inside a session, everything is free: panning, zooming, animating, refreshing, and toggling layers. Layer count does not affect cost.

When asked how usage is measured, show the arithmetic — buckets → sessions → accesses — rather than just a number. The documented example:

A user views a radar layer from 8:03 to 8:07 — 4 minutes, but it straddles the :05 boundary, so it touches two buckets (8:00–8:05 and 8:05–8:10) = 2 sessions = 300 accesses.

The same 4 minutes from 8:05 to 8:09 touches one bucket = 1 session = 150 accesses. Same duration, half the cost, purely from clock alignment.

Two consequences worth volunteering unprompted, because they invert the intuition people bring from tile-based pricing:

Adding layers is free. Five layers viewed for four minutes costs 1 session — the same as one layer. There is no cost reason to limit how many layers a user enables. (On Raster Maps the same five layers would cost 5×.)

Short visits get almost no discount. A 30-second view averages ~1.1 sessions and a 5-minute view ~2.0, so a visit ten times shorter costs 55% as much, not 10%. Drive-by page loads are the expensive traffic shape.

For capacity planning, a view of d minutes starting at a random time averages floor(d/5) + 1 + (d mod 5)/5 sessions — not d/5. Assuming one session per short view underestimates by 10–20%. Table of common durations in references/sessions.md.

The only real lever is when layers are on the map: don't call addWeatherLayer until the user asks for weather, and removeWeatherLayer when the map goes out of view or idle. Optimising layer count, animation, or interaction is pointless — those are free.

Full model, more worked examples (long-running displays, high-traffic short visits), the MapsGL vs. Raster Maps billing comparison, and reduction tactics: references/sessions.md.

Setup

1. Get API credentials

MapsGL needs two separate sets of credentials, and both are required — the map won't render without either one:

  1. Xweather account keys (CLIENT_ID / CLIENT_SECRET) — generated from the account portal at https://data.portal.xweather.com/account/keys. These authenticate MapsGL's own data requests and are what get passed to new aerisweather.mapsgl.Account(id, secret).
  2. The underlying map provider's own key/token — independent of Xweather:
    • Mapbox GL → mapboxgl.accessToken (Mapbox account access token)
    • MapLibre GL → no key needed, but the style URL usually comes from a tile provider (e.g. MapTiler) that does require its own key
    • Google Maps → a Google Maps JavaScript API key, plus a Map ID with vector-map support enabled (required — MapsGL renders as a WebGL overlay on the vector basemap)
    • Leaflet → no key needed for the base L.tileLayer, though the tile provider used may require one

If a user reports "nothing renders" or an auth error, check both credential sets before digging into MapsGL-specific config.

2. Install

Every install needs @xweather/mapsgl plus the map library being wrapped — MapsGL does not bundle it, whether installing via CDN or npm.

CDN — include the map provider's own <link>/<script> tags in addition to MapsGL's, not instead of them. MapsGL's CDN build exposes everything under window.aerisweather.mapsgl.

MapsGL's own assets come from cdn.aerisapi.com — nowhere else

Copy these two lines verbatim, changing only the version number:

<link href="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css" rel="stylesheet" />
<script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js"></script>
https://cdn.aerisapi.com/sdk/js/mapsgl/{version}/aerisweather.mapsgl.js
https://cdn.aerisapi.com/sdk/js/mapsgl/{version}/aerisweather.mapsgl.css

Never serve MapsGL from unpkg.com, cdn.jsdelivr.net, esm.sh, or any other npm mirror, and never invent a filename like mapsgl.js or @xweather/mapsgl/dist/... for a <script> tag. This is worth stating flatly because the wrong URL appears to work:

  • https://unpkg.com/@xweather/mapsgl/dist/mapsgl.js returns HTTP 200. So does the .css beside it. There is no network error to notice.
  • But that is the npm build, whose UMD wrapper assigns globalThis.mapsglnot aerisweather.mapsgl. The page then dies at runtime with ReferenceError: aerisweather is not defined, which looks like a MapsGL bug rather than a bad URL.

If a user reports aerisweather is not defined, check the <script src> host first — it is almost always this.

The unpkg/jsdelivr paths are only meaningful in a bundled project that imports @xweather/mapsgl as a module, and even there the import comes from npm, not a CDN URL.

The map library's own CDN tags

These are separate from MapsGL and do legitimately come from unpkg for some providers:

Provider CDN tags for the map library only
Mapbox GL https://api.mapbox.com/mapbox-gl-js/<version>/mapbox-gl.{css,js}
MapLibre GL e.g. https://unpkg.com/maplibre-gl@<version>/dist/maplibre-gl.{css,js}
Leaflet e.g. https://unpkg.com/leaflet@<version>/dist/leaflet.{css,js}
Google Maps <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script> (no separate CSS)

Don't let the unpkg entries in this table bleed into MapsGL's URLs — the rows above cover Mapbox, MapLibre, Leaflet and Google only. MapsGL always comes from cdn.aerisapi.com.

A complete pair of includes, MapsGL plus its map library:

<!-- 1. The map library — Mapbox GL shown; swap for MapLibre/Leaflet/Google per the table above -->
<link href="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css" rel="stylesheet" />
<script defer src="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js"></script>

<!-- 2. MapsGL itself — always cdn.aerisapi.com -->
<link href="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css" rel="stylesheet" />
<script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js"></script>

Pin an explicit version for every <script>/<link> tag (MapsGL's and the map library's) rather than latest, for anything beyond a quick prototype. cdn.aerisapi.com/sdk/js/mapsgl/latest/… does resolve if you need it.

For MapsGL's version, use the releases endpoint — not npm, which can be ahead of the current release:

curl -s https://www.xweather.com/docs/api/releases/versions \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["products"]["mapsgl"]["version"])'

npmonly when the user is already in a bundled project or asks for it. Install @xweather/mapsgl plus whichever provider package applies:

npm install --save @xweather/mapsgl mapbox-gl        # Mapbox GL
npm install --save @xweather/mapsgl maplibre-gl       # MapLibre GL
npm install --save @xweather/mapsgl leaflet           # Leaflet
npm install --save @xweather/mapsgl                   # Google Maps — loaded via <script> from Google, no npm package needed
import * as mapsgl from '@xweather/mapsgl';
import '@xweather/mapsgl/dist/mapsgl.css';

3. Container markup

The map container needs explicit dimensions — a blank/invisible map is almost always a missing CSS rule, not a JS error:

<div id="map"></div>
<style>
  body, html { margin: 0; padding: 0; }
  #map { width: 100%; height: 100vh; }
</style>

4. Initialize a controller

Every provider follows the same pattern: create the native map, wrap it in the matching *MapController, wait for load, then add layers. Only the map-creation step differs.

mapboxgl.accessToken = 'MAPBOX_TOKEN';
const map = new mapboxgl.Map({
  container: document.getElementById('map'),
  style: 'mapbox://styles/mapbox/light-v11',
  center: [-74.5, 40],
  zoom: 3
});

const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });

controller.on('load', () => {
  controller.addWeatherLayer('temperatures');
});
Provider Controller class Map constructor
Mapbox GL MapboxMapController new mapboxgl.Map({...})
MapLibre GL MaplibreMapController new maplibregl.Map({...})
Google Maps GoogleMapController new google.maps.Map(el, { mapId: '...', ... })
Leaflet LeafletMapController L.map('map').setView([lat, lon], zoom)

All four take (map, { account, units?, animation? }). Google's variant also accepts interleaved?: boolean. Always gate MapsGL calls behind controller.on('load', ...) — calling layer/source methods before load throws.

The four controller constructors and the complete controller API (properties, events, all methods) are in references/api-reference.md.

Complete example

This is the shape to produce by default — one file, saveable and openable in a browser. Adapt it rather than starting from scratch: swap the map provider's CDN tags and constructor, change the addWeatherLayer codes, and adjust center/zoom.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>MapsGL + Mapbox GL</title>

    <link href="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css" rel="stylesheet" />
    <script defer src="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js"></script>

    <link href="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css" rel="stylesheet" />
    <script defer src="https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js"></script>

    <style>
    body, html { margin: 0; padding: 0; }
    #map { height: 100vh; width: 100%; }
    /* Attribution is required wherever Xweather data is displayed. */
    #attribution {
        position: absolute; bottom: 8px; right: 8px; z-index: 1;
        background: rgba(255, 255, 255, 0.85); border-radius: 3px;
        padding: 3px 6px; font: 12px system-ui, sans-serif;
    }
    #attribution a { color: #333; text-decoration: none; }
    </style>
</head>
<body>
    <div id="map"></div>
    <div id="attribution">
        <a href="https://www.xweather.com/" target="_blank" title="Powered by Vaisala Xweather">Powered by Vaisala Xweather</a>
    </div>

    <script>
        window.addEventListener('load', () => {
            mapboxgl.accessToken = 'MAPBOX_TOKEN';
            const map = new mapboxgl.Map({
                container: 'map',
                style: 'mapbox://styles/mapbox/light-v11',
                center: [-85.5, 40],
                zoom: 3
            });

            const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
            const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });

            controller.on('load', () => {
                // built-in weather layer, unstyled
                controller.addWeatherLayer('radar');

                // built-in weather layer with a custom style override
                controller.addWeatherLayer('alerts-outline', {
                    paint: {
                        opacity: 0.5
                    }
                });
            });
        });
    </script>
</body>
</html>

Source: https://www.xweather.com/docs/mapsgl/examples/mapbox

Two details that matter in the single-file form specifically:

  • The CDN tags use defer, so wrap the setup in window.addEventListener('load', ...). Without it, mapboxgl and aerisweather aren't defined yet and the script throws.
  • #map needs an explicit height. A silently blank map is nearly always this, not a JS error.

npm / ES modules — only when asked

Same structure: swap the CDN <script> tags for package imports, and aerisweather.mapsgl. for the imported mapsgl. namespace. The window.addEventListener('load', ...) wrapper is unnecessary since bundlers execute the module after the DOM is parsed (keep a defer/module script tag, or bundle into the page's entry point). Nothing else changes.

// main.js — loaded via <script type="module" src="./main.js"></script>
import mapboxgl from 'mapbox-gl';
import * as mapsgl from '@xweather/mapsgl';
import 'mapbox-gl/dist/mapbox-gl.css';
import '@xweather/mapsgl/dist/mapsgl.css';

mapboxgl.accessToken = 'MAPBOX_TOKEN';
const map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/light-v11', center: [-85.5, 40], zoom: 3 });

const account = new mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');
const controller = new mapsgl.MapboxMapController(map, { account });

controller.on('load', () => {
  controller.addWeatherLayer('radar');
});

The host page is then just the container plus <script type="module" src="./main.js"></script> — the #map height rule still applies.

Adding, removing, and listing weather layers

controller.addWeatherLayer('radar');
controller.addWeatherLayer('wind-particles');

// with overrides (data quality, time clamping, paint, legend, mask, filter, ...)
controller.addWeatherLayer('temperatures', {
  data: { quality: aerisweather.mapsgl.DataQuality.low },
});

controller.hasWeatherLayer('radar');            // boolean
controller.getWeatherLayer('temperatures');     // WebGLLayer | WebGLLayer[] | undefined
controller.setWeatherLayerVisibility('radar', false);  // hide without disposing
controller.removeWeatherLayer('radar');         // fully removes + frees resources

controller.weatherLayerIds;                     // currently-active weather layer codes

A weather layer's code (e.g. 'temperatures') is not the same string as its actual layer id. If you'll need to update a weather layer's style/opacity/visibility-via-.show()/.hide() later, capture what addWeatherLayer returns (or call getWeatherLayer(code) later) and operate on that WebGLLayer instance directly — don't pass the code to controller.setPaintProperty(), getLayer(), or moveLayer(), which expect the real layer id and will silently no-op on a code they don't recognize. Composite codes return an array of layers to iterate over. Full explanation and verified example in references/weather-layers.md.

Never guess a layer code — look it up. references/layers.md lists every layer by category with their render type, animatability, cost multiplier, coverage, data range, and update interval. It is generated from the public catalog and refreshed weekly, so grep it first; no network call needed.

If a code isn't there, or the snapshot looks stale, fetch the live catalog:

https://www.xweather.com/docs/api/mapsgl/layers

A plain public JSON endpoint (no auth) returning { layers: [{ id, title, description, type, categories, animatable, dataRange, dataCoverage, updateInterval, multiplier }, ...] }. It overrides the snapshot. For what the authenticated account can actually render — neither file knows about entitlements — ask at runtime:

controller.weatherProvider.getLayerMetadata().then((data) => console.log(data));

No separate forecast layers — one layer spans past and future

Raster Maps splits time across two layers: temperatures is observed, ftemperatures is forecast. MapsGL does not. A single temperatures layer covers -7 to +15 days, and the timeline decides which interval renders.

So don't look for ftemperatures, fradar, or fwind-speeds here — they don't exist. Twelve Raster Maps pairs collapse into one MapsGL layer each:

dew-points · feels-like · heat-index · humidity · radar · satellite · snow-depth · temperatures · visibility · wind-chill · wind-gusts · wind-speeds

Reach a forecast interval by moving the timeline, not by adding a different layer — controller.timeline.containsFuture reports whether the current range includes one. See references/timeline.md.

Range is per-layer, so check it rather than assuming. layers.md lists it for every code. Two cases don't follow the pattern:

  • satellite is past-only (-7 days). Raster Maps has fsatellite reaching +15 days; MapsGL has no forecast satellite at all. That's a missing capability, not a renamed one — don't promise a satellite forecast on MapsGL.
  • Road weather keeps an f split, meaning something different. road-weather-* is a +2 hour nowcast refreshed every 15 minutes; froad-weather-* is a +24 hour forecast refreshed every 6 hours. Both are forecasts, so there the prefix marks range, not past-versus-future.

Some codes are composite (expand to multiple sub-layers, e.g. boundaries, roads, stormcells) — addWeatherLayer returns an array for these, and overrides.childLayers can target one sub-layer by id. All 14 composite codes are listed together at the top of references/layers.md; they're the ones with render type none.

A layer's render type also tells you how to style it — a sample layer takes paint.sample.colorscale, a line layer takes paint.stroke. Reading the type out of layers.md before writing a paint override saves a round of guessing.

Styling layers

Pass a paint object namespaced by render type. Full property tables for every render type (raster, fill, stroke/line, circle, sample, grid, contour, particle, heatmap, icon, symbol, text) are in references/styles.md.

Always use MapsGL expressions — ['operator', ...args] arrays — for any data-driven paint value (color/size/opacity derived from a feature property), not evaluator functions or the { property } shorthand. See references/expressions.md for the full operator reference.

controller.addWeatherLayer('temperatures', {
  paint: {
    sample: {
      colorscale: {
        stops: [-40, '#58005b', 0, '#81e8ff', 20, '#ecf93d', 40, '#6b0001'],
        interval: 5
      }
    }
  }
});

Update a style after the layer exists — for built-in weather layers, get the actual layer instance first; the weather layer code is not a valid layer id (see the "code vs. id" gotcha below):

const tempLayer = controller.getWeatherLayer('temperatures');   // or capture addWeatherLayer's return value
tempLayer.setPaintProperty('sample.colorscale', newColorScale);

For a layer you created yourself with addLayer(id, ...), the id you chose is the real layer id, so controller.setPaintProperty(id, prop, value) works directly.

Custom (non-weather) layer, styled with a static or data-driven fill:

controller.addSource('alerts', {
  type: 'vector',
  url: 'https://maps{s}.aerisapi.com/CLIENT_ID_CLIENT_SECRET/alerts/{z}/{x}/{y}/0.pbf'
});
controller.addLayer('alerts-fill', {
  type: 'fill',
  source: 'alerts',
  paint: { fill: { color: ['get', 'COLOR'], opacity: 0.6 } }
});
// ...
controller.removeLayer('alerts-fill');
controller.removeSource('alerts');   // only after no layers reference it

For color scales (built-in named palettes + custom stops) see references/color-scales.md. For expression syntax (data-driven values and filter) see references/expressions.md. For layer masking (e.g. clip a weather layer to land/water or another layer's geometry) and filter, see the bottom of references/styles.md.

Custom data sources

Four source types: raster, vector (MVT), geojson, encoded (RGBA-packed grids — used internally by weather layers, rarely built by hand). See references/api-reference.md for full constructor options per type. Quick pattern:

controller.addSource('earthquakes', {
  type: 'geojson',
  data: 'https://data.api.xweather.com/earthquakes/search?query=mag:1&limit=200&format=geojson&client_id=ID&client_secret=SECRET'
});
controller.getSource('earthquakes').setUrl('...');       // swap remote data
controller.getSource('earthquakes').setData({ ... });    // set static data directly

Animating over time

controller.timeline controls playback across every animated layer at once. Full API (setting ranges by Date/offset/relative string, speed, play/pause/goTo) is in references/timeline.md. Quick start:

controller.on('load', () => {
  controller.timeline.setStartDateUsingRelativeTime('-3 hours');
  controller.timeline.duration = 1.5;   // seconds per loop
  controller.timeline.play();
});

To show one specific time, set the range before seeking to it. goToDate(date) moves within startDateendDate — it never widens the window, and a date outside the range simply doesn't display, with no error:

const target = new Date('2026-08-09T18:00:00Z');

controller.timeline.startDate = new Date(target.getTime() - 3 * 3600 * 1000);
controller.timeline.endDate   = new Date(target.getTime() + 3 * 3600 * 1000);
controller.timeline.goToDate(target);

This is the most common reason a "jump to this timestamp" feature silently does nothing. If the target can be arbitrary, test it against the current range and widen when it falls outside — worked example in references/timeline.md. The window also has to sit inside the layer's own dataRange.

Legends & data inspection

controller.addLegendControl('#legend-container');   // auto-syncs with active weather layers
controller.removeLegendControl();

controller.addDataInspectorControl({ event: 'click' });  // click/hover to inspect raw values
controller.removeDataInspectorControl();

If you override a layer's paint colors, override its legend too. Auto-detection only works for unmodified color scales, so a custom paint with a default legend produces a legend that lies about the map. Use legend: { points: {...} } for categorical data and legend: { bar: {...} } for a continuous gradient — and for a gradient, pass the same colorscale stops to both so they can't drift apart:

controller.addWeatherLayer('temperatures', {
  paint:  { sample: { colorscale: myColorscale } },
  legend: { bar: { colorscale: myColorscale, measurement: { type: 'temperature', units: 'C' } } }
});

Full field reference plus complete categorical and gradient examples: references/legends.md.

Querying data at a point

const results = controller.query({ lat: 40, lon: -74.5 });   // sync
const results = await controller.queryPromise({ lat: 40, lon: -74.5 });
// -> { [layerId]: sampled value(s) / feature(s) at that coordinate }

Checklist for common tasks

  • "Build me a map / show me an example" → one self-contained HTML file, vanilla JS, CDN tags, aerisweather.mapsgl.*. No bundler or framework unless explicitly requested. See "How to write MapsGL code examples" above.
  • "Add a weather layer"controller.addWeatherLayer(code) inside on('load', ...); look the code up in references/layers.md, or fetch https://www.xweather.com/docs/api/mapsgl/layers if it isn't listed there.
  • "Remove/hide a layer"removeWeatherLayer (frees resources) vs setWeatherLayerVisibility(code, false) (cheap toggle, keeps resources loaded).
  • "Change the colors/thresholds of a layer" → override paint.sample.colorscale (see references/color-scales.md); remember stop values must be in the data's native metric units.
  • "Toggle/update a weather layer's opacity or paint from a UI control (slider, checkbox, etc.)" → don't call controller.setPaintProperty(code, ...) with the weather layer code — get the real layer with controller.getWeatherLayer(code) (or the value returned by addWeatherLayer) and call .setPaintProperty(...) on it directly; handle the array case for composite codes. This is the single most common silent-failure bug with built-in weather layers — see references/weather-layers.md.
  • "Only show values above/below X"paint.sample.drawRange for continuous data, or a filter expression for vector/geojson layers.
  • "Style based on a feature property" → an expression: ['get', 'FIELD'] for a direct value, ['match', ['get', 'FIELD'], ...] for categorical colors/sizes, ['interpolate', ['linear'], ['get', 'FIELD'], ...] for continuous ranges. See references/expressions.md.
  • "Animate over time / add a time slider"controller.timeline, see references/timeline.md.
  • "Jump to a specific timestamp" / goToDate does nothing → the date is outside startDateendDate. goToDate seeks within the range and never widens it, so set the range first, then seek. Silent failure, no error thrown.
  • "Show a legend"addLegendControl; override legend.points (categorical) or legend.bar (gradient) if paint was customized — see references/legends.md.
  • "Add my own data (not a built-in weather layer)"addSource + addLayer with an explicit type/paint; see references/api-reference.md.
  • "What layers/options are available?"references/layers.md for the full listing by category; https://www.xweather.com/docs/api/mapsgl/layers if that snapshot might be stale; or controller.weatherProvider.getLayerMetadata() at runtime for account-specific availability. Never invent a code from memory — one of these three always has the answer.
  • "How many accesses / how much does this cost?" → sessions, not tiles or layers: count the 5-minute clock buckets the viewing touches, × 150 accesses. Show the arithmetic and mention that layers and interaction are free inside a session. See references/sessions.md.
  • aerisweather is not defined / Cannot read properties of undefined → the <script src> is pointing at an npm mirror (unpkg.com, cdn.jsdelivr.net) instead of https://cdn.aerisapi.com/sdk/js/mapsgl/<version>/aerisweather.mapsgl.js. The mirror returns 200 and loads a build that defines globalThis.mapsgl rather than aerisweather.mapsgl, so there's no network error to spot — only the runtime failure. Fix the host, don't rename the global.
  • "Handle load errors / show an error state" → there is no controller.on('error', ...) — that event doesn't exist on MapController and will never fire. See the events note in references/api-reference.md.

Attribution is required

Xweather requires attribution wherever its data or imagery is displayed. This applies to all products — Weather API, Raster Maps, and MapsGL alike. Build it into anything you produce, and say so when handing over code or URLs that will end up in front of users.

The minimum is a link to https://www.xweather.com/ reading "Powered by Vaisala Xweather":

<a href="https://www.xweather.com/" target="_blank" title="Powered by Vaisala Xweather">Powered by Vaisala Xweather</a>

The logo may be substituted for the "Xweather" text. Light and dark variants exist in SVG and PNG:

<a href="https://www.xweather.com/" target="_blank" title="Powered by Vaisala Xweather">
  <img src="https://www.xweather.com/assets/logos/vaisala-xweather-logo-dark.svg" alt="Vaisala Xweather" height="40" />
</a>

Swap -dark for -light over a dark background, or .svg for .png. Using the logo brings rules: keep it unmodified, leave at least a 10px buffer of space around it, and only adjust lightness or opacity in greyscale. Don't rotate it, don't recolour it (monotone black or white excepted), and don't use the symbol without the Xweather name.

Full guide: https://www.xweather.com/docs/weather-api/resources/attribution

Reference files

  • references/api-reference.md — full Account, MapController, and DataSource API (all methods, properties, events, per-provider setup)
  • references/layers.md — every weather layer by category: code, description, render type, animatability, cost multiplier, coverage, data range, update interval; composite codes and cost multipliers grouped up front
  • references/weather-layers.md — how to discover layer codes, the catalog schema, and the code-vs-layer-id gotcha that silently breaks style updates
  • references/styles.md — paint property spec for every render type, plus filters and masks
  • references/color-scales.md — color scale config format and built-in named palettes
  • references/expressions.md — style/filter expression operator reference
  • references/legends.mdpoints (categorical) and bar (gradient) legend config reference
  • references/timeline.md — animation/timeline API
  • references/sessions.md — how MapsGL usage is measured: the session model, clock-aligned billing, worked examples, the MapsGL vs. Raster Maps comparison, and how to reduce consumption