Skip to Content
ExamplesAdd Raster Layer

MapsGL Android - Adding a custom raster layer

This example renders the satellite-geocolor Xweather Maps raster layer using a custom data source and adjusting its opacity.

It is the Android port of the MapsGL JS Adding a custom raster layer example. Building the source by hand is the point: it shows the tile URL template a custom raster product would use, rather than letting WeatherService.SatelliteGeocolor wire it for you. The complete activity ships in the MapsGL Android SDK demo  as AddRasterLayerActivity.

Satellite geocolor imagery covering Europe, drawn at partial opacity over the base map

Set up the map

Create a MapboxMapController, position the camera, and wait for the map to load before adding the source and 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 val clientId = getString(R.string.xweather_client_id) val clientSecret = getString(R.string.xweather_client_secret) val xweatherAccount = XweatherAccount(clientId, clientSecret) val controller = MapboxMapController(mapView, xweatherAccount) val mapboxMap = controller.mapboxMap ?: return // Matches the JS example's `center: [12.35666, 45.34880], zoom: 3`. controller.setCenter(Coordinate(45.34880, 12.35666)) controller.setZoom(3.0) val mapLoadedCancelable = mapboxMap.subscribeMapLoaded { // Add the source and layer here — see below. }

Add the raster tile source

Raster tiles come from an ImageSourceDescriptor. {z}, {x}, and {y} are filled in per tile; the access key and product code are baked into the template up front, which is what WeatherService does for the built-in raster products.

import com.xweather.mapsgl.sources.source.spec.ImageSourceDescriptor import com.xweather.mapsgl.types.TileSize // Add the raster data tile source. if (!controller.hasSource("satellite-geocolor")) { controller.addSource( ImageSourceDescriptor( id = "satellite-geocolor", url = "https://maps.api.xweather.com/${clientId}_$clientSecret/satellite-geocolor/{z}/{x}/{y}/0@2x.png", tileSize = TileSize(512), ), ) }

The JS example uses the older sharded maps{s}.aerisapi.com host. This uses the current maps.api.xweather.com host, matching what WeatherService.makeRasterSourceDescriptor builds. See custom data sources for the [CLIENT_ID]_[CLIENT_SECRET] template form, which lets the controller’s authenticator expand credentials per request instead of interpolating them yourself.

Add the raster layer

Add a RasterLayerDescriptor referencing the source id.

import com.xweather.mapsgl.layers.spec.RasterLayerDescriptor import com.xweather.mapsgl.style.RasterLayerPaint import com.xweather.mapsgl.style.RasterPaint // Add the raster layer and associate it with the data source. if (!controller.hasLayer("satellite")) { controller.addLayer( RasterLayerDescriptor( id = "satellite", source = "satellite-geocolor", paint = RasterLayerPaint( opacity = 0.7f, raster = RasterPaint(), ), ), beforeID = null, ) }

Clean up

An Android activity is torn down and recreated across configuration changes, so remove the layer and then the source it references, and cancel the map-loaded subscription, in onDestroy.

override fun onDestroy() { if (controller.hasLayer("satellite")) controller.removeLayer("satellite") if (controller.hasSource("satellite-geocolor")) controller.removeSource("satellite-geocolor") mapLoadedCancelable?.cancel() mapView.onDestroy() super.onDestroy() }

Complete example

The layout is a single Mapbox MapView:

activity_add_raster_layer.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:layout_width="match_parent" android:layout_height="match_parent"> <com.mapbox.maps.MapView android:id="@+id/addRasterMapView" 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" /> </androidx.constraintlayout.widget.ConstraintLayout>

And the activity, which adds the source and layer once the map has loaded:

AddRasterLayerActivity.kt
package com.example.mapsgldemo.docExamples import android.os.Bundle import android.view.ViewTreeObserver import androidx.appcompat.app.AppCompatActivity import com.example.mapsgldemo.R import com.example.mapsgldemo.databinding.ActivityAddRasterLayerBinding 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.layers.spec.RasterLayerDescriptor import com.xweather.mapsgl.map.mapbox.MapboxMapController import com.xweather.mapsgl.sources.source.spec.ImageSourceDescriptor import com.xweather.mapsgl.style.RasterLayerPaint import com.xweather.mapsgl.style.RasterPaint import com.xweather.mapsgl.types.Coordinate import com.xweather.mapsgl.types.TileSize class AddRasterLayerActivity : AppCompatActivity() { private lateinit var binding: ActivityAddRasterLayerBinding private lateinit var mapView: MapView private var mapboxMap: MapboxMap? = null private lateinit var controller: MapboxMapController private var mapLoadedCancelable: Cancelable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityAddRasterLayerBinding.inflate(layoutInflater) setContentView(binding.root) mapView = binding.addRasterMapView val clientId = getString(R.string.xweather_client_id) val clientSecret = getString(R.string.xweather_client_secret) val xweatherAccount = XweatherAccount(clientId, clientSecret) 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: [12.35666, 45.34880], zoom: 3`. // Coordinate is (lat, lon). controller.setCenter(Coordinate(45.34880, 12.35666)) controller.setZoom(3.0) mapLoadedCancelable = mapboxMap?.subscribeMapLoaded { mapboxMap?.style?.setProjection(projection(ProjectionName.MERCATOR)) // Add the raster data tile source. if (!controller.hasSource(SOURCE_ID)) { controller.addSource( ImageSourceDescriptor( id = SOURCE_ID, url = "$AMP_SERVER/${clientId}_$clientSecret/$PRODUCT_CODE/{z}/{x}/{y}/0@2x.png", tileSize = TileSize(512), ), ) } // Add the raster layer and associate it with the data source. if (!controller.hasLayer(LAYER_ID)) { controller.addLayer( RasterLayerDescriptor( id = LAYER_ID, source = SOURCE_ID, paint = RasterLayerPaint( opacity = 0.7f, raster = RasterPaint(), ), ), beforeID = null, ) } } } }, ) } override fun onStart() { super.onStart() mapView.onStart() } override fun onStop() { mapView.onStop() super.onStop() } override fun onLowMemory() { super.onLowMemory() mapView.onLowMemory() } override fun onDestroy() { if (::controller.isInitialized) { if (controller.hasLayer(LAYER_ID)) controller.removeLayer(LAYER_ID) if (controller.hasSource(SOURCE_ID)) controller.removeSource(SOURCE_ID) } mapLoadedCancelable?.cancel() mapLoadedCancelable = null mapView.onDestroy() super.onDestroy() } companion object { private const val SOURCE_ID = "satellite-geocolor" private const val LAYER_ID = "satellite" private const val PRODUCT_CODE = "satellite-geocolor" private const val AMP_SERVER = "https://maps.api.xweather.com" } }
© 2026 Xweather (opens in a new tab)Terms of Service (opens in a new tab)Privacy Policy (opens in a new tab)