← Vaisala Xweather API & MapsCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Vaisala Xweather API & Maps
Snapshot Sep 30, 2026 · 23:15 UTC · version 0.14.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"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.",
"included_files": [
{
"relative_path": "references/api-reference.md",
"size_in_bytes": 10439
},
{
"relative_path": "references/color-scales.md",
"size_in_bytes": 3950
},
{
"relative_path": "references/expressions.md",
"size_in_bytes": 3236
},
{
"relative_path": "references/layers.md",
"size_in_bytes": 77604
},
{
"relative_path": "references/legends.md",
"size_in_bytes": 4638
},
{
"relative_path": "references/sessions.md",
"size_in_bytes": 8331
},
{
"relative_path": "references/styles.md",
"size_in_bytes": 10694
},
{
"relative_path": "references/timeline.md",
"size_in_bytes": 5393
},
{
"relative_path": "references/weather-layers.md",
"size_in_bytes": 8053
}
],
"name": "mapsgl",
"skill_md_contents": "---\nname: mapsgl\ndescription: 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.\nlicense: MIT\n---\n\n# MapsGL JavaScript SDK\n\nThe MapsGL JavaScript SDK (`@xweather/mapsgl`) renders weather and custom map data client-side in\nWebGL, layered on top of Mapbox GL, MapLibre GL, Google Maps, or Leaflet. It requires an active\nXweather account with Weather API + Maps access (client id + secret).\n\n**This skill is the web-based JavaScript SDK.** For a native Swift app on iOS, iPadOS, Mac Catalyst, or visionOS, use\nthe `mapsgl-apple` skill instead — a separate SDK with a Swift API (`MapboxMapController`,\n`WeatherService.LayerCode`), its own install channels, and a smaller layer set. The concepts below\ntransfer; none of the code does. MapsGL ships for Android as well.\n\n**Always qualify the name as the *MapsGL* JavaScript SDK.** Xweather ships an unrelated product simply\ncalled the JavaScript SDK (<https://www.xweather.com/docs/javascript-sdk>), so the bare term is\nambiguous. Xweather's own naming for the family is MapsGL, MapsGL iOS, and MapsGL Android; \"MapsGL\nJavaScript SDK\" is the unambiguous way to name this one.\n\n## How to write MapsGL code examples\n\n**Default to a single self-contained HTML file using vanilla JavaScript and the CDN build.** One\nfile the user can save and open in a browser — CDN `<script>`/`<link>` tags, a `<div>` for the map,\nand a plain `<script>` block. No build step, no bundler, no package installs, no framework.\n\nIn that form, MapsGL lives on the global `aerisweather.mapsgl` namespace:\n\n```javascript\nconst account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');\nconst controller = new aerisweather.mapsgl.MapboxMapController(map, { account });\n```\n\n**Look up the current released version before writing any example.** Don't reuse a version from\nmemory or from an earlier turn — MapsGL ships often, and a stale pin is the most common thing to go\nwrong in otherwise-correct example code:\n\n```bash\ncurl -s https://www.xweather.com/docs/api/releases/versions \\\n | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"products\"][\"mapsgl\"][\"version\"])'\n```\n\nThat endpoint is the release source of truth for every Xweather product, keyed by product id —\n`mapsgl` here, alongside `weather-api`, `maps`, `maps-ui-sdk`, `mapsgl-apple-sdk`,\n`mapsgl-android-sdk`, `mcp-server`, `phrases-api`. It's a small public JSON document, no auth needed.\n\n**MapsGL's own script and stylesheet always come from `cdn.aerisapi.com`**, with that version\nsubstituted:\n\n```html\n<link href=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css\" rel=\"stylesheet\" />\n<script defer src=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js\"></script>\n```\n\n`1.9.4` above is the version at the time this file was last regenerated — use it only as a fallback\nif the endpoint can't be reached, and say so when you do.\n\nNote that **npm may be ahead of the released version.** `@xweather/mapsgl` on npm has carried a\nhigher version than the releases endpoint reports, and `cdn.aerisapi.com` serves those newer paths\ntoo — so a version that resolves is not evidence it's the current release. Trust the releases\nendpoint, not npm and not a 200 from the CDN.\n\nNever substitute `unpkg.com`, `cdn.jsdelivr.net`, or another npm mirror for those two tags — the npm\nbuild exposes a different global and the page breaks at runtime even though the files load. See\n\"MapsGL's own assets come from `cdn.aerisapi.com`\" under Setup for why.\n\n**Every generated example must include the Xweather attribution** — a link to\n`https://www.xweather.com/` reading \"Powered by Vaisala Xweather\", or the logo. It's a requirement of\nusing the product, not a nicety, so build it into the markup rather than mentioning it afterwards. The\ncomplete example below shows it positioned over the map. See \"Attribution is required\" for the rules.\n\nOnly produce **npm / ES-module / bundler** code when the user explicitly asks for it, or when they're\nplainly already working in such a project — an existing `package.json`, a `src/` tree with imports, or\nthey name a bundler. Same for **React or any other framework**: only on explicit request. Don't offer\na framework version alongside the vanilla one \"in case\", and don't reach for a framework because the\ntask looks app-shaped.\n\nWhen the user is in an npm project, the translation is mechanical: swap the CDN tags for\n`import * as mapsgl from '@xweather/mapsgl'` plus the map library's own import, and replace\n`aerisweather.mapsgl.` with `mapsgl.`. Everything else — the controller API, layer codes, paint\nobjects, expressions — is identical, so the rest of this skill applies unchanged.\n\n## Core concepts\n\n| Concept | What it is |\n|---|---|\n| `Account` | Wraps client id/secret credentials used for all data requests |\n| `MapController` | Adapter between the underlying map instance (Mapbox/MapLibre/Google/Leaflet) and MapsGL — the object almost everything below is called on |\n| `DataSource` | Where layer data comes from: `raster`, `vector`, `geojson`, or `encoded` (RGBA-packed weather grids) |\n| `WebGLLayer` | A visual rendering of a source: `raster`, `fill`, `line`, `circle`, `sample`, `grid`, `contour`, `particle`, `heatmap`, `symbol` |\n| `paint` | Per-layer style config (colors, radii, colorscales, etc.), keyed by render type — see `references/styles.md` |\n| Expressions | `['operator', ...args]` arrays for data-driven paint values and `filter`s — see `references/expressions.md` |\n| `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` |\n| Legend (`LegendControl`) | An on-map UI element showing what a layer's colors/symbols mean; categorical (`points`) or gradient (`bar`) — see `references/legends.md` |\n| `DataInspectorControl` | An on-map UI element that shows raw layer values at the clicked/hovered point |\n| `timeline` (`Timeline`) | Drives time-based animation (play/pause/scrub) across every animated layer on a controller at once — see `references/timeline.md` |\n\nBuilt-in **weather layers** are pre-wired combinations of an encoded source + one or more styled\nlayers, addressed by a single string code (e.g. `'temperatures'`, `'radar'`, `'alerts'`). Prefer\nthese over hand-building sources/layers unless visualizing custom or non-weather data.\n\n## Usage is measured in sessions\n\nMapsGL bills in **sessions**, not tiles, layers, or requests. A session is a continuous interaction\nwith a MapsGL map for **up to 5 minutes**, starting when any weather layer is added. On a Weather API\nand Maps subscription, **1 session = 150 accesses** (a 150× multiplier).\n\nThree rules produce every answer:\n\n1. **Sessions align to the wall clock** — boundaries at `:00`, `:05`, `:10`, `:15`. Not a rolling\n window from first interaction. What matters is how many 5-minute buckets the viewing touches, not\n how long it lasted.\n2. **At least one session per data request.** No proration — 150 accesses is the floor.\n3. **Inside a session, everything is free**: panning, zooming, animating, refreshing, and toggling\n layers. **Layer count does not affect cost.**\n\n**When asked how usage is measured, show the arithmetic** — buckets → sessions → accesses — rather\nthan just a number. The documented example:\n\n> A user views a radar layer from **8:03 to 8:07** — 4 minutes, but it straddles the `:05` boundary,\n> so it touches two buckets (8:00–8:05 and 8:05–8:10) = **2 sessions = 300 accesses**.\n>\n> The same 4 minutes from **8:05 to 8:09** touches one bucket = **1 session = 150 accesses**. Same\n> duration, half the cost, purely from clock alignment.\n\nTwo consequences worth volunteering unprompted, because they invert the intuition people bring from\ntile-based pricing:\n\n> **Adding layers is free.** Five layers viewed for four minutes costs 1 session — the same as one\n> layer. There is no cost reason to limit how many layers a user enables. (On Raster Maps the same\n> five layers would cost 5×.)\n>\n> **Short visits get almost no discount.** A 30-second view averages ~1.1 sessions and a 5-minute\n> view ~2.0, so a visit ten times shorter costs 55% as much, not 10%. Drive-by page loads are the\n> expensive traffic shape.\n\nFor capacity planning, a view of `d` minutes starting at a random time averages\n`floor(d/5) + 1 + (d mod 5)/5` sessions — **not** `d/5`. Assuming one session per short view\nunderestimates by 10–20%. Table of common durations in `references/sessions.md`.\n\nThe only real lever is **when layers are on the map**: don't call `addWeatherLayer` until the user\nasks for weather, and `removeWeatherLayer` when the map goes out of view or idle. Optimising layer\ncount, animation, or interaction is pointless — those are free.\n\nFull model, more worked examples (long-running displays, high-traffic short visits), the MapsGL vs.\nRaster Maps billing comparison, and reduction tactics: `references/sessions.md`.\n\n## Setup\n\n### 1. Get API credentials\n\nMapsGL needs **two separate sets of credentials**, and both are required — the map won't render\nwithout either one:\n\n1. **Xweather account keys** (`CLIENT_ID` / `CLIENT_SECRET`) — generated from the account portal at\n **https://data.portal.xweather.com/account/keys**. These authenticate MapsGL's own data\n requests and are what get passed to `new aerisweather.mapsgl.Account(id, secret)`.\n2. **The underlying map provider's own key/token** — independent of Xweather:\n - Mapbox GL → `mapboxgl.accessToken` (Mapbox account access token)\n - MapLibre GL → no key needed, but the `style` URL usually comes from a tile provider (e.g.\n MapTiler) that does require its own key\n - Google Maps → a Google Maps JavaScript API key, plus a **Map ID** with vector-map\n support enabled (required — MapsGL renders as a WebGL overlay on the vector basemap)\n - Leaflet → no key needed for the base `L.tileLayer`, though the tile provider used may require one\n\nIf a user reports \"nothing renders\" or an auth error, check both credential sets before digging\ninto MapsGL-specific config.\n\n### 2. Install\n\nEvery install needs `@xweather/mapsgl` **plus** the map library being wrapped — MapsGL does not\nbundle it, whether installing via CDN or npm.\n\n**CDN** — include the map provider's own `<link>`/`<script>` tags *in addition to* MapsGL's, not\ninstead of them. MapsGL's CDN build exposes everything under `window.aerisweather.mapsgl`.\n\n### MapsGL's own assets come from `cdn.aerisapi.com` — nowhere else\n\nCopy these two lines verbatim, changing only the version number:\n\n```html\n<link href=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css\" rel=\"stylesheet\" />\n<script defer src=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js\"></script>\n```\n\n```\nhttps://cdn.aerisapi.com/sdk/js/mapsgl/{version}/aerisweather.mapsgl.js\nhttps://cdn.aerisapi.com/sdk/js/mapsgl/{version}/aerisweather.mapsgl.css\n```\n\n**Never serve MapsGL from `unpkg.com`, `cdn.jsdelivr.net`, `esm.sh`, or any other npm mirror**, and\nnever invent a filename like `mapsgl.js` or `@xweather/mapsgl/dist/...` for a `<script>` tag. This is\nworth stating flatly because the wrong URL *appears to work*:\n\n- `https://unpkg.com/@xweather/mapsgl/dist/mapsgl.js` returns **HTTP 200**. So does the `.css` beside\n it. There is no network error to notice.\n- But that is the **npm** build, whose UMD wrapper assigns `globalThis.mapsgl` — **not**\n `aerisweather.mapsgl`. The page then dies at runtime with\n `ReferenceError: aerisweather is not defined`, which looks like a MapsGL bug rather than a bad URL.\n\nIf a user reports `aerisweather is not defined`, check the `<script src>` host first — it is almost\nalways this.\n\nThe unpkg/jsdelivr paths are only meaningful in a bundled project that imports `@xweather/mapsgl` as a\nmodule, and even there the import comes from npm, not a CDN URL.\n\n### The map library's own CDN tags\n\nThese are separate from MapsGL and *do* legitimately come from unpkg for some providers:\n\n| Provider | CDN tags for the **map library only** |\n|---|---|\n| Mapbox GL | `https://api.mapbox.com/mapbox-gl-js/<version>/mapbox-gl.{css,js}` |\n| MapLibre GL | e.g. `https://unpkg.com/maplibre-gl@<version>/dist/maplibre-gl.{css,js}` |\n| Leaflet | e.g. `https://unpkg.com/leaflet@<version>/dist/leaflet.{css,js}` |\n| Google Maps | `<script src=\"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY\"></script>` (no separate CSS) |\n\nDon't let the unpkg entries in this table bleed into MapsGL's URLs — the rows above cover Mapbox,\nMapLibre, Leaflet and Google *only*. MapsGL always comes from `cdn.aerisapi.com`.\n\nA complete pair of includes, MapsGL plus its map library:\n\n```html\n<!-- 1. The map library — Mapbox GL shown; swap for MapLibre/Leaflet/Google per the table above -->\n<link href=\"https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css\" rel=\"stylesheet\" />\n<script defer src=\"https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js\"></script>\n\n<!-- 2. MapsGL itself — always cdn.aerisapi.com -->\n<link href=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css\" rel=\"stylesheet\" />\n<script defer src=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js\"></script>\n```\n\nPin an explicit version for every `<script>`/`<link>` tag (MapsGL's and the map library's) rather\nthan `latest`, for anything beyond a quick prototype. `cdn.aerisapi.com/sdk/js/mapsgl/latest/…` does\nresolve if you need it.\n\nFor MapsGL's version, use the releases endpoint — **not** npm, which can be ahead of the current\nrelease:\n\n```bash\ncurl -s https://www.xweather.com/docs/api/releases/versions \\\n | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"products\"][\"mapsgl\"][\"version\"])'\n```\n\n**npm** — *only when the user is already in a bundled project or asks for it.* Install\n`@xweather/mapsgl` plus whichever provider package applies:\n```bash\nnpm install --save @xweather/mapsgl mapbox-gl # Mapbox GL\nnpm install --save @xweather/mapsgl maplibre-gl # MapLibre GL\nnpm install --save @xweather/mapsgl leaflet # Leaflet\nnpm install --save @xweather/mapsgl # Google Maps — loaded via <script> from Google, no npm package needed\n```\n```javascript\nimport * as mapsgl from '@xweather/mapsgl';\nimport '@xweather/mapsgl/dist/mapsgl.css';\n```\n\n### 3. Container markup\n\nThe map container needs explicit dimensions — a blank/invisible map is almost always a missing\nCSS rule, not a JS error:\n```html\n<div id=\"map\"></div>\n<style>\n body, html { margin: 0; padding: 0; }\n #map { width: 100%; height: 100vh; }\n</style>\n```\n\n### 4. Initialize a controller\n\nEvery provider follows the same pattern: create the native map, wrap it in the matching\n`*MapController`, wait for `load`, then add layers. Only the map-creation step differs.\n\n```javascript\nmapboxgl.accessToken = 'MAPBOX_TOKEN';\nconst map = new mapboxgl.Map({\n container: document.getElementById('map'),\n style: 'mapbox://styles/mapbox/light-v11',\n center: [-74.5, 40],\n zoom: 3\n});\n\nconst account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');\nconst controller = new aerisweather.mapsgl.MapboxMapController(map, { account });\n\ncontroller.on('load', () => {\n controller.addWeatherLayer('temperatures');\n});\n```\n\n| Provider | Controller class | Map constructor |\n|---|---|---|\n| Mapbox GL | `MapboxMapController` | `new mapboxgl.Map({...})` |\n| MapLibre GL | `MaplibreMapController` | `new maplibregl.Map({...})` |\n| Google Maps | `GoogleMapController` | `new google.maps.Map(el, { mapId: '...', ... })` |\n| Leaflet | `LeafletMapController` | `L.map('map').setView([lat, lon], zoom)` |\n\nAll four take `(map, { account, units?, animation? })`. Google's variant also accepts\n`interleaved?: boolean`. **Always gate MapsGL calls behind `controller.on('load', ...)`** —\ncalling layer/source methods before load throws.\n\nThe four controller constructors and the complete controller API (properties, events, all methods)\nare in `references/api-reference.md`.\n\n## Complete example\n\n**This is the shape to produce by default** — one file, saveable and openable in a browser. Adapt it\nrather than starting from scratch: swap the map provider's CDN tags and constructor, change the\n`addWeatherLayer` codes, and adjust `center`/`zoom`.\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>MapsGL + Mapbox GL</title>\n\n <link href=\"https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css\" rel=\"stylesheet\" />\n <script defer src=\"https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js\"></script>\n\n <link href=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.css\" rel=\"stylesheet\" />\n <script defer src=\"https://cdn.aerisapi.com/sdk/js/mapsgl/1.9.4/aerisweather.mapsgl.js\"></script>\n\n <style>\n body, html { margin: 0; padding: 0; }\n #map { height: 100vh; width: 100%; }\n /* Attribution is required wherever Xweather data is displayed. */\n #attribution {\n position: absolute; bottom: 8px; right: 8px; z-index: 1;\n background: rgba(255, 255, 255, 0.85); border-radius: 3px;\n padding: 3px 6px; font: 12px system-ui, sans-serif;\n }\n #attribution a { color: #333; text-decoration: none; }\n </style>\n</head>\n<body>\n <div id=\"map\"></div>\n <div id=\"attribution\">\n <a href=\"https://www.xweather.com/\" target=\"_blank\" title=\"Powered by Vaisala Xweather\">Powered by Vaisala Xweather</a>\n </div>\n\n <script>\n window.addEventListener('load', () => {\n mapboxgl.accessToken = 'MAPBOX_TOKEN';\n const map = new mapboxgl.Map({\n container: 'map',\n style: 'mapbox://styles/mapbox/light-v11',\n center: [-85.5, 40],\n zoom: 3\n });\n\n const account = new aerisweather.mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');\n const controller = new aerisweather.mapsgl.MapboxMapController(map, { account });\n\n controller.on('load', () => {\n // built-in weather layer, unstyled\n controller.addWeatherLayer('radar');\n\n // built-in weather layer with a custom style override\n controller.addWeatherLayer('alerts-outline', {\n paint: {\n opacity: 0.5\n }\n });\n });\n });\n </script>\n</body>\n</html>\n```\n\nSource: https://www.xweather.com/docs/mapsgl/examples/mapbox\n\nTwo details that matter in the single-file form specifically:\n\n- The CDN tags use `defer`, so wrap the setup in `window.addEventListener('load', ...)`. Without it,\n `mapboxgl` and `aerisweather` aren't defined yet and the script throws.\n- `#map` needs an explicit height. A silently blank map is nearly always this, not a JS error.\n\n### npm / ES modules — only when asked\n\nSame structure: swap the CDN `<script>` tags for package imports, and `aerisweather.mapsgl.` for the\nimported `mapsgl.` namespace. The `window.addEventListener('load', ...)` wrapper is unnecessary since\nbundlers execute the module after the DOM is parsed (keep a `defer`/module script tag, or bundle into\nthe page's entry point). Nothing else changes.\n\n```javascript\n// main.js — loaded via <script type=\"module\" src=\"./main.js\"></script>\nimport mapboxgl from 'mapbox-gl';\nimport * as mapsgl from '@xweather/mapsgl';\nimport 'mapbox-gl/dist/mapbox-gl.css';\nimport '@xweather/mapsgl/dist/mapsgl.css';\n\nmapboxgl.accessToken = 'MAPBOX_TOKEN';\nconst map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/light-v11', center: [-85.5, 40], zoom: 3 });\n\nconst account = new mapsgl.Account('CLIENT_ID', 'CLIENT_SECRET');\nconst controller = new mapsgl.MapboxMapController(map, { account });\n\ncontroller.on('load', () => {\n controller.addWeatherLayer('radar');\n});\n```\n\nThe host page is then just the container plus `<script type=\"module\" src=\"./main.js\"></script>` — the\n`#map` height rule still applies.\n\n## Adding, removing, and listing weather layers\n\n```javascript\ncontroller.addWeatherLayer('radar');\ncontroller.addWeatherLayer('wind-particles');\n\n// with overrides (data quality, time clamping, paint, legend, mask, filter, ...)\ncontroller.addWeatherLayer('temperatures', {\n data: { quality: aerisweather.mapsgl.DataQuality.low },\n});\n\ncontroller.hasWeatherLayer('radar'); // boolean\ncontroller.getWeatherLayer('temperatures'); // WebGLLayer | WebGLLayer[] | undefined\ncontroller.setWeatherLayerVisibility('radar', false); // hide without disposing\ncontroller.removeWeatherLayer('radar'); // fully removes + frees resources\n\ncontroller.weatherLayerIds; // currently-active weather layer codes\n```\n\n**A weather layer's code (e.g. `'temperatures'`) is not the same string as its actual layer id.**\nIf you'll need to update a weather layer's style/opacity/visibility-via-`.show()`/`.hide()` later,\ncapture what `addWeatherLayer` returns (or call `getWeatherLayer(code)` later) and operate on that\n`WebGLLayer` instance directly — don't pass the code to `controller.setPaintProperty()`,\n`getLayer()`, or `moveLayer()`, which expect the real layer id and will silently no-op on a code\nthey don't recognize. Composite codes return an **array** of layers to iterate over. Full\nexplanation and verified example in `references/weather-layers.md`.\n\n**Never guess a layer code — look it up.** `references/layers.md` lists every layer by category\nwith their render type, animatability, cost multiplier, coverage, data range, and update interval. It\nis generated from the public catalog and refreshed weekly, so grep it first; no network call needed.\n\nIf a code isn't there, or the snapshot looks stale, fetch the live catalog:\n\n```\nhttps://www.xweather.com/docs/api/mapsgl/layers\n```\n\nA plain public JSON endpoint (no auth) returning `{ layers: [{ id, title, description, type,\ncategories, animatable, dataRange, dataCoverage, updateInterval, multiplier }, ...] }`. It overrides\nthe snapshot. For what the **authenticated account** can actually render — neither file knows about\nentitlements — ask at runtime:\n\n```javascript\ncontroller.weatherProvider.getLayerMetadata().then((data) => console.log(data));\n```\n\n### No separate forecast layers — one layer spans past and future\n\nRaster Maps splits time across two layers: `temperatures` is observed, `ftemperatures` is forecast.\n**MapsGL does not.** A single `temperatures` layer covers `-7 to +15 days`, and the timeline decides\nwhich interval renders.\n\nSo don't look for `ftemperatures`, `fradar`, or `fwind-speeds` here — they don't exist. Twelve Raster\nMaps pairs collapse into one MapsGL layer each:\n\n`dew-points` · `feels-like` · `heat-index` · `humidity` · `radar` · `satellite` · `snow-depth` ·\n`temperatures` · `visibility` · `wind-chill` · `wind-gusts` · `wind-speeds`\n\nReach a forecast interval by moving the timeline, not by adding a different layer —\n`controller.timeline.containsFuture` reports whether the current range includes one. See\n`references/timeline.md`.\n\n**Range is per-layer, so check it rather than assuming.** `layers.md` lists it for every code. Two\ncases don't follow the pattern:\n\n- **`satellite` is past-only** (`-7 days`). Raster Maps has `fsatellite` reaching +15 days; MapsGL has\n no forecast satellite at all. That's a missing capability, not a renamed one — don't promise a\n satellite forecast on MapsGL.\n- **Road weather keeps an `f` split, meaning something different.** `road-weather-*` is a +2 hour\n nowcast refreshed every 15 minutes; `froad-weather-*` is a +24 hour forecast refreshed every 6\n hours. Both are forecasts, so there the prefix marks *range*, not past-versus-future.\n\nSome codes are **composite** (expand to multiple sub-layers, e.g. `boundaries`, `roads`,\n`stormcells`) — `addWeatherLayer` returns an array for these, and `overrides.childLayers` can\ntarget one sub-layer by id. All 14 composite codes are listed together at the top of\n`references/layers.md`; they're the ones with render type `none`.\n\nA layer's **render type also tells you how to style it** — a `sample` layer takes\n`paint.sample.colorscale`, a `line` layer takes `paint.stroke`. Reading the type out of\n`layers.md` before writing a `paint` override saves a round of guessing.\n\n## Styling layers\n\nPass a `paint` object namespaced by render type. Full property tables for every render type\n(`raster`, `fill`, `stroke`/`line`, `circle`, `sample`, `grid`, `contour`, `particle`, `heatmap`,\n`icon`, `symbol`, `text`) are in `references/styles.md`.\n\n**Always use MapsGL expressions — `['operator', ...args]` arrays — for any data-driven paint\nvalue** (color/size/opacity derived from a feature property), not evaluator functions or the\n`{ property }` shorthand. See `references/expressions.md` for the full operator reference.\n\n```javascript\ncontroller.addWeatherLayer('temperatures', {\n paint: {\n sample: {\n colorscale: {\n stops: [-40, '#58005b', 0, '#81e8ff', 20, '#ecf93d', 40, '#6b0001'],\n interval: 5\n }\n }\n }\n});\n```\n\nUpdate a style after the layer exists — **for built-in weather layers, get the actual layer\ninstance first; the weather layer code is not a valid layer id** (see the \"code vs. id\" gotcha\nbelow):\n```javascript\nconst tempLayer = controller.getWeatherLayer('temperatures'); // or capture addWeatherLayer's return value\ntempLayer.setPaintProperty('sample.colorscale', newColorScale);\n```\nFor a layer you created yourself with `addLayer(id, ...)`, the id you chose *is* the real layer id,\nso `controller.setPaintProperty(id, prop, value)` works directly.\n\n**Custom (non-weather) layer**, styled with a static or data-driven fill:\n```javascript\ncontroller.addSource('alerts', {\n type: 'vector',\n url: 'https://maps{s}.aerisapi.com/CLIENT_ID_CLIENT_SECRET/alerts/{z}/{x}/{y}/0.pbf'\n});\ncontroller.addLayer('alerts-fill', {\n type: 'fill',\n source: 'alerts',\n paint: { fill: { color: ['get', 'COLOR'], opacity: 0.6 } }\n});\n// ...\ncontroller.removeLayer('alerts-fill');\ncontroller.removeSource('alerts'); // only after no layers reference it\n```\n\nFor color scales (built-in named palettes + custom stops) see `references/color-scales.md`.\nFor expression syntax (data-driven values and `filter`) see `references/expressions.md`.\nFor layer masking (e.g. clip a weather layer to land/water or another layer's geometry) and\n`filter`, see the bottom of `references/styles.md`.\n\n## Custom data sources\n\nFour source types: `raster`, `vector` (MVT), `geojson`, `encoded` (RGBA-packed grids — used\ninternally by weather layers, rarely built by hand). See `references/api-reference.md` for full\nconstructor options per type. Quick pattern:\n\n```javascript\ncontroller.addSource('earthquakes', {\n type: 'geojson',\n data: 'https://data.api.xweather.com/earthquakes/search?query=mag:1&limit=200&format=geojson&client_id=ID&client_secret=SECRET'\n});\ncontroller.getSource('earthquakes').setUrl('...'); // swap remote data\ncontroller.getSource('earthquakes').setData({ ... }); // set static data directly\n```\n\n## Animating over time\n\n`controller.timeline` controls playback across every animated layer at once. Full API\n(setting ranges by Date/offset/relative string, speed, play/pause/goTo) is in\n`references/timeline.md`. Quick start:\n\n```javascript\ncontroller.on('load', () => {\n controller.timeline.setStartDateUsingRelativeTime('-3 hours');\n controller.timeline.duration = 1.5; // seconds per loop\n controller.timeline.play();\n});\n```\n\n**To show one specific time, set the range before seeking to it.** `goToDate(date)` moves *within*\n`startDate`…`endDate` — it never widens the window, and a date outside the range simply doesn't\ndisplay, with no error:\n\n```javascript\nconst target = new Date('2026-08-09T18:00:00Z');\n\ncontroller.timeline.startDate = new Date(target.getTime() - 3 * 3600 * 1000);\ncontroller.timeline.endDate = new Date(target.getTime() + 3 * 3600 * 1000);\ncontroller.timeline.goToDate(target);\n```\n\nThis is the most common reason a \"jump to this timestamp\" feature silently does nothing. If the target\ncan be arbitrary, test it against the current range and widen when it falls outside — worked example in\n`references/timeline.md`. The window also has to sit inside the layer's own `dataRange`.\n\n## Legends & data inspection\n\n```javascript\ncontroller.addLegendControl('#legend-container'); // auto-syncs with active weather layers\ncontroller.removeLegendControl();\n\ncontroller.addDataInspectorControl({ event: 'click' }); // click/hover to inspect raw values\ncontroller.removeDataInspectorControl();\n```\n\n**If you override a layer's `paint` colors, override its `legend` too.** Auto-detection only works for\nunmodified color scales, so a custom paint with a default legend produces a legend that lies about the\nmap. Use `legend: { points: {...} }` for categorical data and `legend: { bar: {...} }` for a\ncontinuous gradient — and for a gradient, pass the **same `colorscale` stops** to both so they can't\ndrift apart:\n\n```javascript\ncontroller.addWeatherLayer('temperatures', {\n paint: { sample: { colorscale: myColorscale } },\n legend: { bar: { colorscale: myColorscale, measurement: { type: 'temperature', units: 'C' } } }\n});\n```\n\nFull field reference plus complete categorical and gradient examples: `references/legends.md`.\n\n## Querying data at a point\n\n```javascript\nconst results = controller.query({ lat: 40, lon: -74.5 }); // sync\nconst results = await controller.queryPromise({ lat: 40, lon: -74.5 });\n// -> { [layerId]: sampled value(s) / feature(s) at that coordinate }\n```\n\n## Checklist for common tasks\n\n- **\"Build me a map / show me an example\"** → one self-contained HTML file, vanilla JS, CDN tags,\n `aerisweather.mapsgl.*`. No bundler or framework unless explicitly requested. See\n \"How to write MapsGL code examples\" above.\n- **\"Add a weather layer\"** → `controller.addWeatherLayer(code)` inside `on('load', ...)`; look the\n code up in `references/layers.md`, or fetch\n `https://www.xweather.com/docs/api/mapsgl/layers` if it isn't listed there.\n- **\"Remove/hide a layer\"** → `removeWeatherLayer` (frees resources) vs\n `setWeatherLayerVisibility(code, false)` (cheap toggle, keeps resources loaded).\n- **\"Change the colors/thresholds of a layer\"** → override `paint.sample.colorscale` (see\n `references/color-scales.md`); remember stop values must be in the data's native metric units.\n- **\"Toggle/update a weather layer's opacity or paint from a UI control (slider, checkbox, etc.)\"**\n → don't call `controller.setPaintProperty(code, ...)` with the weather layer code — get the real\n layer with `controller.getWeatherLayer(code)` (or the value returned by `addWeatherLayer`) and\n call `.setPaintProperty(...)` on it directly; handle the array case for composite codes. This is\n the single most common silent-failure bug with built-in weather layers — see\n `references/weather-layers.md`.\n- **\"Only show values above/below X\"** → `paint.sample.drawRange` for continuous data, or a\n `filter` expression for vector/geojson layers.\n- **\"Style based on a feature property\"** → an expression: `['get', 'FIELD']` for a direct value,\n `['match', ['get', 'FIELD'], ...]` for categorical colors/sizes, `['interpolate', ['linear'],\n ['get', 'FIELD'], ...]` for continuous ranges. See `references/expressions.md`.\n- **\"Animate over time / add a time slider\"** → `controller.timeline`, see `references/timeline.md`.\n- **\"Jump to a specific timestamp\" / `goToDate` does nothing** → the date is outside\n `startDate`…`endDate`. `goToDate` seeks within the range and never widens it, so set the range\n first, then seek. Silent failure, no error thrown.\n- **\"Show a legend\"** → `addLegendControl`; override `legend.points` (categorical) or `legend.bar`\n (gradient) if paint was customized — see `references/legends.md`.\n- **\"Add my own data (not a built-in weather layer)\"** → `addSource` + `addLayer` with an explicit\n `type`/`paint`; see `references/api-reference.md`.\n- **\"What layers/options are available?\"** → `references/layers.md` for the full listing by category;\n `https://www.xweather.com/docs/api/mapsgl/layers` if that snapshot might be stale; or\n `controller.weatherProvider.getLayerMetadata()` at runtime for account-specific availability. Never\n invent a code from memory — one of these three always has the answer.\n- **\"How many accesses / how much does this cost?\"** → sessions, not tiles or layers: count the\n 5-minute clock buckets the viewing touches, × 150 accesses. Show the arithmetic and mention that\n layers and interaction are free inside a session. See `references/sessions.md`.\n- **`aerisweather is not defined` / `Cannot read properties of undefined`** → the `<script src>` is\n pointing at an npm mirror (`unpkg.com`, `cdn.jsdelivr.net`) instead of\n `https://cdn.aerisapi.com/sdk/js/mapsgl/<version>/aerisweather.mapsgl.js`. The mirror returns 200 and\n loads a build that defines `globalThis.mapsgl` rather than `aerisweather.mapsgl`, so there's no\n network error to spot — only the runtime failure. Fix the host, don't rename the global.\n- **\"Handle load errors / show an error state\"** → there is no `controller.on('error', ...)` —\n that event doesn't exist on `MapController` and will never fire. See the events note in\n `references/api-reference.md`.\n\n## Attribution is required\n\nXweather requires attribution wherever its data or imagery is displayed. This applies to **all\nproducts** — Weather API, Raster Maps, and MapsGL alike. Build it into anything you produce, and say\nso when handing over code or URLs that will end up in front of users.\n\nThe minimum is a link to `https://www.xweather.com/` reading \"Powered by Vaisala Xweather\":\n\n```html\n<a href=\"https://www.xweather.com/\" target=\"_blank\" title=\"Powered by Vaisala Xweather\">Powered by Vaisala Xweather</a>\n```\n\nThe logo may be substituted for the \"Xweather\" text. Light and dark variants exist in SVG and PNG:\n\n```html\n<a href=\"https://www.xweather.com/\" target=\"_blank\" title=\"Powered by Vaisala Xweather\">\n <img src=\"https://www.xweather.com/assets/logos/vaisala-xweather-logo-dark.svg\" alt=\"Vaisala Xweather\" height=\"40\" />\n</a>\n```\n\nSwap `-dark` for `-light` over a dark background, or `.svg` for `.png`. Using the logo brings rules:\nkeep it unmodified, leave at least a **10px buffer** of space around it, and only adjust lightness or\nopacity in greyscale. Don't rotate it, don't recolour it (monotone black or white excepted), and don't\nuse the symbol without the Xweather name.\n\nFull guide: https://www.xweather.com/docs/weather-api/resources/attribution\n\n## Reference files\n\n- `references/api-reference.md` — full `Account`, `MapController`, and `DataSource` API (all methods, properties, events, per-provider setup)\n- `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\n- `references/weather-layers.md` — how to discover layer codes, the catalog schema, and the code-vs-layer-id gotcha that silently breaks style updates\n- `references/styles.md` — paint property spec for every render type, plus filters and masks\n- `references/color-scales.md` — color scale config format and built-in named palettes\n- `references/expressions.md` — style/filter expression operator reference\n- `references/legends.md` — `points` (categorical) and `bar` (gradient) legend config reference\n- `references/timeline.md` — animation/timeline API\n- `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\n"
}SHA-256 of public snapshot: 0208ad04b128fce74b6e35f697f0efba55e4bcebb361cc7488bf89ab774d5cad