Skip to content

Show Polygon Info on Click

This tutorial shows how to display information about a polygon zone when users tap inside it on your MapMetrics Android map.

Prerequisites

Clickable Polygons with Info

Create named zones and show details on tap:

kotlin
import android.graphics.Color
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.JsonObject
import org.maplibre.android.camera.CameraPosition
import org.maplibre.android.geometry.LatLng
import org.maplibre.android.maps.MapView
import org.maplibre.android.maps.MapMetricsMap
import org.maplibre.android.maps.Style
import org.maplibre.android.style.expressions.Expression.*
import org.maplibre.android.style.layers.FillLayer
import org.maplibre.android.style.layers.LineLayer
import org.maplibre.android.style.layers.PropertyFactory.*
import org.maplibre.android.style.sources.GeoJsonSource
import org.maplibre.geojson.*

class PolygonInfoActivity : AppCompatActivity() {

    private lateinit var mapView: MapView
    private lateinit var map: MapMetricsMap
    private lateinit var infoPanel: TextView

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

        infoPanel = findViewById(R.id.tvInfo)
        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 ->
                addZones(style)
                setupClickHandler()
            }
        }
    }

    private fun addZones(style: Style) {
        val zones = listOf(
            createZoneFeature(
                "zone-1", "Le Marais",
                "Historic district, popular for art galleries and cafés",
                "#FF6B35",
                listOf(
                    Point.fromLngLat(2.3500, 48.8600),
                    Point.fromLngLat(2.3650, 48.8600),
                    Point.fromLngLat(2.3650, 48.8530),
                    Point.fromLngLat(2.3500, 48.8530),
                    Point.fromLngLat(2.3500, 48.8600),
                )
            ),
            createZoneFeature(
                "zone-2", "Saint-Germain",
                "Left Bank intellectual quarter with bookshops and bistros",
                "#4285F4",
                listOf(
                    Point.fromLngLat(2.3200, 48.8550),
                    Point.fromLngLat(2.3400, 48.8550),
                    Point.fromLngLat(2.3400, 48.8480),
                    Point.fromLngLat(2.3200, 48.8480),
                    Point.fromLngLat(2.3200, 48.8550),
                )
            ),
            createZoneFeature(
                "zone-3", "Montmartre",
                "Hilltop village known for Sacré-Cœur and street artists",
                "#34A853",
                listOf(
                    Point.fromLngLat(2.3300, 48.8900),
                    Point.fromLngLat(2.3500, 48.8900),
                    Point.fromLngLat(2.3500, 48.8830),
                    Point.fromLngLat(2.3300, 48.8830),
                    Point.fromLngLat(2.3300, 48.8900),
                )
            ),
        )

        style.addSource(
            GeoJsonSource("zones-source", FeatureCollection.fromFeatures(zones))
        )

        // Fill layer — colored by feature property
        style.addLayer(
            FillLayer("zones-fill", "zones-source")
                .withProperties(
                    fillColor(get("color")),
                    fillOpacity(0.25f)
                )
        )

        // Outline
        style.addLayer(
            LineLayer("zones-outline", "zones-source")
                .withProperties(
                    lineColor(get("color")),
                    lineWidth(2f)
                )
        )

        // Camera
        map.cameraPosition = CameraPosition.Builder()
            .target(LatLng(48.865, 2.340))
            .zoom(12.5)
            .build()
    }

    private fun createZoneFeature(
        id: String,
        name: String,
        description: String,
        color: String,
        points: List<Point>
    ): Feature {
        val props = JsonObject().apply {
            addProperty("id", id)
            addProperty("name", name)
            addProperty("description", description)
            addProperty("color", color)
        }
        return Feature.fromGeometry(
            Polygon.fromLngLats(listOf(points)),
            props,
            id
        )
    }

    private fun setupClickHandler() {
        map.addOnMapClickListener { latLng ->
            val screenPoint = map.projection.toScreenLocation(latLng)
            val features = map.queryRenderedFeatures(screenPoint, "zones-fill")

            if (features.isNotEmpty()) {
                val feature = features[0]
                val name = feature.getStringProperty("name") ?: "Unknown"
                val description = feature.getStringProperty("description") ?: ""

                // Show in info panel
                infoPanel.text = "$name\n$description"
                infoPanel.visibility = android.view.View.VISIBLE

                // Highlight the tapped zone
                highlightZone(feature.getStringProperty("id"))
            } else {
                infoPanel.visibility = android.view.View.GONE
                clearHighlight()
            }

            true
        }
    }

    private fun highlightZone(zoneId: String?) {
        val layer = map.style?.getLayer("zones-fill") as? FillLayer
        layer?.setProperties(
            fillOpacity(
                match(
                    get("id"),
                    literal(0.15f),               // default: dim
                    stop(zoneId ?: "", literal(0.5f))  // highlighted: bright
                )
            )
        )
    }

    private fun clearHighlight() {
        val layer = map.style?.getLayer("zones-fill") as? FillLayer
        layer?.setProperties(fillOpacity(0.25f))
    }

    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)
    }
}

Layout (res/layout/activity_polygon_info.xml):

xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <org.maplibre.android.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:id="@+id/tvInfo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="#DD000000"
        android:padding="16dp"
        android:textColor="#FFFFFF"
        android:textSize="14sp"
        android:visibility="gone" />

</FrameLayout>

Next Steps


Tip: Use queryRenderedFeatures with the specific layer ID ("zones-fill") to only detect polygon taps. Without a layer filter, it would also match road labels, building polygons, and other style features under the tap point.