← Files Vaisala Xweather API & MapsARCHIVED FILE

skills/mapsgl-android/references/legends-inspector.md

8.44 KB · Oct 5, 2026 · 18:33 UTC

↓ Download file

# Legends & data inspector - MapsGL Android

Verified against `LegendControl`, `Legend`, `BarLegend`, `PointLegend`, `LegendCode`,
`DataInspectorControl`, `Presentation` and `MeasurementUnits` at the SDK's `release/1.6.1` tag.
Docs are secondary when they disagree:
https://www.xweather.com/docs/mapsgl-android-sdk/getting-started/legends ·
https://www.xweather.com/docs/mapsgl-android-sdk/controls/data-inspector

## `LegendControl`

```kotlin
val legendControl = LegendControl().apply { mapView = binding.mapView }
controller.add(legendControl)

parentLayout.addView(legendControl.getView())   // LegendContainerView
```

Built-in weather layers register and unregister their own legends once the control is attached.
Several layers can share one legend - it is removed only when the last referencing layer goes. That
ref-counting is why removing one layer sometimes leaves a legend on screen, which is correct rather
than a bug.

Position `getView()` with normal `ConstraintLayout` / `FrameLayout` params; the docs suggest
bottom-end with ~16dp margins and ~300dp width.

### Configuration

```kotlin
legendControl.units = MeasurementUnits.IMPERIAL   // or METRIC
legendControl.toggleUnitsOnTap = true             // default
legendControl.onUnitsToggled = { units -> /* mirror elsewhere in your UI */ }
legendControl.isHidden = false
legendControl.maxLegendContentHeightDp = 240
legendControl.backgroundColor = Color.Transparent  // Compose Color, not android.graphics.Color
legendControl.setHostTitleColor(Color.White)

legendControl.applyUnits(MeasurementUnits.METRIC)
legendControl.toggleUnits()
```

`toggleUnitsOnTap` is on by default, so tapping the legend flips imperial/metric whether or not you
wired anything up. Use `onUnitsToggled` to keep the rest of your UI in step.

`backgroundColor` and `setHostTitleColor` take **Compose** `androidx.compose.ui.graphics.Color` -
passing an `android.graphics.Color` int will not compile.

### Dark mode

```kotlin
legendControl.setDarkTheme(true)
legendControl.usesDarkTheme = true
```

`setDarkTheme` swaps the panel colour (roughly `0xCC000000` dark, `0xE6FFFFFF` light) and restyles
legends as they are added. Set it before adding layers so their legends pick it up.

## The two legend types

`Legend` is the interface (`id`, `title`, `titleFontSize`, `titleColorValue`, `units`). Two
implementations ship.

### `BarLegend` - continuous ranges

```kotlin
BarLegend<UnitType>(
    id = "my-legend",
    items = listOf(
        BarLegendItem(
            colorScaleOptions = ColorScaleOptions(stops = listOf(/* ... */)),
            labels = BarLegendLabels(values = /* Values<UnitType> */, placement = Placement.MIDDLE),
            height = BarLegendDefaults.barHeight,
            rounded = true,
        )
    ),
    title = "Temperature",
    measurement = MeasurementUnits.MeasurementType.TEMPERATURE,
    units = MeasurementUnits.IMPERIAL,
    resample = null,          // optional EasingCurve to redistribute the ramp
)
```

Generic over a `Dimension` unit type, and `currentUnits` defaults from `units.dimensions(measurement)`
- so setting `measurement` is what makes unit switching work. `MeasurementType` covers `TEMPERATURE`,
`TEMPERATURE_DELTA`, `SPEED`, `PRESSURE`, `DISTANCE`, `HEIGHT`, `PRECIPITATION`, `SNOWFALL`,
`DIRECTION`, `TIME`.

Each `BarLegendItem` carries its own `ColorScaleOptions`, so one legend can stack several bars.

### `PointLegend` - discrete categories

```kotlin
PointLegend(
    id = "alerts-legend",
    title = "Alerts",
    items = listOf(
        PointLegendItem(color = 0xFFFF0000.toInt(), label = "Warning"),
        PointLegendItem(color = 0xFFFFA500.toInt(), label = "Watch"),
    ),
    radius = 6.0,
    units = MeasurementUnits.IMPERIAL,
)
```

Note the asymmetry: **`PointLegendItem.color` is an `@ColorInt` Int**, not a Compose `Color`, unlike
everything else in the legend API.

`itemResolver` plus `layerId` lets a point legend build its items from the features actually on screen
rather than a fixed list - that is how the alerts legend shows only the alert types currently visible.
`layerId` is treated as a **regex**, so it can match a family of layer ids.

## Built-in legends

`LegendCode` is an auto-generated enum of **63** legend ids, each able to build its legend:

```kotlin
val legend: Legend = LegendCode.ACCUM_PRECIP.getLegend()
```

Codes are id-style strings - `accum-precip`, `accum-snow`, `air-quality-index`,
`air-quality-index-cai`, and so on. Use it to start from a stock legend and adjust, rather than
building a colour ramp from scratch.

**`LegendCode` and `LayerCode` are different enums with different names for the same thing.** The
temperature legend is `LegendCode.TEMPERATURE` (singular) while the layer is `LayerCode.TEMPERATURES`
(plural), and there are 63 legend codes against 209 layer codes, so there is no one-to-one mapping.
Look the legend code up rather than deriving it from the layer code.

Do not hand-edit `LegendCode.kt`; it is generated.

## Overriding a weather layer's legend

Every `WeatherLayerConfiguration` carries `var legend: Legend?`. Set it before adding the layer:

```kotlin
val config = WeatherService.Temperatures(controller.service)
config.legend = LegendCode.TEMPERATURE.getLegend().apply { title = "Temp (F)" }
controller.addWeatherLayer(config)
```

**Whenever you customize a categorical layer's paint, override its legend too** - the stock legend
describes the stock colours and will otherwise disagree with what is drawn.

`controller.addLegend(forConfig)` registers a configuration's legend explicitly, which is useful when
you built the configuration but added its layers by another route.

## Updating and removing

```kotlin
legendControl.add(legend)
legendControl.replaceLegend(id, newLegend)     // swap in place, keeps position
legendControl.legends                          // Map<String, Legend>, read-only view
controller.removeLegendControl()
```

`replaceLegend` is the way to change a legend that is already on screen - removing and re-adding
disturbs ordering and loses the ref count.

## `DataInspectorControl`

```kotlin
val control = controller.addDataInspectorControl(binding.mapView)
controller.removeDataInspectorControl()

control.cancelCalloutWhenOffScreen = true      // default
control.show(screenPoint, coordinate)
control.move(screenPoint)
control.update()
control.hide()
control.bringCalloutToFront()
```

Also reachable as `controller.dataInspector` without adding it.

### Presentations - formatting the callout

A `Presentation` has a title and a function turning the feature payload into display text.

```kotlin
import com.xweather.mapsgl.weather.common.Presentation
import com.xweather.mapsgl.weather.common.Units

val tempsConfig = WeatherService.Temperatures(controller.service)
tempsConfig.presentation = Presentation(
    title = "Temperature (F)",
    fn = { features ->
        val featureMap = features as? Map<*, *> ?: return@Presentation ""
        val tempC = (featureMap["value"] as? Float) ?: return@Presentation ""
        String.format("%.1f", Units.CtoF(tempC))
    },
)
controller.addWeatherLayer(tempsConfig)
```

**Encoded values are metric** - temperature in °C, wind in m/s - so convert in the presentation.
`Units` carries the helpers (`CtoF`, `msToMph`, …).

Wind layers expose `value` and `angle`. **`angle` is the direction the wind is blowing *toward*;** the
meteorological "from" bearing is `angle - 180`:

```kotlin
val windsConfig = WeatherService.WindSpeeds(controller.service)
windsConfig.presentation = Presentation(
    title = "Winds (mph)",
    fn = { features ->
        val featureMap = features as? Map<*, *> ?: return@Presentation ""
        val speedMs = (featureMap["value"] as? Float) ?: return@Presentation ""
        val angleDeg = (featureMap["angle"] as? Float) ?: return@Presentation ""
        "${String.format("%.1f", Units.msToMph(speedMs))}, ${String.format("%.0f", angleDeg - 180)} degrees"
    },
)
controller.addWeatherLayer(windsConfig)
```

### Setting a presentation after the layer exists

`setPresentation` keys on the **style layer id**, not `LayerCode` - resolve it first:

```kotlin
controller.getWeatherLayer(LayerCode.TEMPERATURES)?.let { layer ->
    control.setPresentation(layer.id, tempsPresentation)
}
control.removePresentation(layerId)
```

Passing `LayerCode.TEMPERATURES.value` or the string `"temperatures"` will not match.

## Attribution

The Mapbox logo and attribution are `MapView` settings and are governed by Mapbox's terms. Xweather's
own "Powered by Vaisala Xweather" credit is separate and always required - see the attribution
section of `SKILL.md`.

SHA-256: 3c6f9122ebb6225720774d6c1ba601d95eee6ced4ba7acf572cf4f47ffaa0339