Skip to content

Perform GeoView operations

With the ArcGIS Maps SDK for Kotlin, you can perform advanced operations on the composable MapView and SceneView components using proxy classes like MapViewProxy and SceneViewProxy. The proxy classes provides access to methods and properties that aren't suitable for use in a composable function's initializer or a modifier. They enable operations such as:

  • Identifying features on the map.
  • Setting the viewpoint with animation.
  • Converting coordinates between the map and screen.
  • Exporting a snapshot of the view's image.

To use a proxy, you must first create a one-to-one relationship between it and the composable view. This is done by passing a proxy instance to the composable MapView or SceneView function.

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
        MapView(
            arcGISMap = map,
            graphicsOverlays = listOf(mapViewModel.graphicsOverlay),
            mapViewProxy = mapViewModel.mapViewProxy
        )

Note: The example above shows a MapView that uses a MapViewProxy. Similarly, you would use a SceneViewProxy with a SceneView.

Identify features

The following example demonstrates how to use the MapViewProxy to identify features on a map. When a user taps the screen, the identifyLayers method is called to retrieve features at that location and display their associated popups or attributes.

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
152
153
154
155
156
157
158
159
160
161
162
163
164
// In the ViewModel:
class MapViewModel(application: Application) : AndroidViewModel(application) {
    // ....

    // Proxy object used to help identify the feature at the tapped coordinate
    val mapViewProxy = MapViewProxy()

    fun identifyFeatureAt(
        screenCoordinate: ScreenCoordinate,
        onFeatureSelected: (ArcGISFeature) -> Unit
    ) {
        viewModelScope.launch {
            // Identify the feature tapped at the given screen coords
            val identifyLayerResults = mapViewProxy.identifyLayers(
                screenCoordinate = screenCoordinate,
                tolerance = 5.dp,
                returnPopupsOnly = false,
                maximumResults = 1
            ).getOrThrow()

            // Identify the layer
            val identifyLayerResult = identifyLayerResults.firstOrNull()

            // Identify the feature
            val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
            if (foundFeature != null) {

                // Report the selected feature to UI
                onFeatureSelected(foundFeature)
            }

        }
    }
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
152
153
154
155
156
157
158
159
160
161
162
163
164
// In the Composable:
@Composable
fun MainScreen(modifier: Modifier, viewmodel: MapViewModel = viewModel()) {
    var selectedFeature by remember { mutableStateOf<ArcGISFeature?>(null) }
    MapView(
        modifier = modifier.fillMaxSize(),
        arcGISMap = viewmodel.map,
        mapViewProxy = viewmodel.mapViewProxy,
        onSingleTapConfirmed = { tapEvent ->
            viewmodel.identifyFeatureAt(
                screenCoordinate = tapEvent.screenCoordinate,
                onFeatureSelected = { feature ->
                    selectedFeature = feature
                }
            )
        }
    ) {

    }
}

Set a Viewpoint

You can use the MapViewProxy to programmatically change the viewpoint of the map. This is useful for things like zooming to a specific feature or panning to a new location.

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
152
153
154
155
156
157
158
159
160
161
162
163
164
            // Identify the feature
            val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
            if (foundFeature != null) {

                // Animate the viewpoint to center on the feature.
                foundFeature.geometry?.let { centerPoint ->
                    mapViewProxy.setViewpointAnimated(
                        viewpoint = Viewpoint(centerPoint),
                        duration = 300.milliseconds,
                        curve = AnimationCurve.EaseOutSine
                    )
                }

                // Report the selected feature to UI
                onFeatureSelected(foundFeature)
            }

Display a Callout on a GeoElement

When a user taps on a map and a feature is identified, you often want to display a Popup, or Callout, to show more information. You can use a mutableStateOf property in your ViewModel to hold the identified GeoElement. When this property is updated, Compose automatically recomposes the UI and displays the Callout.

The following example combines the above patterns to create a full workflow.

  1. A user taps the map, triggering the onSingleTapConfirmed event handler.
  2. The ViewModel's identifyFeatureAt function is called, which uses the MapViewProxy to find a feature at the tapped location.
  3. The setViewpointAnimated function is used to gracefully move the map's viewpoint to the identified feature.
  4. The ViewModel updates its selectedFeature state.
  5. Compose detects the state change and displays a Callout at the feature's location, using its attributes to populate the content.

This seamless pattern demonstrates how MapViewProxy and composable state work together to build a responsive and interactive user experience.

Perform Geoview Operations Demo
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
152
153
154
155
156
157
158
159
160
161
162
163
164
// In the Composable:
@Composable
fun MainScreen(modifier: Modifier, viewmodel: MapViewModel = viewModel()) {
    var selectedFeature by remember { mutableStateOf<ArcGISFeature?>(null) }
    MapView(
        modifier = modifier.fillMaxSize(),
        arcGISMap = viewmodel.map,
        mapViewProxy = viewmodel.mapViewProxy,
        onSingleTapConfirmed = { tapEvent ->
            viewmodel.identifyFeatureAt(
                screenCoordinate = tapEvent.screenCoordinate,
                onFeatureSelected = { feature ->
                    selectedFeature = feature
                }
            )
        }
    ) {

        selectedFeature?.let { selectedFeature ->
            Callout(geoElement = selectedFeature) {
                CalloutContent(
                    selectedElementAttributes = viewmodel.filterAttributes(
                        attributes = selectedFeature.attributes
                    )
                )
            }
        }

    }
}

@Composable
fun CalloutContent(selectedElementAttributes: Map<String, Any?>) {
    LazyColumn(
        modifier = Modifier.widthIn(max = 250.dp),
        contentPadding = PaddingValues(8.dp)
    ) {
        selectedElementAttributes.forEach { attribute ->
            item {
                Row(
                    modifier = Modifier.fillMaxSize(),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.Top
                ) {
                    Text(
                        text = "${attribute.key}:",
                        fontStyle = FontStyle.Italic,
                        style = MaterialTheme.typography.labelMedium
                    )
                    Spacer(modifier = Modifier.width(20.dp))
                    Text(
                        text = "${attribute.value}",
                        style = MaterialTheme.typography.bodySmall,
                        textAlign = TextAlign.End
                    )
                }
            }
        }
    }
}

// In the ViewModel:
class MapViewModel(application: Application) : AndroidViewModel(application) {
    // ....

    // Feature table of street trees in Portland.
    private val featureTable = ServiceFeatureTable(
        uri = "https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/Trees_of_Portland/FeatureServer/0"
    )

    val map = ArcGISMap(BasemapStyle.ArcGISTopographic).apply {
        // Set a viewpoint to downtown Portland, OR.
        initialViewpoint = Viewpoint(latitude = 45.5266, longitude = -122.6219, scale = 6000.0)
        // Create a feature layer from the feature table and add it to the map.
        operationalLayers.add(element = FeatureLayer.createWithFeatureTable(featureTable))
    }

    // Proxy object used to help identify the feature at the tapped coordinate
    val mapViewProxy = MapViewProxy()

    fun identifyFeatureAt(
        screenCoordinate: ScreenCoordinate,
        onFeatureSelected: (ArcGISFeature) -> Unit
    ) {
        viewModelScope.launch {
            // Identify the feature tapped at the given screen coords
            val identifyLayerResults = mapViewProxy.identifyLayers(
                screenCoordinate = screenCoordinate,
                tolerance = 5.dp,
                returnPopupsOnly = false,
                maximumResults = 1
            ).getOrThrow()

            // Identify the layer
            val identifyLayerResult = identifyLayerResults.firstOrNull()

            // Identify the feature
            val foundFeature = identifyLayerResult?.geoElements?.firstOrNull() as? ArcGISFeature
            if (foundFeature != null) {

                // Animate the viewpoint to center on the feature.
                foundFeature.geometry?.let { centerPoint ->
                    mapViewProxy.setViewpointAnimated(
                        viewpoint = Viewpoint(centerPoint),
                        duration = 300.milliseconds,
                        curve = AnimationCurve.EaseOutSine
                    )
                }

                // Report the selected feature to UI
                onFeatureSelected(foundFeature)
            }

        }
    }

    // Helper function for simple Callout content
    fun filterAttributes(attributes: Map<String, Any?>): Map<String, Any?> {
        // Filter undesired feature attributes like, empty or null values and GlobalIDs.
        return attributes
            .filter { attribute -> attribute.value != null }
            .filter { attribute -> attribute.value.toString().trim().isNotEmpty() }
            .filter { attribute -> !attribute.key.contains("GlobalID") }
    }
}

Note:

  • See the tutorial guide Display device location to learn how to display the current device location on a map or scene.
  • See the sample app Identify layer features to learn how to identify features in all layers in a map.
  • See the sample app Show popup to learn how to show a predefined popup from a web map.
  • See the sample app Show callout to learn how to show a callout with the latitude and longitude of user-tapped points.

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