Render multilayer symbols

View on GitHub

Show different kinds of multilayer symbols on a map similar to some pre-defined 2D simple symbol styles.

Image of render multilayer symbols

Use case

Allows you to customize a graphic with a multilayer symbol. For example, you may want more customizable symbols than the one that is provided with the API to display a unique representation of a landmark.

How to use the sample

The sample loads multilayer symbols for points, polylines, and polygons.

How it works

  1. Create multilayer symbols for each predefined 2D simple symbol style.
    • For multilayer point symbols, use MultilayerPointSymbol(symbolLayers:referenceProperties:).
    • For multilayer polyline symbols, use MultilayerPolylineSymbol(symbolLayers:referenceProperties:).
    • For multilayer polygon symbols, use MultiLayerPolygonSymbol(symbolLayers:referenceProperties:).
  2. Create graphics by passing in a geometry and the associated symbol.
  3. Add graphics to the graphics overlay with graphicsOverlay.addGraphics(_:)

Relevant API

  • Graphic
  • GraphicsOverlay
  • MultiLayerPointSymbol
  • MultiLayerPolygonSymbol
  • MultiLayerPolylineSymbol
  • PictureMarkerSymbolLayer
  • SolidFillSymbolLayer
  • SolidStrokeSymbolLayer
  • VectorMarkerSymbolLayer

About the data

The campsite picture marker symbol is constructed from a URL.

Tags

graphic, marker, multilayer, picture, symbol

Sample Code

RenderMultilayerSymbolsView.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
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
// 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
//
//   https://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 ArcGIS
import SwiftUI

struct RenderMultilayerSymbolsView: View {
    /// A map with a light gray basemap.
    @State private var map: Map = {
        let map = Map(basemapStyle: .arcGISLightGray)
        map.initialViewpoint = Viewpoint(boundingGeometry: Point(x: -713_800, y: 0, spatialReference: .webMercator))
        return map
    }()

    /// The graphics overlay containing the multilayer graphics and associated text graphics.
    @State private var graphicsOverlay: GraphicsOverlay = {
        let graphicsOverlay = GraphicsOverlay()

        // Create graphics and add them to the graphics overlay.
        graphicsOverlay.addGraphics(makeMultilayerPointSimpleMarkerGraphics())
        graphicsOverlay.addGraphics(makeMultilayerPointPictureMarkerGraphics())
        graphicsOverlay.addGraphics(makeMultilayerPolylineGraphics())
        graphicsOverlay.addGraphics(makeMultilayerPolygonGraphics())
        graphicsOverlay.addGraphics(makeComplexMultilayerSymbolGraphics())

        return graphicsOverlay
    }()

    /// The offset used to keep a consistent distance between symbols in the same column.
    private static let offsetBetweenSymbols = 20.0

    var body: some View {
        MapView(map: map, graphicsOverlays: [graphicsOverlay])
    }
}

private extension RenderMultilayerSymbolsView {
    // MARK: Methods

    /// Creates a text symbol with the text to be displayed on the map.
    /// - Parameter text: The text used to create the text symbol.
    /// - Returns: A new `TextSymbol` object.
    private static func makeTextSymbol(text: String) -> TextSymbol {
        // Create text symbol with a white background.
        let textSymbol = TextSymbol(text: text, color: .black, size: 10)
        textSymbol.backgroundColor = .white
        return textSymbol
    }

    // MARK: MultilayerPoint Simple Markers

    /// Creates the multilayer point simple marker graphics.
    /// - Returns: The new `Graphic` objects to display on the map.
    private static func makeMultilayerPointSimpleMarkerGraphics() -> [Graphic] {
        // Create a text graphic.
        var graphics = [
            Graphic(
                geometry: Point(x: -150, y: 50, spatialReference: .wgs84),
                symbol: makeTextSymbol(text: "MultilayerPoint\nSimple Markers")
            )
        ]

        // Create a red diamond graphic from JSON and a multilayer polygon symbol.
        let redFillLayer = SolidFillSymbolLayer(color: .red)
        let polygonSymbol = MultilayerPolygonSymbol(symbolLayers: [redFillLayer])
        if let diamondGeometry = try? Geometry.fromJSON(.diamondGeometryJSON) {
            graphics.append(makeGraphicWithVectorMarkerSymbolElement(
                symbol: polygonSymbol,
                geometry: diamondGeometry,
                offset: 0
            ))
        }

        // Create a triangle graphic from JSON and a multilayer polygon symbol.
        if let triangleGeometry = try? Geometry.fromJSON(.triangleGeometryJSON) {
            graphics.append(makeGraphicWithVectorMarkerSymbolElement(
                symbol: polygonSymbol,
                geometry: triangleGeometry,
                offset: offsetBetweenSymbols
            ))
        }

        // Create a cross graphic from JSON and a multilayer polyline symbol.
        let redStrokeLayer = SolidStrokeSymbolLayer(width: 1, color: .red)
        let polylineSymbol = MultilayerPolylineSymbol(symbolLayers: [redStrokeLayer])
        if let crossGeometry = try? Geometry.fromJSON(.crossGeometryJSON) {
            graphics.append(makeGraphicWithVectorMarkerSymbolElement(
                symbol: polylineSymbol,
                geometry: crossGeometry,
                offset: offsetBetweenSymbols * 2
            ))
        }

        return graphics
    }

    /// Creates a graphic of multilayer point symbol using a vector marker symbol element.
    /// - Parameters:
    ///   - symbol: The symbol to create the graphic of.
    ///   - geometry: The geometry used to make the vector marker symbol element.
    ///   - offset: The offset used to keep a consistent distance between symbols in the same column.
    /// - Returns: A new `Graphic` object of the multilayer point symbol.
    private static func makeGraphicWithVectorMarkerSymbolElement(
        symbol: MultilayerSymbol,
        geometry: Geometry,
        offset: Double
    ) -> Graphic {
        // Create a vector element using the passed symbol and geometry.
        let vectorElement = VectorMarkerSymbolElement(geometry: geometry, multilayerSymbol: symbol)

        // Create a vector layer using the vector element.
        let vectorLayer = VectorMarkerSymbolLayer(vectorMarkerSymbolElements: [vectorElement])

        // Create a point symbol using the vector layer.
        let pointSymbol = MultilayerPointSymbol(symbolLayers: [vectorLayer])

        // Create a graphic of the multilayer point symbol.
        return Graphic(geometry: Point(x: -150, y: 20 - offset, spatialReference: .wgs84), symbol: pointSymbol)
    }

    // MARK: MultilayerPoint Picture Markers

    /// Creates the multilayer point picture marker graphics .
    /// - Returns: The new `Graphic` objects to display on the map.
    private static func makeMultilayerPointPictureMarkerGraphics() -> [Graphic] {
        // Create a text graphic.
        var graphics = [
            Graphic(
                geometry: Point(x: -80, y: 50, spatialReference: .wgs84),
                symbol: makeTextSymbol(text: "MultilayerPoint\nPicture Markers")
            )
        ]

        // Create a campsite graphic using a URL.
        let campsiteLayer = PictureMarkerSymbolLayer(url: .campsiteImage)
        graphics.append(
            makeGraphicFromPictureMarkerSymbol(layer: campsiteLayer, offset: 0)
        )

        // Create a pin graphic using an image in the project assets.
        let pinLayer = PictureMarkerSymbolLayer(image: .pinBlueStar)
        graphics.append(
            makeGraphicFromPictureMarkerSymbol(layer: pinLayer, offset: 40)
        )

        return graphics
    }

    /// Creates a graphic from a picture marker symbol layer using a multilayer point symbol.
    /// - Parameters:
    ///   - layer: The layer used to create the multilayer point symbol.
    ///   - offset: The offset used to keep a consistent distance between symbols in the same column.
    /// - Returns: A new `Graphic` object of the multilayer point symbol.
    private static func makeGraphicFromPictureMarkerSymbol(layer: PictureMarkerSymbolLayer, offset: Double) -> Graphic {
        // Create a multilayer point symbol using the picture marker symbol layer.
        layer.size = 40
        let symbol = MultilayerPointSymbol(symbolLayers: [layer])

        // Create the location for symbol using a point.
        let symbolPoint = Point(x: -80, y: 20 - offset, spatialReference: .wgs84)

        // Create the graphic for multilayer point symbol.
        return Graphic(geometry: symbolPoint, symbol: symbol)
    }

    // MARK: Multilayer Polylines

    /// Creates the multilayer polyline graphics.
    /// - Returns: The new `Graphic` objects to display on the map.
    private static func makeMultilayerPolylineGraphics() -> [Graphic] {
        // Create a text graphic.
        var graphics = [
            Graphic(
                geometry: Point(x: 0, y: 50, spatialReference: .wgs84),
                symbol: makeTextSymbol(text: "Multilayer\nPolylines")
            )
        ]

        // Create the polyline graphics.
        graphics.append(contentsOf: [
            // Dash, dot, dot.
            makeMultilayerPolylineSymbolGraphic(dashSpacing: [4, 6, 0.5, 6, 0.5, 6], offset: 0),
            // Dashes.
            makeMultilayerPolylineSymbolGraphic(dashSpacing: [4, 6], offset: offsetBetweenSymbols),
            // Dash, dot.
            makeMultilayerPolylineSymbolGraphic(dashSpacing: [7, 9, 0.5, 9], offset: offsetBetweenSymbols * 2)
        ])

        return graphics
    }

    /// Creates a graphic of a dashed multilayer polyline symbol.
    /// - Parameters:
    ///   - dashSpacing: The pattern of spaces and dashes used to create a dash effect.
    ///   - offset: The offset used to keep a consistent distance between symbols in the same column.
    /// - Returns: A new `Graphic` object of the multilayer polyline symbol.
    private static func makeMultilayerPolylineSymbolGraphic(dashSpacing: [Double], offset: Double) -> Graphic {
        // Create a dash effect with the passed dash spacing.
        let dashEffect = DashGeometricEffect(dashTemplate: dashSpacing)

        // Create a solid stroke symbol layer for the line symbol.
        let strokeLayer = SolidStrokeSymbolLayer(width: 3, color: .red, geometricEffects: [dashEffect])
        strokeLayer.capStyle = .round

        // Create a multilayer polyline symbol from the stroke layer.
        let lineSymbol = MultilayerPolylineSymbol(symbolLayers: [strokeLayer])

        // Create a polyline for the graphic.
        let polyline = Polyline(
            points: [
                Point(x: -30, y: 20 - offset),
                Point(x: 30, y: 20 - offset)
            ],
            spatialReference: .wgs84
        )

        // Create a graphic of the multilayer polyline symbol.
        return Graphic(geometry: polyline, symbol: lineSymbol)
    }

    // MARK: Multilayer Polygons

    /// Creates the multilayer polygon graphics.
    /// - Returns: The new `Graphic` objects to display on the map.
    private static func makeMultilayerPolygonGraphics() -> [Graphic] {
        // Create a text graphic.
        var graphics = [
            Graphic(
                geometry: Point(x: 65, y: 50, spatialReference: .wgs84),
                symbol: makeTextSymbol(text: "Multilayer\nPolygons")
            )
        ]

        // Create multilayer polygon symbols.
        graphics.append(contentsOf: [
            // Cross-hatched diagonal lines.
            makeMultilayerPolygonSymbolGraphic(angles: [-45, 45], offset: 0),
            // Hatched diagonal lines.
            makeMultilayerPolygonSymbolGraphic(angles: [-45], offset: offsetBetweenSymbols),
            // Hatched vertical lines.
            makeMultilayerPolygonSymbolGraphic(angles: [90], offset: offsetBetweenSymbols * 2)
        ])

        return graphics
    }

    /// Creates a graphic of a multilayer polygon symbol.
    /// - Parameters:
    ///   - angles: The angles at which to draw the fill lines within the polygon.
    ///   - offset: The offset used to keep a consistent distance between symbols in the same column.
    /// - Returns: A new `Graphic` object of the multilayer polygon symbol.
    private static func makeMultilayerPolygonSymbolGraphic(angles: [Double], offset: Double) -> Graphic {
        // Create a stroke symbol layer to make the symbol layer.
        let hatchStrokeLayer = SolidStrokeSymbolLayer(width: 2, color: .red)

        // Create a list to hold all necessary symbol layers.
        var symbolLayers: [SymbolLayer] = []

        // For each angle, create a hatch fill symbol layer with hatched lines at the given angle.
        for angle in angles {
            let hatchLayer = HatchFillSymbolLayer(
                polylineSymbol: MultilayerPolylineSymbol(symbolLayers: [hatchStrokeLayer]),
                angle: angle
            )

            // Set separation distance for lines.
            hatchLayer.separation = 9
            symbolLayers.append(hatchLayer)
        }

        // Create a stroke symbol layer to be used as an outline.
        let outlineStrokeLayer = SolidStrokeSymbolLayer(width: 1, color: .black)
        symbolLayers.append(outlineStrokeLayer)

        // Create a multilayer polygon symbol from the symbol layer list.
        let polygonSymbol = MultilayerPolygonSymbol(symbolLayers: symbolLayers)

        // Create rectangle polygon for the graphic.
        let polygon = Polygon(
            points: [
                Point(x: 60, y: 25 - offset),
                Point(x: 70, y: 25 - offset),
                Point(x: 70, y: 20 - offset),
                Point(x: 60, y: 20 - offset)
            ],
            spatialReference: .wgs84
        )

        // Create a graphic of the multilayer polygon symbol.
        return Graphic(geometry: polygon, symbol: polygonSymbol)
    }

    // MARK: Complex Multilayer Symbols

    /// Creates the complex multilayer symbol graphics.
    /// - Returns: The new `Graphic` objects to display on the map.
    private static func makeComplexMultilayerSymbolGraphics() -> [Graphic] {
        // Create a text graphic.
        var graphics = [
            Graphic(
                geometry: Point(x: 130, y: 50, spatialReference: .wgs84),
                symbol: makeTextSymbol(text: "Complex Multilayer\nSymbols")
            )
        ]

        // Create the complex multilayer graphics: a point, polygon and polyline.
        if let complexPointGeometry = try? Geometry.fromJSON(.complexPointGeometryJSON) {
            graphics.append(makeComplexPointGraphic(geometry: complexPointGeometry))
        }
        graphics.append(contentsOf: [
            makeComplexPolygonGraphic(),
            makeComplexPolylineGraphic()
        ])

        return graphics
    }

    /// Creates a graphic of a complex multilayer point from multiple symbol layers and given geometry.
    /// - Parameter geometry: The geometry used to create a multilayer polygon symbol.
    /// - Returns: A new `Graphic` object of the multilayer point symbol.
    private static func makeComplexPointGraphic(geometry: Geometry) -> Graphic {
        // Create the vector marker symbol layers for the complex point.
        let orangeSquareLayer = makeLayerForComplexPoint(fillColor: .orange, outlineColor: .blue, size: 11)
        orangeSquareLayer.anchor = SymbolAnchor(x: -4, y: -6, placementMode: .absolute)

        let blackSquareLayer = makeLayerForComplexPoint(fillColor: .black, outlineColor: .cyan, size: 6)
        blackSquareLayer.anchor = SymbolAnchor(x: 2, y: 1, placementMode: .absolute)

        let purpleSquareLayer = makeLayerForComplexPoint(fillColor: .clear, outlineColor: .magenta, size: 14)
        purpleSquareLayer.anchor = SymbolAnchor(x: 4, y: 2, placementMode: .absolute)

        // Create a layer of a yellow hexagon with a black outline from the passed geometry.
        let yellowFillLayer = SolidFillSymbolLayer(color: .yellow)
        let blackOutline = SolidStrokeSymbolLayer(width: 2, color: .black)
        let hexagonVectorElement = VectorMarkerSymbolElement(
            geometry: geometry,
            multilayerSymbol: MultilayerPolygonSymbol(symbolLayers: [yellowFillLayer, blackOutline])
        )
        let yellowHexagonLayer = VectorMarkerSymbolLayer(vectorMarkerSymbolElements: [hexagonVectorElement])
        yellowHexagonLayer.size = 35

        // Create the multilayer point symbol from the symbol layers.
        let pointSymbol = MultilayerPointSymbol(symbolLayers: [
            yellowHexagonLayer,
            orangeSquareLayer,
            blackSquareLayer,
            purpleSquareLayer
        ])

        // Create a graphic of the multilayer point symbol.
        return Graphic(geometry: Point(x: 130, y: 20, spatialReference: .wgs84), symbol: pointSymbol)
    }

    /// Creates a vector marker symbol layer for use in the composition of a complex point.
    /// - Parameters:
    ///   - fillColor: The fill color of the symbol.
    ///   - outlineColor: The  outline color of the symbol.
    ///   - size: The size of the symbol.
    /// - Returns: A new `VectorMarkerSymbolLayer` object of the created symbol.
    private static func makeLayerForComplexPoint(
        fillColor: UIColor,
        outlineColor: UIColor,
        size: Double
    ) -> VectorMarkerSymbolLayer {
        // Create a fill layer and outline.
        let fillLayer = SolidFillSymbolLayer(color: fillColor)
        let outlineLayer = SolidStrokeSymbolLayer(width: 2, color: outlineColor)

        // Create a geometry from an envelope.
        let geometry = Envelope(
            min: Point(x: -0.5, y: -0.5, spatialReference: .wgs84),
            max: Point(x: 0.5, y: 0.5, spatialReference: .wgs84)
        )

        // Create a vector marker symbol element using the geometry and a multilayer polygon symbol.
        let symbolElement = VectorMarkerSymbolElement(
            geometry: geometry,
            multilayerSymbol: MultilayerPolygonSymbol(symbolLayers: [fillLayer, outlineLayer])
        )

        // Create a symbol layer containing the symbol element.
        let symbolLayer = VectorMarkerSymbolLayer(vectorMarkerSymbolElements: [symbolElement])
        symbolLayer.size = size

        return symbolLayer
    }

    /// Creates a graphic of a complex polygon made with multiple symbol layers.
    /// - Returns: A new `Graphic` object of the multilayer polygon symbol.
    private static func makeComplexPolygonGraphic() -> Graphic {
        // Create a multilayer polygon symbol from the symbol layers.
        let polygonSymbol = MultilayerPolygonSymbol(
            symbolLayers: makeLayersForComplexMultilayerSymbols(includeRedFill: true)
        )

        // Create a polygon for the graphic.
        let polygon = Polygon(
            points: [
                Point(x: 120, y: 0),
                Point(x: 140, y: 0),
                Point(x: 140, y: -10),
                Point(x: 120, y: -10)
            ],
            spatialReference: .wgs84
        )

        // Create a graphic of the multilayer polygon symbol.
        return Graphic(geometry: polygon, symbol: polygonSymbol)
    }

    /// Creates a graphic of the complex polyline made with multiple layers.
    /// - Returns: A new `Graphic` object of the multilayer polyline symbol.
    private static func makeComplexPolylineGraphic() -> Graphic {
        // Create a multilayer polyline symbol from the symbol layers.
        let polylineSymbol = MultilayerPolylineSymbol(
            symbolLayers: makeLayersForComplexMultilayerSymbols(includeRedFill: false)
        )

        // Create a polyline geometry of the graphic.
        let polyline = Polyline(
            points: [
                Point(x: 120, y: -25),
                Point(x: 140, y: -25)
            ],
            spatialReference: .wgs84
        )

        // Create a graphic of the multilayer polygon symbol.
        return Graphic(geometry: polyline, symbol: polylineSymbol)
    }

    /// Creates the symbol layers used to create the complex multilayer symbols.
    /// - Parameter includeRedFill: A Boolean that indicates whether to include a red fill layer.
    /// - Returns: The new `SymbolLayer` objects.
    private static func makeLayersForComplexMultilayerSymbols(includeRedFill: Bool) -> [SymbolLayer] {
        // Create a black dash effect.
        let dashEffect = DashGeometricEffect(dashTemplate: [5, 3])
        let blackDashesLayer = SolidStrokeSymbolLayer(width: 1, color: .black, geometricEffects: [dashEffect])
        blackDashesLayer.capStyle = .square

        // Create a black outline.
        let blackOutlineLayer = SolidStrokeSymbolLayer(width: 7, color: .black)
        blackOutlineLayer.capStyle = .round

        // Create a yellow stroke.
        let yellowStrokeLayer = SolidStrokeSymbolLayer(width: 5, color: .yellow)
        yellowStrokeLayer.capStyle = .round

        let symbolLayers = [blackOutlineLayer, yellowStrokeLayer, blackDashesLayer]
        return includeRedFill ? symbolLayers + [SolidFillSymbolLayer(color: .red)] : symbolLayers
    }
}

private extension URL {
    /// The URL to a campsite image on ArcGIS online.
    static let campsiteImage = URL(
        string: "https://static.arcgis.com/images/Symbols/OutdoorRecreation/Camping.png"
    )!
}

private extension String {
    /// The JSON for a complex point geometry.
    static let complexPointGeometryJSON = "{\"rings\":[[[-2.89,5],[2.89,5],[5.77,0],[2.89,-5],[-2.89,-5],[-5.77,0],[-2.89,5]]]}"

    /// The JSON for a diamond geometry.
    static let diamondGeometryJSON = "{\"rings\":[[[0,2.5],[2.5,0],[0,-2.5],[-2.5,0],[0,2.5]]]}"

    /// The JSON for a triangle geometry.
    static let triangleGeometryJSON = "{\"rings\":[[[0,5],[5,-5],[-5,-5],[0,5]]]}"

    /// The JSON for a cross geometry.
    static let crossGeometryJSON = "{\"paths\":[[[-1,1],[0,0],[1,-1]],[[1,1],[0,0],[-1,-1]]]}"
}

#Preview {
    RenderMultilayerSymbolsView()
}

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