Skip to main content

Highlight buildings

Contents

The Mobile Maps SDK examples require that you first complete the initial project setup.

This example provides a reference of how to highlight buildings on click in a Jetpack Compose environment. It highlights a 2D building when the user clicks on it, similar to the Highlight Buildings code example for Java and Kotlin.

import android.content.Context
import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import com.trimblemaps.account.LicensedFeature
import com.trimblemaps.account.TrimbleMapsAccountManager
import com.trimblemaps.account.models.TrimbleMapsAccount
import com.trimblemaps.geojson.Feature
import com.trimblemaps.geojson.FeatureCollection
import com.trimblemaps.mapsdk.TrimbleMaps
import com.trimblemaps.mapsdk.camera.CameraPosition
import com.trimblemaps.mapsdk.geometry.LatLng
import com.trimblemaps.mapsdk.maps.MapView
import com.trimblemaps.mapsdk.maps.OnMapReadyCallback
import com.trimblemaps.mapsdk.maps.Style
import com.trimblemaps.mapsdk.maps.TrimbleMapsMap
import com.trimblemaps.mapsdk.style.layers.LineLayer
import com.trimblemaps.mapsdk.style.layers.PropertyFactory.lineColor
import com.trimblemaps.mapsdk.style.layers.PropertyFactory.lineOpacity
import com.trimblemaps.mapsdk.style.layers.PropertyFactory.lineWidth
import com.trimblemaps.mapsdk.style.sources.GeoJsonSource

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            // IMPORTANT: Use your real API key
            HighlightBuildingsScreen(apiKey = "YOUR-API-KEY")
        }
    }
}

@Composable
fun HighlightBuildingsScreen(apiKey: String) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    // Gate rendering the MapView until account initialization completes
    var isInitialized by remember { mutableStateOf(false) }
    var trimbleMap by remember { mutableStateOf<TrimbleMapsMap?>(null) }

    val highlights: List<Feature> = listOf()
    val highlightsSrcLayer = "highlighted_buildings"

    // Initialize the Trimble account/session once.
    LaunchedEffect(apiKey) {
        // Build account with your API key and the Maps SDK licensed feature
        val account = TrimbleMapsAccount.builder()
            .apiKey(apiKey)
            .addLicensedFeature(LicensedFeature.MAPS_SDK)
            .build()

        // Initialize and block until ready
        TrimbleMapsAccountManager.initialize(account)
        TrimbleMapsAccountManager.awaitInitialization()

        // Obtain the Trimble Maps instance BEFORE creating/using MapView
        TrimbleMaps.getInstance(context)

        isInitialized = true
    }

    // Remember updated map reference for click listener
    val updatedMap = rememberUpdatedState(trimbleMap)

    if (isInitialized) {
        val mapView = rememberMapViewWithLifecycle(context, lifecycleOwner.lifecycle)

        AndroidView(
            modifier = Modifier.fillMaxSize(),
            factory = { mapView },
            update = { view ->
                // Request a TrimbleMapsMap asynchronously
                view.getMapAsync(OnMapReadyCallback { map ->
                    trimbleMap = map

                    // Set style with source and layer for highlighted buildings
                    // Building outlines will be displayed in yellow
                    map.setStyle(
                        Style.Builder()
                            .fromUri(Style.TrimbleMobileStyle.MOBILE_NIGHT)
                            .withSource(
                                GeoJsonSource(
                                    highlightsSrcLayer,
                                    FeatureCollection.fromFeatures(highlights)
                                )
                            )
                            .withLayer(
                                LineLayer(highlightsSrcLayer, highlightsSrcLayer).withProperties(
                                    lineWidth(4f),
                                    lineColor(Color.YELLOW),
                                    lineOpacity(.8f)
                                )
                            )
                    )

                    // Set camera position
                    val position = CameraPosition.Builder()
                        .target(LatLng(39.96012475826224, -75.16184676002608))
                        .zoom(17.0)
                        .build()
                    map.cameraPosition = position

                    // Add click listener to highlight buildings
                    map.addOnMapClickListener { clickedLatLng ->
                        updatedMap.value?.let { currentMap ->
                            // Convert our LatLng to a pixel
                            val pixel = currentMap.projection?.toScreenLocation(clickedLatLng)

                            // Find any features from the "building_2d" layer that our point intersects
                            val features = pixel?.let {
                                currentMap.queryRenderedFeatures(it, "building_2d")
                            }

                            // Update/Replace the source of data with these new found features
                            features?.let {
                                currentMap.style?.getSourceAs<GeoJsonSource>(highlightsSrcLayer)
                                    ?.setGeoJson(FeatureCollection.fromFeatures(it))
                            }
                        }
                        true
                    }
                })
            }
        )
    }
}

/**
 * Handle the lifecycle events from the map view, onDestroy etc.
 */
@Composable
private fun rememberMapViewWithLifecycle(
    context: Context,
    lifecycle: Lifecycle
): MapView {
    // Create once per composition
    val mapView = remember {
        MapView(context)
    }

    // Hook the MapView into the lifecycle
    DisposableEffect(lifecycle, mapView) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_CREATE -> { /* no-op (Trimble handled) */ }
                Lifecycle.Event.ON_START -> mapView.onStart()
                Lifecycle.Event.ON_RESUME -> mapView.onResume()
                Lifecycle.Event.ON_PAUSE -> mapView.onPause()
                Lifecycle.Event.ON_STOP -> mapView.onStop()
                Lifecycle.Event.ON_DESTROY -> mapView.onDestroy()
                else -> Unit
            }
        }
        lifecycle.addObserver(observer)

        onDispose {
            lifecycle.removeObserver(observer)
            try {
                mapView.onDestroy()
            } catch (_: Throwable) {
            }
        }
    }

    return mapView
}
Last updated November 26, 2025.
Contents