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.

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:
<?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:
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"
}
}Related
- Custom data sources — tile URL templates and credential expansion
- Custom map layers — all supported layer descriptors
- Weather layers — adding
satellite-geocolorand other products without a custom source