← 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 Android SDK (mapsgl-android-sdk / com.xweather.mapsgl) - setting up MapboxMapController, adding or removing weather layers via LayerCode or WeatherService configs, styling with StyleValue and Expression, custom sources and layers, legends, data inspector presentations, timeline animation, layer masks, or integrating the AAR/JitPack dependency into an Android app. Use it whenever a task mentions MapsGL Android, MapboxMapController, addWeatherLayer, LayerCode, WeatherService, XweatherAccount, or weather overlays on Mapbox Maps SDK for Android. Also covers MapsGL session-based usage/cost (shared with the JS SDK) and common Android gotchas (Mercator, OpenGL ES 3.0, minSdk 28, Mapbox peer dependency). Documents the MapsGL Android 1.6.1 release - every API here is in that published build, with no unreleased or development-branch surface. When docs and SDK disagree, prefer the SDK (source / KDoc / demos) over xweather.com documentation.",
"included_files": [
{
"relative_path": "references/android-gotchas.md",
"size_in_bytes": 1650
},
{
"relative_path": "references/api-reference.md",
"size_in_bytes": 13165
},
{
"relative_path": "references/custom-layers.md",
"size_in_bytes": 1440
},
{
"relative_path": "references/data-driven.md",
"size_in_bytes": 2958
},
{
"relative_path": "references/expressions.md",
"size_in_bytes": 7001
},
{
"relative_path": "references/layers.md",
"size_in_bytes": 20905
},
{
"relative_path": "references/legends-inspector.md",
"size_in_bytes": 8644
},
{
"relative_path": "references/sessions.md",
"size_in_bytes": 5203
},
{
"relative_path": "references/setup.md",
"size_in_bytes": 13063
},
{
"relative_path": "references/sources.md",
"size_in_bytes": 1862
},
{
"relative_path": "references/styles.md",
"size_in_bytes": 1846
},
{
"relative_path": "references/timeline.md",
"size_in_bytes": 11572
},
{
"relative_path": "references/weather-layers.md",
"size_in_bytes": 1964
},
{
"relative_path": "references/weather-styling.md",
"size_in_bytes": 2064
}
],
"name": "mapsgl-android",
"skill_md_contents": "---\nname: mapsgl-android\ndescription: >-\n This skill should be used when working with the Xweather MapsGL Android SDK\n (mapsgl-android-sdk / com.xweather.mapsgl) - setting up MapboxMapController,\n adding or removing weather layers via LayerCode or WeatherService configs,\n styling with StyleValue and Expression, custom sources and layers, legends,\n data inspector presentations, timeline animation, layer masks, or integrating\n the AAR/JitPack dependency into an Android app. Use it whenever a task mentions\n MapsGL Android, MapboxMapController, addWeatherLayer, LayerCode,\n WeatherService, XweatherAccount, or weather overlays on Mapbox Maps SDK for\n Android. Also covers MapsGL session-based usage/cost (shared with the JS SDK)\n and common Android gotchas (Mercator, OpenGL ES 3.0, minSdk 28, Mapbox peer\n dependency). Documents the MapsGL Android 1.6.1 release - every API here is in\n that published build, with no unreleased or development-branch surface. When docs\n and SDK disagree, prefer the SDK (source / KDoc / demos) over xweather.com\n documentation.\nlicense: MIT\n---\n\n# MapsGL Android\n\nMapsGL Android renders weather and custom map data on top of the **Mapbox Maps\nSDK for Android** (encoded grids via OpenGL ES custom layers; many vector\nweather layers via Mapbox style layers). Requires an Xweather account (Weather\nAPI + Maps) **and** Mapbox access / downloads tokens.\n\n## Source of truth (read this first)\n\nWhen answering or writing code, resolve conflicts in this order:\n\n1. **The MapsGL Android SDK** - public Kotlin APIs in `mapsglmaps` / published AAR,\n in-repo demos under `app/`, and generated KDoc\n2. **This skill** (kept to match the SDK)\n3. **https://www.xweather.com/docs/mapsgl-android-sdk/** - useful for tutorials and\n recipes, but often lagging (deprecated ctors, wrong `removeWeatherLayer` args,\n missing Mapbox peer dep, \"coming soon\" for shipped features, invented overload\n shapes)\n\n**Prefer the SDK to the docs.** If a docs snippet disagrees with a real method\nsignature, package, or demo in the SDK, follow the SDK and say so. Never invent an\nAPI - if it isn't in the SDK source, KDoc or a demo, it doesn't exist.\n\nDocs hub (optional context only):\nhttps://www.xweather.com/docs/mapsgl-android-sdk/\n\n**API scope:** public MapsGL Android APIs only - no internals.\n\n**This skill documents the 1.6.1 release.** Every API described here exists in the\npublished 1.6.1 artifact - there is no unreleased or development-branch surface to\nfilter out, and nothing is marked *(unreleased)*.\n\nThat also bounds what the skill can offer. Features added after 1.6.1 - the\n`[\"map-time\"]` filter expression, MapsGL's own GLES vector rendering pipeline, and\nthe `*-text` data-query layers - are deliberately absent. If a task needs one of\nthose, say it is not available in 1.6.1 rather than writing code against it.\n\n**Never hardcode a version number.** Resolve the current release when you need\none:\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-android-sdk\"][\"version\"])'\n```\n\nThat endpoint is the release source of truth for every Xweather product, keyed by\nproduct id - `mapsgl-android-sdk` here, alongside `mapsgl`, `mapsgl-apple-sdk`,\n`weather-api`, `maps`, and others. It's a small public JSON document, no auth\nneeded.\n\nA version is only needed for a deliberate Gradle pin or an API-reference URL.\nWhere this skill's `references/` note behaviour \"on 1.6.x\", that records what the\nguidance was checked against - verify against the release you're actually on\nbefore relying on it.\n\n## How to write examples\n\n**Default:** one Kotlin `Activity` / Fragment with ViewBinding + Mapbox `MapView`.\nNo Compose unless asked. Match the surrounding project when it disagrees with that\ndefault - if it is a Fragment codebase, or already uses Compose interop, follow it.\n\n## API reference\n\n`references/api-reference.md` carries real signatures for every public type. The\npublished KDoc is the per-version authority:\n\n```\nhttps://cdn.aerisapi.com/sdk/android/mapsgl/docs/v{version}/mapsglmaps/{package}/{-class-name}/index.html\n```\n\nThere is **no `latest` alias** - `/docs/latest/` returns 404. Resolve the version\nfirst (above). Class paths dash-case the name: `MapController` becomes\n`-map-controller`.\n\n## How much of this has been proven\n\nNot all of it to the same standard, and the difference matters when something here\ndisagrees with what you observe.\n\n**Built and run.** The Setup section and the Complete example below were applied to\na blank Android project, compiled, and run against MapsGL Android 1.6.1. That\ncovers `XweatherAccount`, `MapboxMapController`, `setCenter`/`setZoom`,\n`onLoadStart`/`onLoadComplete`, `subscribeMapLoaded`, the Mercator `setProjection`,\n`LegendControl`, `addDataInspectorControl`, `animationOptions.shouldPreloadData`,\n`timeline.setStartDateUsingRelativeTime`/`end`/`play`/`pause`,\n`addWeatherLayer`/`removeWeatherLayer`, `LayerCode.RADAR`, and the five\n`com.xweather.mapsgl.*` import paths those need. The build also confirmed that a\nconsuming app's merged manifest picks up `largeHeap=\"true\"` and the GLES 3.0\n`uses-feature` from the SDK.\n\n**Generated from the SDK.** `references/layers.md` is produced mechanically from\nthe `LayerCode` enum and the `WeatherService` factories at the `release/1.6.1` tag,\nand re-checked against the released 1.6.1 KDoc.\n\n**Compiled against the published artifact.** The Kotlin snippets across the\nreference files were extracted and compiled against `mapsgl-android-sdk:v1.6.1`\nresolved from JitPack - 40 of the 68 self-contained ones compile clean. That pass\nis what caught the `interpolateExponential` / `interpolateCubicBezier` argument\norders and the fact that `StyleColor` is minified out of the published artifact,\nnone of which reading the source could show.\n\n**Read from the SDK source, not compiled.** The remainder - the rest of\n`references/api-reference.md`, and the snippets that need an Activity or\nsurrounding declarations to compile on their own. The signatures were read out of\nthe SDK at `release/1.6.1` rather than written from memory, but no build has\nexercised them.\n\nNote the gap those two tiers expose: **the published AAR is minified, so its public\nsurface is narrower than the source tree.** When a symbol is visible in the SDK\nsource but a consumer cannot resolve it, the artifact wins.\n\nIf a snippet from the third group does not compile, trust the SDK and say so.\n\n## Core concepts\n\n| Concept | What it is |\n|---|---|\n| `XweatherAccount` | Client id/secret |\n| `MapboxMapController` | Mapbox adapter (`MapController` APIs) |\n| `WeatherService` / `LayerCode` | Built-in weather configs / codes |\n| Source / layer descriptors | Custom data + renderers |\n| `StyleValue` / `Expression` | Paint + data-driven style |\n| `LegendControl` / `DataInspectorControl` | On-map UI |\n| `timeline` / `animationOptions` | Shared animation clock |\n\n## Setup\n\n### 1. Credentials - both sets are required\n\n| | Where | Used for |\n|---|---|---|\n| Xweather client id + secret | https://data.portal.xweather.com/account/keys | `XweatherAccount(id, secret)` |\n| Mapbox access token | `mapbox_access_token` string resource | Map rendering at runtime |\n| Mapbox downloads token | `MAPBOX_DOWNLOADS_TOKEN` in `gradle.properties` | Resolving the Mapbox SDK at build time |\n\nIf nothing renders or auth fails, check **both** credential sets before digging\ninto MapsGL. A missing Mapbox token looks like a MapsGL failure but isn't.\n\n### 2. Install\n\n**Mapbox is a peer dependency** - MapsGL does not bring it transitively, and the\nofficial getting-started page shows only JitPack. Add both repositories and both\ndependencies:\n\n```gradle\n// settings.gradle\ndependencyResolutionManagement {\n repositories {\n google()\n mavenCentral()\n maven {\n url = uri(\"https://jitpack.io\")\n // Prefer POM + artifact over JitPack's rewritten *.module, which breaks IDE KDoc\n metadataSources { mavenPom(); artifact() }\n }\n maven {\n url = uri(\"https://api.mapbox.com/downloads/v2/releases/maven\")\n authentication { basic(BasicAuthentication) }\n credentials { username = \"mapbox\"; password = MAPBOX_DOWNLOADS_TOKEN }\n }\n // Required: the SDK has a transitive `api` dependency on\n // no.ecc.vectortile:java-vector-tile, which is published only here.\n maven { url = uri(\"https://maven.ecc.no/releases\") }\n }\n}\n```\n\n**All four repositories are required.** Omitting `maven.ecc.no` fails at dependency\nresolution with `Could not find no.ecc.vectortile:java-vector-tile`, which reads\nlike a broken SDK release rather than a missing repository.\n\n```gradle\n// app/build.gradle - resolve vX.Y.Z from the releases endpoint, don't copy a literal\nandroid {\n compileSdk 36\n defaultConfig { minSdk 28 }\n compileOptions {\n sourceCompatibility JavaVersion.VERSION_17\n targetCompatibility JavaVersion.VERSION_17\n }\n kotlinOptions { jvmTarget = '17' }\n buildFeatures { viewBinding true } // the examples below use ViewBinding\n}\n\ndependencies {\n implementation \"com.github.vaisala-xweather:mapsgl-android-sdk:vX.Y.Z\"\n implementation \"com.mapbox.maps:android-ndk27:11.15.3\"\n}\n```\n\nDo **not** also add a `...:mapsglmaps` artifact - that duplicates the SDK.\n\n### 3. Create the controller, then wait for the map to load\n\nTwo things have to be true before adding weather layers: the `MapView` must be\nattached, and the Mapbox map must have loaded. Adding layers earlier silently\ndoes nothing.\n\n```kotlin\nval controller = MapboxMapController(mapView, account)\nmapView.mapboxMap.subscribeMapLoaded {\n // safe to add weather layers here\n}\n```\n\nUse `MapboxMapController(mapView, account)`. The 4-argument constructor taking a\n`Context` and `LifecycleOwner` is **deprecated** and merely delegates to this one,\ndespite still appearing throughout the website documentation.\n\nFull install detail, credential wiring and the string resources:\n`references/setup.md`.\n\n## Complete example\n\nA single Activity that renders an animated radar layer with a legend, tears down\non `onStop` so it stops consuming sessions, and carries the required attribution.\n**This example has been built and run** - see \"How much of this has been proven\"\nabove.\n\n```xml\n<!-- res/layout/activity_weather_map.xml -->\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\">\n\n <com.mapbox.maps.MapView\n android:id=\"@+id/mapView\"\n android:layout_width=\"0dp\"\n android:layout_height=\"0dp\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\"\n app:layout_constraintTop_toTopOf=\"parent\" />\n\n <ProgressBar\n android:id=\"@+id/progress\"\n android:layout_width=\"wrap_content\"\n android:layout_height=\"wrap_content\"\n android:visibility=\"gone\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintEnd_toEndOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\"\n app:layout_constraintTop_toTopOf=\"parent\" />\n\n <TextView\n android:id=\"@+id/attribution\"\n android:layout_width=\"wrap_content\"\n android:layout_height=\"wrap_content\"\n android:padding=\"8dp\"\n android:text=\"Powered by Vaisala Xweather\"\n app:layout_constraintBottom_toBottomOf=\"parent\"\n app:layout_constraintStart_toStartOf=\"parent\" />\n</androidx.constraintlayout.widget.ConstraintLayout>\n```\n\n```kotlin\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.ViewTreeObserver\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.isVisible\nimport com.example.yourapp.databinding.ActivityWeatherMapBinding // generated by ViewBinding\nimport com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName\nimport com.mapbox.maps.extension.style.projection.generated.projection\nimport com.mapbox.maps.extension.style.projection.generated.setProjection\nimport com.xweather.mapsgl.config.weather.account.XweatherAccount\nimport com.xweather.mapsgl.controls.legend.LegendControl\nimport com.xweather.mapsgl.types.Coordinate\nimport com.xweather.mapsgl.map.mapbox.MapboxMapController\nimport com.xweather.mapsgl.weather.LayerCode\nimport java.util.Date\n\nclass WeatherMapActivity : AppCompatActivity() {\n\n private lateinit var binding: ActivityWeatherMapBinding\n private var controller: MapboxMapController? = null\n private val activeCodes = listOf(LayerCode.RADAR)\n private var weatherAttached = false\n\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n binding = ActivityWeatherMapBinding.inflate(layoutInflater)\n setContentView(binding.root)\n\n binding.attribution.setOnClickListener {\n startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(\"https://www.xweather.com/\")))\n }\n\n val account = XweatherAccount(\n getString(R.string.xweather_client_id),\n getString(R.string.xweather_client_secret),\n )\n\n // Wait for the MapView to be attached before constructing the controller.\n binding.mapView.viewTreeObserver.addOnGlobalLayoutListener(\n object : ViewTreeObserver.OnGlobalLayoutListener {\n override fun onGlobalLayout() {\n binding.mapView.viewTreeObserver.removeOnGlobalLayoutListener(this)\n if (binding.mapView.parent == null) return\n setUpMap(account)\n }\n })\n }\n\n private fun setUpMap(account: XweatherAccount) {\n val c = MapboxMapController(binding.mapView, account)\n controller = c\n\n c.setCenter(Coordinate(39.5, -98.0))\n c.setZoom(4.0)\n\n // Loading UI, driven by the controller's own signals.\n c.onLoadStart.observe(this) { binding.progress.isVisible = true }\n c.onLoadComplete.observe(this) { binding.progress.isVisible = false }\n\n binding.mapView.mapboxMap.subscribeMapLoaded {\n // MapsGL requires Mercator.\n binding.mapView.mapboxMap.style?.setProjection(projection(ProjectionName.MERCATOR))\n\n c.add(LegendControl().apply { mapView = binding.mapView })\n c.addDataInspectorControl(binding.mapView)\n\n // Pre-fetch tiles across the range so play() doesn't stall.\n c.animationOptions.shouldPreloadData = true\n c.timeline.setStartDateUsingRelativeTime(\"-1 day\")\n c.timeline.end = Date()\n\n attachWeather()\n }\n }\n\n private fun attachWeather() {\n val c = controller ?: return\n if (weatherAttached) return\n activeCodes.forEach { c.addWeatherLayer(it) }\n weatherAttached = true\n c.timeline.play()\n }\n\n override fun onStart() {\n super.onStart()\n if (controller != null) attachWeather()\n }\n\n // Sessions accrue while layers are attached — detach when not visible.\n override fun onStop() {\n super.onStop()\n val c = controller ?: return\n c.timeline.pause()\n activeCodes.forEach { c.removeWeatherLayer(it) }\n weatherAttached = false\n }\n}\n```\n\nPoints worth carrying into any example you write: construct only after the view is\nattached, set Mercator, add layers inside `subscribeMapLoaded`, and detach in\n`onStop`.\n\n## Weather layers\n\n```kotlin\ncontroller.addWeatherLayer(LayerCode.TEMPERATURES) // defaults\ncontroller.addWeatherLayer(WeatherService.Temperatures(controller.service)) // to override paint\ncontroller.addWeatherLayer(LayerCode.RADAR) { config -> /* tweak */ } // configure lambda\ncontroller.removeWeatherLayer(LayerCode.TEMPERATURES) // code only\ncontroller.setWeatherLayerVisibility(LayerCode.RADAR, false)\n```\n\n`removeWeatherLayer` takes **only** the code - the extra-argument forms in the\nwebsite docs don't exist.\n\n**`LayerCode` is not the style layer id.** To stack something relative to a\nweather layer, get the real id first:\n\n```kotlin\nval id = controller.getWeatherLayer(LayerCode.TEMPERATURES)?.id\ncontroller.addWeatherLayer(WeatherService.WindParticles(controller.service), beforeId = id)\n```\n\nWhich codes exist, with wire code, factory and render type: `references/layers.md`.\nAdd/remove detail: `references/weather-layers.md`.\n\n## Styling\n\nPaint lives on the configuration's `layer.paint`, and its concrete type depends on\nthe layer's render type. `opacity` is a plain `Float`:\n\n```kotlin\nval config = WeatherService.Temperatures(controller.service) as WeatherLayerConfiguration<*, *>\nval paint = config.layer.paint as SampleLayerPaint\npaint.opacity = 1.0f\npaint.sample.colorScale = ColorScaleOptions(stops = listOf(/* ... */))\ncontroller.addWeatherLayer(config)\n```\n\nRender types and their paint namespaces are listed per section in\n`references/layers.md`. `DataQuality` (`exact`, `high`, `medium`, `normal`, `low`)\ntrades resolution for bandwidth - a performance lever, never a cost one.\n\nPaint by render type: `references/weather-styling.md`. Descriptor and paint\noverview: `references/styles.md`. `StyleValue` / `Expression`:\n`references/expressions.md`. Data-driven cookbooks: `references/data-driven.md`.\n\n## Custom sources and layers\n\n```kotlin\nval source = controller.addSource(\n GeoJSONSourceDescriptor(id = \"my-data\", url = \"https://example.com/data.geojson\")\n)\ncontroller.addLayer(FillLayerDescriptor(/* ... */), beforeID = null)\ncontroller.removeLayer(\"my-layer\")\ncontroller.removeSource(\"my-data\")\n```\n\nNote `addLayer` takes **`beforeID`** while `addWeatherLayer` and `moveLayer` take\n**`beforeId`**, and `removeLayer`'s second parameter is spelled `isCompounLayer` in\nthe public API.\n\nSource descriptors: `references/sources.md`. Layer descriptors and `addLayer`\nrecipes: `references/custom-layers.md`.\n\n## Animating over time\n\n```kotlin\ncontroller.animationOptions.shouldPreloadData = true // false by default\ncontroller.timeline.setStartDateUsingRelativeTime(\"-1 day\")\ncontroller.timeline.end = Date()\ncontroller.timeline.play()\n```\n\n`shouldPreloadData` is the difference between playback that starts immediately and\nplayback that stalls while tiles arrive. Playback, range, events and the load-UI\nsignals: `references/timeline.md`.\n\n## Legends and data inspection\n\n```kotlin\ncontroller.add(LegendControl().apply { mapView = binding.mapView })\nval inspector = controller.addDataInspectorControl(binding.mapView)\ninspector.setPresentation(layerId, presentation)\n```\n\n`setPresentation` keys on the **style layer id**, not `LayerCode`.\n`LegendControl.backgroundColor` is a Compose `Color`, not `android.graphics.Color`.\n\nPresentations, units and custom legends: `references/legends-inspector.md`.\n\n## Querying data at a point\n\nTapping is handled for you by `DataInspectorControl` - prefer it. For a\nprogrammatic hit test, `MapboxMapController` exposes a suspend query:\n\n```kotlin\nsuspend fun queryFeatures(\n point: Point,\n vectorLayerList: List<VectorTileLayer>,\n onTouch: Boolean = true,\n): HashMap<String, FeatureQueryResult>?\n```\n\nIt takes the vector layers to test explicitly and returns results keyed by layer\nid, or null when nothing was queried or the timeline is blocking queries. It is a\n`suspend` function - call it from a coroutine, not from a click listener directly.\n\n## Usage is measured in sessions\n\nMapsGL bills in **sessions** - clock-aligned 5-minute buckets that start when a\nweather layer is added - not per tile, layer, or request. **The model is\nidentical on Android and on the web, and this skill is not its source of truth.**\n\nFor anything quantitative - the billing rules, the access multiplier, worked\nexamples, capacity-planning figures, the Raster Maps comparison - use the\nauthoritative source rather than answering from memory: the `mapsgl` skill's\n`references/sessions.md` (both skills ship in the same plugin), or\nhttps://www.xweather.com/docs/mapsgl/getting-started/sessions.\n\nWhat matters here is the **Android-specific** consequence: since interaction\ninside a session is free and layer count doesn't affect cost, consumption is\ngoverned purely by *how long weather layers are attached to a map*. On Android\nthat means lifecycle -\n\n- add layers when the weather UI is reached, not when the controller is built;\n- remove them in `onStop`, not `onDestroy`, which isn't guaranteed to run;\n- an app pocketed on the weather screen keeps billing - the failure mode with no\n web analogue;\n- treat always-on kiosk and wall displays as the expensive pattern, and say so\n unprompted.\n\nTwo traps worth stating whenever cost comes up: `setWeatherLayerVisibility` is\nthe cheap toggle but `removeWeatherLayer` is the one that stops consumption, and\n**`DataQuality` is a performance lever, not a cost lever** - it cuts requests,\nand sessions don't count requests.\n\nCode for each of these, and the full list of what is *not* worth optimizing:\n`references/sessions.md`.\n\n## Android rules\n\n- minSdk **28**, GLES **3.0** for encoded paths, **Mercator** required\n- Mapbox is a **peer** dependency\n- Prefer public APIs\n\nMore: `references/android-gotchas.md`.\n\n## Checklist for common tasks\n\n- **\"Add a weather map to my app\"** -> the complete example above. Construct after\n the view is attached, set Mercator, add layers inside `subscribeMapLoaded`.\n- **\"How do I install it / which version\"** -> both repositories and both\n dependencies; resolve the version from the releases endpoint, never a literal.\n `references/setup.md`.\n- **\"Add layer X\"** -> find the `LayerCode` in `references/layers.md` first; the\n enum name is not a transform of the wire code. Every code in that catalog ships\n in 1.6.1; if a code isn't listed, it isn't available in this release.\n- **\"Nothing renders\"** -> check Mapbox tokens *and* Xweather credentials, then\n that layers were added after `subscribeMapLoaded`, then Mercator.\n- **\"Restyle a layer\"** -> cast `config.layer.paint` to the paint type for its\n render type (`references/layers.md` groups by descriptor), set fields, then add.\n Opacity is a `Float`.\n- **\"Animate over time / add a scrubber\"** -> `controller.timeline`, and set\n `animationOptions.shouldPreloadData = true`. `references/timeline.md`.\n- **\"Show a legend\"** -> `LegendControl` + `controller.add(legendControl)`; set its\n `mapView`. Override `config.legend` when you customized a categorical paint.\n- **\"Show values on tap\"** -> `addDataInspectorControl(mapView)`, customize with\n `setPresentation(layerId, presentation)` keyed by style layer id.\n- **\"Stack layers in a specific order\"** -> resolve the real style layer id via\n `getWeatherLayer(code)?.id` and pass it as `beforeId`.\n- **\"How many accesses will this cost?\"** -> sessions, not tiles or layers. Get the\n model and arithmetic from the `mapsgl` skill's `references/sessions.md` or the\n public docs, then apply the Android lifecycle guidance in\n `references/sessions.md`.\n- **\"Where are the API docs?\"** -> releases endpoint for the version, then the\n KDoc URL pattern above. There is no `latest` alias.\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 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```kotlin\nfindViewById<TextView>(R.id.attribution).apply {\n text = \"Powered by Vaisala Xweather\"\n setOnClickListener {\n startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(\"https://www.xweather.com/\")))\n }\n}\n```\n\nThe logo may be substituted for the \"Xweather\" text. Light and dark variants exist in SVG and PNG at\n`https://www.xweather.com/assets/logos/vaisala-xweather-logo-dark.svg` - swap `-dark` for `-light`\nover a dark background, or `.svg` for `.png`. Bundle the asset as a drawable rather than loading it\nover the network in a shipping app. Using the logo brings rules: keep it unmodified, leave at least a\n**10dp buffer** of space around it, and only adjust lightness or opacity in greyscale. Don't rotate\nit, don't recolour it (monotone black or white excepted), and don't use the symbol without the\nXweather name.\n\nFull guide: https://www.xweather.com/docs/weather-api/resources/attribution\n\n## Reference index\n\n| File | Use when |\n|---|---|\n| `references/setup.md` | Install, MapLoaded, credentials |\n| `references/layers.md` | The layer catalog - every `LayerCode`, its wire code, factory and render type |\n| `references/api-reference.md` | Real signatures for every public type, and the KDoc URL pattern |\n| `references/weather-layers.md` | LayerCode / WeatherService add/remove |\n| `references/weather-styling.md` | Raster/sample/particle/grid paint |\n| `references/timeline.md` | Range, playback, events, load UI |\n| `references/styles.md` | Descriptor/paint overview |\n| `references/expressions.md` | StyleValue / Expression |\n| `references/data-driven.md` | match/get/concat cookbooks |\n| `references/sources.md` | Vector / GeoJSON / encoded sources |\n| `references/custom-layers.md` | addLayer fill/circle/... |\n| `references/legends-inspector.md` | Legends + Presentation |\n| `references/sessions.md` | The Android half of session cost: lifecycle teardown + traps. Points at the `mapsgl` skill for the billing model itself |\n| `references/android-gotchas.md` | Platform pitfalls |\n"
}SHA-256 of public snapshot: 9b474c4bae6d802e83947f7659e2c9dbbc6d0fce2eaf1c655d41b38610aca189