Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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 ​

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.MapLibreMap
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: MapLibreMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_map)

        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync { maplibreMap ->
            map = maplibreMap

            map.setStyle(
                Style.Builder().fromUri(
                    "https://gateway.mapmetrics-atlas.net/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 ​


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.