Identify layer features

View on GitHubSample viewer app

Identify features in all layers in a map.

Image of identify layers

Use case

"Identify layers" operation allows users to tap on a map, returning features at that location across multiple layers. Because some layer types have sublayers, the sample recursively counts results for sublayers within each layer.

How to use the sample

Tap to identify features. A bottom text banner will show all layers with features under the tapped location, as well as the number of features.

How it works

  1. The tapped position is passed to MapView.identifyLayers(...) method.
  2. For each IdentifyLayerResult in the results, features are counted.
    • Note: there is one identify result per layer with matching features; if the feature count is 0, that means a sublayer contains the matching features.

Relevant API

  • IdentifyLayerResult
  • IdentifyLayerResult.sublayerResults
  • LayerContent

Additional information

This sample uses the GeoView-Compose Toolkit module to be able to implement a composable MapView. The GeoView supports two methods of identify: identifyLayer, which identifies features within a specific layer and identifyLayers, which identifies features for all layers in the current view.

Tags

geoview-compose, identify, recursion, recursive, sublayers, toolkit

Sample Code

MapViewModel.ktMapViewModel.ktMainActivity.ktMainScreen.kt
Use dark colors for code blocksCopy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/* Copyright 2023 Esri
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.esri.arcgismaps.sample.identifylayerfeatures.components

import android.app.Application
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import com.arcgismaps.data.ServiceFeatureTable
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.ArcGISMapImageLayer
import com.arcgismaps.mapping.layers.FeatureLayer.Companion.createWithFeatureTable
import com.arcgismaps.mapping.view.IdentifyLayerResult
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import com.esri.arcgismaps.sample.identifylayerfeatures.R
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

class MapViewModel(
    application: Application,
    private val sampleCoroutineScope: CoroutineScope
) : AndroidViewModel(application) {

    // create a map using the topographic basemap style
    val map: ArcGISMap = ArcGISMap(BasemapStyle.ArcGISTopographic)

    // create a mapViewProxy that will be used to identify features in the MapView
    // should also be passed to the composable MapView this mapViewProxy is associated with
    val mapViewProxy = MapViewProxy()

    // create a ViewModel to handle dialog interactions
    val messageDialogVM: MessageDialogViewModel = MessageDialogViewModel()

    // string text to display the identify layer results
    val bottomTextBanner = mutableStateOf("Tap on the map to identify feature layers")

    init {
        // create a feature layer of damaged property data
        val featureTable = ServiceFeatureTable(application.getString(R.string.damage_assessment))
        val featureLayer = createWithFeatureTable(featureTable)

        // create a layer with world cities data
        val mapImageLayer = ArcGISMapImageLayer(application.getString(R.string.world_cities))
        sampleCoroutineScope.launch {
            mapImageLayer.load().onSuccess {
                mapImageLayer.apply {
                    subLayerContents.value[1].isVisible = false
                    subLayerContents.value[2].isVisible = false
                }
            }.onFailure { error ->
                // show the message dialog and pass the error message to be displayed in the dialog
                messageDialogVM.showMessageDialog(error.message.toString(), error.cause.toString())
            }
        }

        // add the world cities layer with and the damaged properties feature layer
        map.apply {
            // set initial Viewpoint to North America
            initialViewpoint = Viewpoint(39.8, -98.6, 5e7)
            operationalLayers.addAll(listOf(mapImageLayer, featureLayer))
        }

    }

    /**
     * Identify the feature layer results and display the resulting information
     */
    private fun handleIdentifyResult(result: Result<List<IdentifyLayerResult>>) {
        sampleCoroutineScope.launch {
            result.onSuccess { identifyResultList ->
                val message = StringBuilder()
                var totalCount = 0
                identifyResultList.forEach { identifyLayerResult ->
                    val geoElementsCount = geoElementsCountFromResult(identifyLayerResult)
                    val layerName = identifyLayerResult.layerContent.name
                    message.append(layerName).append(": ").append(geoElementsCount)

                    // add new line character if not the final element in array
                    if (identifyLayerResult != identifyResultList[identifyResultList.size - 1]) {
                        message.append("\n")
                    }
                    totalCount += geoElementsCount
                }
                // if any elements were found show the results, else notify user that no elements were found
                if (totalCount > 0) {
                    bottomTextBanner.value = "Number of elements found:\n${message}"
                } else {
                   bottomTextBanner.value = "Number of elements found: N/A"
                    messageDialogVM.showMessageDialog(
                        title = "No element found",
                        description = "Tap an area on the map with visible features"
                    )
                }
            }.onFailure { error ->
                messageDialogVM.showMessageDialog(
                    title = "Error identifying results: ${error.message.toString()}",
                    description = error.cause.toString()
                )
            }
        }
    }

    /**
     * Gets a count of the GeoElements in the passed result layer.
     * This method recursively calls itself to descend into sublayers and count their results.
     * @param result from a single layer.
     * @return the total count of GeoElements.
     */
    private fun geoElementsCountFromResult(result: IdentifyLayerResult): Int {
        var subLayerGeoElementCount = 0
        for (sublayerResult in result.sublayerResults) {
            // recursively call this function to accumulate elements from all sublayers
            subLayerGeoElementCount += geoElementsCountFromResult(sublayerResult)
        }
        return subLayerGeoElementCount + result.geoElements.size
    }

    /**
     * Identifies the tapped screen coordinate in the provided [singleTapConfirmedEvent]
     */
    fun identify(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
        sampleCoroutineScope.launch {
            // identify the layers on the tapped coordinate
            val identifyResult = mapViewProxy.identifyLayers(
                screenCoordinate = singleTapConfirmedEvent.screenCoordinate,
                tolerance = 12.dp,
                maximumResults = 10
            )
            // use the layer result to display feature information
            handleIdentifyResult(identifyResult)
        }
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.