Skip to Content
ExamplesChange Map Units

MapsGL Android - Change map units

This example shows wind speeds with a legend and a selector that switches the speed unit between km/h, mph, and m/s at runtime. The legend relabels itself to match; the underlying data is unchanged, so only the formatting moves.

It is the Android port of the MapsGL JS Change map units example. The complete activity ships in the MapsGL Android SDK demo  as ChangeMapUnitsActivity.

The same wind-speed data, before and after switching the selector. Only the formatting changes — the legend relabels itself and rescales its ticks, while the layer underneath is untouched.

Wind speeds with the legend reading 0 mph through 100
The same wind speeds with the legend reading 0 km/h through 180

Set up the map

Create a MapboxMapController, position the camera, and wait for the map to load before adding the legend and weather layer.

import com.mapbox.maps.MapboxMap import com.xweather.mapsgl.config.weather.account.XweatherAccount import com.xweather.mapsgl.map.mapbox.MapboxMapController import com.xweather.mapsgl.types.Coordinate import com.xweather.mapsgl.weather.LayerCode val xweatherAccount = XweatherAccount( getString(R.string.xweather_client_id), getString(R.string.xweather_client_secret), ) val controller = MapboxMapController(mapView, xweatherAccount) val mapboxMap = controller.mapboxMap ?: return // Matches the JS example's `center: [-93, 40], zoom: 4`. Coordinate is (lat, lon). controller.setCenter(Coordinate(40.0, -93.0)) controller.setZoom(4.0) val mapLoadedCancelable = mapboxMap.subscribeMapLoaded { attachLegend() // Seed the controller from the selector so the two agree before the first draw. applySpeedUnit(speedUnitIndex) controller.addWeatherLayer(LayerCode.WIND_SPEEDS) }

Change the units

Assign a modified copy of the controller’s current MeasurementUnits. Only the category you name changes; every other unit keeps its current value.

import com.xweather.mapsgl.extensions.UnitSpeed // The Android equivalent of `controller.setUnits({ speed: … })`. controller.units = controller.units.copy(speed = UnitSpeed.kilometersPerHour)

UnitSpeed supplies kilometersPerHour, milesPerHour, and metersPerSecond, matching the three options in the JS example’s <select>. Wire them to whatever control you like — the demo uses a Spinner:

private fun applySpeedUnit(index: Int) { val unit = when (index) { 0 -> UnitSpeed.kilometersPerHour 2 -> UnitSpeed.metersPerSecond else -> UnitSpeed.milesPerHour } controller.units = controller.units.copy(speed = unit) }

MeasurementUnits also ships IMPERIAL and METRIC presets if you want to switch every category at once rather than one field.

Add the legend

The JS example’s addLegendControl('#legend') becomes a LegendControl added to the controller, whose view you then position in your own layout. The legend picks up unit changes on its own — no extra call is needed when controller.units changes.

import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import com.xweather.mapsgl.controls.legend.LegendControl private val legendControl = LegendControl() private fun attachLegend() { controller.add(legendControl) legendControl.setDarkTheme(true) val legendView = legendControl.getView() legendView.id = View.generateViewId() binding.changeMapUnitsRoot.addView(legendView, 1) val params = legendView.layoutParams as ConstraintLayout.LayoutParams val parentId = ConstraintLayout.LayoutParams.PARENT_ID params.endToEnd = parentId params.bottomToBottom = parentId params.bottomMargin = dp(24) params.marginEnd = dp(16) params.width = dp(300) legendView.layoutParams = params } private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt()

See the legend control for styling and placement options.

Complete example

The layout is a Mapbox MapView with a label and Spinner for the unit selector. The root ConstraintLayout needs an id, since the legend view is added to it at runtime:

activity_change_map_units.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/changeMapUnitsRoot" android:layout_width="match_parent" android:layout_height="match_parent"> <com.mapbox.maps.MapView android:id="@+id/changeMapUnitsMapView" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:mapbox_mapSurface="texture_view" /> <TextView android:id="@+id/changeMapUnitsSpeedLabel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="12dp" android:text="Wind speed units" android:textSize="13sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Spinner android:id="@+id/changeMapUnitsSpeedSpinner" android:layout_width="140dp" android:layout_height="44dp" android:layout_marginStart="8dp" android:layout_marginTop="4dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/changeMapUnitsSpeedLabel" /> </androidx.constraintlayout.widget.ConstraintLayout>

And the activity:

ChangeMapUnitsActivity.kt
package com.example.mapsgldemo.docExamples import android.os.Bundle import android.view.View import android.view.ViewTreeObserver import android.widget.AdapterView import android.widget.ArrayAdapter import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout import com.example.mapsgldemo.R import com.example.mapsgldemo.databinding.ActivityChangeMapUnitsBinding import com.mapbox.common.Cancelable import com.mapbox.maps.MapView import com.mapbox.maps.MapboxMap import com.mapbox.maps.extension.style.layers.properties.generated.ProjectionName import com.mapbox.maps.extension.style.projection.generated.projection import com.mapbox.maps.extension.style.projection.generated.setProjection import com.xweather.mapsgl.config.weather.account.XweatherAccount import com.xweather.mapsgl.controls.legend.LegendControl import com.xweather.mapsgl.extensions.UnitSpeed import com.xweather.mapsgl.map.mapbox.MapboxMapController import com.xweather.mapsgl.types.Coordinate import com.xweather.mapsgl.weather.LayerCode class ChangeMapUnitsActivity : AppCompatActivity() { private lateinit var binding: ActivityChangeMapUnitsBinding private lateinit var mapView: MapView private var mapboxMap: MapboxMap? = null private lateinit var controller: MapboxMapController private var mapLoadedCancelable: Cancelable? = null private val legendControl = LegendControl() private var syncingUnitsSpinner = false private var speedUnitIndex = DEFAULT_SPEED_UNIT_INDEX override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityChangeMapUnitsBinding.inflate(layoutInflater) setContentView(binding.root) mapView = binding.changeMapUnitsMapView val xweatherAccount = XweatherAccount( getString(R.string.xweather_client_id), getString(R.string.xweather_client_secret), ) setupUnitsSpinner() mapView.viewTreeObserver.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { mapView.viewTreeObserver.removeOnGlobalLayoutListener(this) if (mapView.parent == null) return controller = MapboxMapController(mapView, xweatherAccount) mapboxMap = controller.mapboxMap // Matches the JS example's `center: [-93, 40], zoom: 4`. Coordinate is (lat, lon). controller.setCenter(Coordinate(40.0, -93.0)) controller.setZoom(4.0) mapLoadedCancelable = mapboxMap?.subscribeMapLoaded { mapboxMap?.style?.setProjection(projection(ProjectionName.MERCATOR)) attachLegend() // Seed the controller from the selector so the two agree // before the first draw. applySpeedUnit(speedUnitIndex) controller.addWeatherLayer(LayerCode.WIND_SPEEDS) } } }, ) } /** Mirrors the JS `addLegendControl('#legend')`. */ private fun attachLegend() { controller.add(legendControl) legendControl.setDarkTheme(true) val legendView = legendControl.getView() legendView.id = View.generateViewId() binding.changeMapUnitsRoot.addView(legendView, 1) val params = legendView.layoutParams as ConstraintLayout.LayoutParams val parentId = ConstraintLayout.LayoutParams.PARENT_ID params.endToEnd = parentId params.bottomToBottom = parentId params.bottomMargin = dp(24) params.marginEnd = dp(16) params.width = dp(300) legendView.layoutParams = params } private fun setupUnitsSpinner() { val adapter = ArrayAdapter( this, R.layout.map_overlay_spinner_item, SPEED_UNIT_LABELS, ).also { it.setDropDownViewResource(R.layout.map_overlay_spinner_dropdown_item) } binding.changeMapUnitsSpeedSpinner.adapter = adapter syncingUnitsSpinner = true binding.changeMapUnitsSpeedSpinner.setSelection(speedUnitIndex) syncingUnitsSpinner = false binding.changeMapUnitsSpeedSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected( parent: AdapterView<*>?, view: View?, position: Int, id: Long, ) { if (syncingUnitsSpinner) return if (position == speedUnitIndex) return speedUnitIndex = position if (::controller.isInitialized) applySpeedUnit(position) } override fun onNothingSelected(parent: AdapterView<*>?) {} } } /** The Android equivalent of `controller.setUnits({ speed: … })`. */ private fun applySpeedUnit(index: Int) { val unit = when (index) { 0 -> UnitSpeed.kilometersPerHour 2 -> UnitSpeed.metersPerSecond else -> UnitSpeed.milesPerHour } controller.units = controller.units.copy(speed = unit) } private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt() override fun onStart() { super.onStart() mapView.onStart() } override fun onStop() { mapView.onStop() super.onStop() } override fun onLowMemory() { super.onLowMemory() mapView.onLowMemory() } override fun onDestroy() { mapLoadedCancelable?.cancel() mapLoadedCancelable = null mapView.onDestroy() super.onDestroy() } companion object { /** Same options and order as the JS example's select, which defaults to mph. */ private val SPEED_UNIT_LABELS = arrayOf("km/h", "mph", "m/s") private const val DEFAULT_SPEED_UNIT_INDEX = 1 } }
© 2026 Xweather (opens in a new tab)Terms of Service (opens in a new tab)Privacy Policy (opens in a new tab)