Skip to content
View on GitHubSample viewer app

Create graphics from an XML file with key-value pairs for each graphic, and display the military symbols using a MIL-STD-2525D web style in 3D.

Image of apply dictionary renderer to graphics overlay

Use case

Use a dictionary renderer on a graphics overlay to display more transient data, such as military messages coming through a local tactical network.

How to use the sample

Run the sample and view the military symbols on the map.

How it works

  1. Create a new DictionarySymbolStyle object with the "mil2525d" specification type and set the style's draw rule configuration to "ORDERED ANCHOR POINTS".
  2. Create a new DictionaryRenderer object with the dictionary symbol style.
  3. Create an instance of GraphicsOverlay.
  4. Set the dictionary renderer to the graphics overlay.
  5. Parse through the local XML file creating a map of key/value pairs for each block of attributes.
  6. Create an instance of Graphic for each attribute.
  7. Use the _wkid key to get the geometry's spatial reference.
  8. Use the _control_points key to get the geometry's shape.
  9. Add the graphic to the graphics overlay.

Relevant API

  • DictionaryRenderer
  • DictionarySymbolStyle
  • GraphicsOverlay

About the data

The sample viewer will load MIL-STD-2525D symbol dictionary web style from ArcGIS Online before loading the sample. This ArcGIS Web Style is for use to build custom applications that incorporate the MIL-STD-2525D symbol dictionary. This style supports a configuration for modeling locations as ordered anchor points or full geometries.

A local XML file containing messages with MIL-STD-2525D fields for military symbology (MIL-STD-2525D Messages 100.13.0) is also used. This is downloaded from ArcGIS Online automatically.

Tags

defense, military, situational awareness, tactical, visualization

Sample Code

ApplyDictionaryRendererToGraphicsOverlayViewModel.ktApplyDictionaryRendererToGraphicsOverlayViewModel.ktMainActivity.ktDownloadActivity.ktApplyDictionaryRendererToGraphicsOverlayScreen.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/* Copyright 2025 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.applydictionaryrenderertographicsoverlay.components

import android.app.Application
import android.util.Xml
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.arcgismaps.geometry.Multipoint
import com.arcgismaps.geometry.Point
import com.arcgismaps.geometry.SpatialReference
import com.arcgismaps.mapping.ArcGISScene
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.PortalItem
import com.arcgismaps.mapping.symbology.DictionaryRenderer
import com.arcgismaps.mapping.symbology.DictionarySymbolStyle
import com.arcgismaps.mapping.view.Camera
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.portal.Portal
import com.arcgismaps.toolkit.geoviewcompose.SceneViewProxy
import com.esri.arcgismaps.sample.applydictionaryrenderertographicsoverlay.R
import com.esri.arcgismaps.sample.sampleslib.components.MessageDialogViewModel
import kotlinx.coroutines.launch
import org.xmlpull.v1.XmlPullParser
import java.io.File

/**
 * ViewModel for the "Apply dictionary renderer to graphics overlay" sample.
 */
class ApplyDictionaryRendererToGraphicsOverlayViewModel(private val app: Application) : AndroidViewModel(app) {

    // The scene shown in the SceneView composable
    val arcGISScene = ArcGISScene(basemapStyle = BasemapStyle.ArcGISTopographic)

    // Graphics overlay that will hold the message graphics
    val graphicsOverlay: GraphicsOverlay = GraphicsOverlay()

    // SceneViewProxy to enable programmatic viewpoint changes
    val sceneViewProxy = SceneViewProxy()

    // Used to display error messages
    val messageDialogVM = MessageDialogViewModel()

    // Provision path where downloaded sample assets will be placed by the downloader activity
    private val provisionPath: String by lazy {
        app.getExternalFilesDir(null)?.path.toString() + File.separator + app.getString(R.string.apply_dictionary_renderer_to_graphics_overlay_app_name)
    }

    init {
        viewModelScope.launch {
            // Load the scene first
            arcGISScene.load().onFailure { throwable ->
                messageDialogVM.showMessageDialog(throwable)
            }

            // Create and apply dictionary renderer from a web style
            val dictionaryRenderer = createMil2525dDictionaryRenderer().getOrElse {
                messageDialogVM.showMessageDialog(it)
                return@launch
            }

            // Create the point graphics from a local XML file
            val pointGraphics = makeMessageGraphics().getOrElse {
                messageDialogVM.showMessageDialog(it)
                return@launch
            }


            // Set the graphics overlay to use the dictionary renderer and add graphics
            graphicsOverlay.apply {
                renderer = dictionaryRenderer
                graphics.addAll(pointGraphics)
            }

            // Sets the camera to look a the graphics in the graphics overlay
            graphicsOverlay.extent?.let { extent ->
                sceneViewProxy.setViewpointCamera(
                    camera = Camera(
                        lookAtPoint = extent.center,
                        distance = 15000.0,
                        heading = 0.0,
                        pitch = 70.0,
                        roll = 0.0
                    )
                )
            }
        }
    }

    /**
     * Create and load a [DictionarySymbolStyle] from a web style and use it to create a [DictionaryRenderer].
     */
    private suspend fun createMil2525dDictionaryRenderer(): Result<DictionaryRenderer> {
        // Creates a dictionary symbol style from a dictionary style portal item.
        val portalItem = PortalItem(
            portal = Portal.arcGISOnline(Portal.Connection.Anonymous),
            itemId = "d815f3bdf6e6452bb8fd153b654c94ca"
        )

        val dictionarySymbolStyle = DictionarySymbolStyle(portalItem = portalItem)

        return dictionarySymbolStyle.load().mapCatching {
            // Uses the "Ordered Anchor Points" for the symbol style draw rule.
            // Get the model configuration from the style's list of configurations.
            val modelConfiguration = dictionarySymbolStyle.configurations.firstOrNull {
                it.name.equals("model", ignoreCase = true)
            }
            if (modelConfiguration != null) {
                // Set the draw rule of the style to "ORDERED ANCHOR POINTS".
                modelConfiguration.value = "ORDERED ANCHOR POINTS"
            }
            DictionaryRenderer(dictionarySymbolStyle = dictionarySymbolStyle)
        }
    }

    /**
     * Create point graphics from a local XML file containing `MIL-STD-2525D` message data.
     */
    private fun makeMessageGraphics(): Result<List<Graphic>> {
        return runCatching {
            val xmlFile = File(provisionPath, "Mil2525DMessages.xml")
            val messageXml = xmlFile.readText()
            val messages = MessageXmlParser().parse(messageXml)

            val graphics = messages.map { message ->
                val wkid = message.wkid
                val controlPoints = message.controlPoints
                if (wkid == null || controlPoints.isEmpty()) {
                    throw IllegalArgumentException("Invalid message: missing wkid or control points")
                }
                val spatialReference = SpatialReference(wkid = wkid)
                val points = controlPoints.map { (x, y) ->
                    Point(x = x, y = y, spatialReference = spatialReference)
                }
                Graphic(geometry = Multipoint(points), attributes = message.other)
            }
            graphics
        }
    }
}

/**
 * Simple XML parser for the `MIL-STD-2525D` message XML file.
 * This is a basic implementation and does not cover all edge cases.
 */
class MessageXmlParser {
    private val messagesTag = "messages"
    private val messageTag = "message"
    private val controlPointsTag = "_control_points"
    private val wkidTag = "_wkid"

    /**
     * Parses the provided XML string and returns a list of Message objects.
     */
    fun parse(xml: String): List<Message> {
        val messages = mutableListOf<Message>()

        val parser = Xml.newPullParser().apply {
            setInput(xml.reader())
        }

        var eventType = parser.eventType
        var currentControlPoints: List<Pair<Double, Double>> = emptyList()
        var currentWkid: Int? = null
        var currentAttrs = mutableMapOf<String, String?>()

        while (eventType != XmlPullParser.END_DOCUMENT) {
            when (eventType) {
                XmlPullParser.START_TAG -> {
                    when (val tagName = parser.name) {
                        controlPointsTag -> {
                            val controlPointsText = parser.nextText()
                            currentControlPoints = controlPointsText.split(";")
                                .mapNotNull {
                                    val coords = it.split(",")
                                    if (coords.size == 2) {
                                        val x = coords[0].toDoubleOrNull()
                                        val y = coords[1].toDoubleOrNull()
                                        if (x != null && y != null) Pair(x, y) else null
                                    } else null
                                }
                        }

                        wkidTag -> {
                            currentWkid = parser.nextText().toIntOrNull()
                        }

                        messageTag, messagesTag -> { /* ignore container tags */
                        }

                        else -> {
                            if (tagName != null) {
                                currentAttrs[tagName] = parser.nextText()
                            }
                        }
                    }
                }

                XmlPullParser.END_TAG -> {
                    if (parser.name == messageTag) {
                        messages.add(
                            Message(
                                controlPoints = currentControlPoints,
                                wkid = currentWkid,
                                other = currentAttrs
                            )
                        )
                        currentControlPoints = emptyList()
                        currentWkid = null
                        currentAttrs = mutableMapOf()
                    }
                }
            }

            eventType = parser.next()
        }

        return messages
    }
}

data class Message(
    val controlPoints: List<Pair<Double, Double>>,
    val wkid: Int?,
    val other: Map<String, String?> = emptyMap()
)

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