Graphics overlay (dictionary renderer) 3D

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 dictionary renderer 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 AGSDictionarySymbolStyle object with the "mil2525d" specification type and set the style's draw rule configuration to "ORDERED ANCHOR POINTS".
  2. Create a new AGSDictionaryRenderer object with the dictionary symbol style.
  3. Create an instance of AGSGraphicsOverlay.
  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 AGSGraphic 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

  • AGSDictionaryRenderer
  • AGSDictionarySymbolStyle
  • AGSGraphicsOverlay

About the data

The sample viewer will load MIL-STD-2525D symbol dictionary web style from ArcGIS Online before loading the sample.

Tags

defense, military, situational awareness, tactical, visualization

Sample Code

GraphicsOverlayDictionaryRenderer3DViewController.swift
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
//
// Copyright © 2019 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.
//

import UIKit
import ArcGIS

/// A view controller that manages the interface of the Graphics Overlay
/// (Dictionary Renderer) 3D sample.
class GraphicsOverlayDictionaryRenderer3DViewController: UIViewController {
    /// The scene view managed by the view controller.
    @IBOutlet weak var sceneView: AGSSceneView! {
        didSet {
            sceneView.scene = AGSScene(basemapStyle: .arcGISTopographic)
            sceneView.graphicsOverlays.add(makeGraphicsOverlay())
        }
    }

    /// Creates a graphics overlay configured to display MIL-STD-2525D
    /// symbology.
    /// - Returns: A new `AGSGraphicsOverlay` object.
    func makeGraphicsOverlay() -> AGSGraphicsOverlay {
        let graphicsOverlay = AGSGraphicsOverlay()

        // Create the style from Joint Military Symbology MIL-STD-2525D
        // portal item.
        let dictionarySymbolStyle = AGSDictionarySymbolStyle(
            portalItem: AGSPortalItem(
                portal: .arcGISOnline(withLoginRequired: false),
                itemID: "d815f3bdf6e6452bb8fd153b654c94ca"
            )
        )
        dictionarySymbolStyle.load { [weak self, weak graphicsOverlay] error in
            guard let self = self, let graphicsOverlay = graphicsOverlay else {
                return
            }
            if let error = error {
                self.presentAlert(error: error)
            } else {
                let camera = AGSCamera(lookAt: graphicsOverlay.extent.center, distance: 15_000, heading: 0, pitch: 70, roll: 0)
                self.sceneView.setViewpointCamera(camera)
                // Use Ordered Anchor Points for the symbol style draw rule.
                if let drawRuleConfiguration = dictionarySymbolStyle.configurations.first(where: { $0.name == "model" }) {
                    drawRuleConfiguration.value = "ORDERED ANCHOR POINTS"
                }
                graphicsOverlay.renderer = AGSDictionaryRenderer(dictionarySymbolStyle: dictionarySymbolStyle)
            }
        }

        // Read the messages and add a graphic to the overlay for each messages.
        if let messagesURL = Bundle.main.url(forResource: "Mil2525DMessages", withExtension: "xml") {
            do {
                let messagesData = try Data(contentsOf: messagesURL)
                let messages = try MessageParser().parseMessages(from: messagesData)
                let graphics = messages.map { AGSGraphic(geometry: AGSMultipoint(points: $0.points), symbol: nil, attributes: $0.attributes) }
                graphicsOverlay.graphics.addObjects(from: graphics)
            } catch {
                print("Error reading or decoding messages: \(error)")
            }
        } else {
            preconditionFailure("Missing Mil2525DMessages.xml")
        }

        return graphicsOverlay
    }

    // MARK: UIViewController

    override func viewDidLoad() {
        super.viewDidLoad()

        // Add the source code button item to the right of navigation bar.
        (navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["GraphicsOverlayDictionaryRenderer3DViewController"]
    }
}

struct Message {
    var points: [AGSPoint]
    var attributes: [String: Any]
}

class MessageParser: NSObject {
    struct ControlPoint {
        var x: Double
        var y: Double
    }

    private var controlPoints = [ControlPoint]()
    private var wkid: Int?
    private var attributes = [String: Any]()
    private var contentsOfCurrentElement = ""

    private var parsedMessages = [Message]()

    func didFinishParsingMessage() {
        let spatialReference = AGSSpatialReference(wkid: wkid!)
        let points = controlPoints.map { AGSPoint(x: $0.x, y: $0.y, spatialReference: spatialReference) }
        let message = Message(points: points, attributes: attributes)
        parsedMessages.append(message)

        wkid = nil
        controlPoints.removeAll()
        attributes.removeAll()
    }

    func parseMessages(from data: Data) throws -> [Message] {
        defer { parsedMessages.removeAll() }
        let parser = XMLParser(data: data)
        parser.delegate = self
        let parsingSucceeded = parser.parse()
        if parsingSucceeded {
            return parsedMessages
        } else if let error = parser.parserError {
            throw error
        } else {
            return []
        }
    }
}

extension MessageParser: XMLParserDelegate {
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String] = [:]) {
        contentsOfCurrentElement.removeAll()
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        contentsOfCurrentElement.append(contentsOf: string)
    }

    enum Element: String {
        case controlPoints = "_control_points"
        case message
        case messages
        case wkid = "_wkid"
    }

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        if let element = Element(rawValue: elementName) {
            switch element {
            case .controlPoints:
                controlPoints = contentsOfCurrentElement.split(separator: ";").map { (pair) in
                    let coordinates = pair.split(separator: ",")
                    return ControlPoint(x: Double(coordinates.first!)!, y: Double(coordinates.last!)!)
                }
            case .message:
                didFinishParsingMessage()
            case .messages:
                break
            case .wkid:
                wkid = Int(contentsOfCurrentElement)
            }
        } else {
            attributes[elementName] = contentsOfCurrentElement
        }
        contentsOfCurrentElement.removeAll()
    }
}

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