Add an Image Overlay
This tutorial shows how to overlay an image (photo, floor plan, historical map) on top of your MapMetrics Android map, positioned at specific geographic coordinates.
Prerequisites
- Completed the Getting Started Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Image Overlay
Place an image on the map anchored to four corner coordinates:
kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.geometry.LatLngQuad
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.layers.RasterLayer
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.sources.ImageSource
class ImageOverlayActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var map: MapMetricsMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
mapView = findViewById(R.id.mapView)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync { mapMetricsMap ->
map = mapMetricsMap
map.setStyle(
Style.Builder().fromUri(
"https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY"
)
) { style ->
addImageOverlay(style)
}
}
}
private fun addImageOverlay(style: Style) {
// Define the four corners of the image placement
val quad = LatLngQuad(
LatLng(48.8640, 2.2880), // Top-left (NW)
LatLng(48.8640, 2.3010), // Top-right (NE)
LatLng(48.8530, 2.3010), // Bottom-right (SE)
LatLng(48.8530, 2.2880) // Bottom-left (SW)
)
// Add image source from drawable resource
style.addSource(
ImageSource("overlay-source", quad, R.drawable.historic_map)
)
// Add raster layer to display the image
style.addLayer(
RasterLayer("overlay-layer", "overlay-source")
.withProperties(
rasterOpacity(0.7f)
)
)
// Center on the overlay
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(48.8585, 2.2945))
.zoom(15.0)
.build()
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { super.onPause(); mapView.onPause() }
override fun onStop() { super.onStop(); mapView.onStop() }
override fun onDestroy() { super.onDestroy(); mapView.onDestroy() }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
}Adjustable Opacity
Let users control the overlay transparency:
kotlin
import android.widget.SeekBar
import android.widget.TextView
private fun setupOpacitySlider(style: Style) {
val opacityText = findViewById<TextView>(R.id.tvOpacity)
findViewById<SeekBar>(R.id.seekOpacity).apply {
max = 100
progress = 70 // default 70%
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, value: Int, user: Boolean) {
val opacity = value / 100f
opacityText.text = "Opacity: $value%"
val layer = style.getLayer("overlay-layer") as? RasterLayer
layer?.setProperties(rasterOpacity(opacity))
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
}
}Toggle Overlay Visibility
Show or hide the overlay with a button:
kotlin
import android.widget.ToggleButton
import org.maplibre.android.style.layers.Property
private var overlayVisible = true
private fun setupToggle(style: Style) {
findViewById<ToggleButton>(R.id.btnToggleOverlay).setOnCheckedChangeListener { _, checked ->
overlayVisible = checked
val layer = style.getLayer("overlay-layer") as? RasterLayer
layer?.setProperties(
visibility(
if (checked) Property.VISIBLE else Property.NONE
)
)
}
}Next Steps
- Animated Image Source — Animate image overlays
- Custom Sprite — Custom icons and sprites
- Building Layer — 3D building extrusions
Tip: Image overlays work best for small areas (a few blocks). For larger coverage, use raster tile sources instead — they load tiles progressively and don't require loading one massive image into memory.