Add vector tiled layer from custom style

View on GitHub

Load an ArcGIS vector tiled layers using custom styles.

Screenshot of Add vector tiled layer from custom style sample with online style Screenshot of Add vector tiled layer from custom style sample with offline style

Use case

Vector tile basemaps can be created in ArcGIS Pro and published as offline packages or online services. You can create a custom style tailored to your needs and easily apply them to your map. ArcGISVectorTiledLayer has many advantages over traditional raster based basemaps (ArcGISTiledLayer), including smooth scaling between different screen DPIs, smaller package sizes, and the ability to rotate symbols and labels dynamically.

How to use the sample

Pan and zoom to explore the vector tile basemap. Select a theme to see it applied to the vector tile basemap.

How it works

  1. Create an ArcGISVectorTiledLayer with the URL of a custom style from AcrGIS Online.
  2. Alternatively, create an ArcGISVectorTiledLayer by taking a portal item offline and applying it to an offline vector tile package: i. Create a PortalItem using the URL of a custom style.
    ii. Create an ExportVectorTilesTask using the portal item.
    iii. Get the ExportVectorTilesJob using ExportVectorTilesTask.makeExportStyleResourceCacheJob(itemResourceCacheURL:).
    iv. Start the job using Job.start().
    v. Create a VectorTileCache using the path of the local vector tile package.
    vi. Once the job is complete, create an ArcGISVectorTiledLayer using the vector tile cache and the ItemResourceCache from the job's result.
  3. Create a Basemap from the ArcGISVectorTiledLayer.
  4. Assign the basemap to the map's basemap.

Relevant API

  • ArcGISVectorTiledLayer
  • ExportVectorTilesTask
  • ItemResourceCache
  • Map
  • VectorTileCache

Offline data

This sample uses the Dodge City OSM vector tile package. It is downloaded from ArcGIS Online automatically.

Tags

tiles, vector, vector basemap, vector tile package, vector tiled layer, vector tiles, vtpk

Sample Code

AddVectorTiledLayerFromCustomStyleView.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
// Copyright 2024 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 AddVectorTiledLayerFromCustomStyleView: View {
    /// The view model for the sample.
    @StateObject private var model = Model()

    /// The viewpoint used to update the map view.
    @State private var viewpoint: Viewpoint?

    /// The label of the style selected by the picker.
    @State private var selectedStyleLabel = "Default"

    /// The error shown in the error alert.
    @State private var error: Error?

    var body: some View {
        MapView(map: model.map, viewpoint: viewpoint)
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Picker("Style", selection: $selectedStyleLabel) {
                        Section("Online Styles") {
                            ForEach(model.onlineStyles, id: \.key) { label, _ in
                                Text(label)
                            }
                        }
                        Section("Offline Styles") {
                            ForEach(model.offlineStyles, id: \.key) { label, _ in
                                Text(label)
                            }
                        }
                    }
                    .task(id: selectedStyleLabel) {
                        // Updates the map's layer when the picker selection changes.
                        do {
                            viewpoint = try await model.setVectorTiledLayer(
                                label: selectedStyleLabel
                            )
                        } catch {
                            self.error = error
                        }
                    }
                    .errorAlert(presentingError: $error)
                }
            }
    }
}

// MARK: Model

private extension AddVectorTiledLayerFromCustomStyleView {
    /// The view model for the sample.
    @MainActor
    final class Model: ObservableObject {
        /// A map with no specified style.
        let map = Map()

        /// The labels and portal item IDs of the online styles.
        let onlineStyles: KeyValuePairs = [
            "Default": "1349bfa0ed08485d8a92c442a3850b06",
            "Style 1": "bd8ac41667014d98b933e97713ba8377",
            "Style 2": "02f85ec376084c508b9c8e5a311724fa",
            "Style 3": "1bf0cc4a4380468fbbff107e100f65a5"
        ]

        /// The labels and portal item IDs of the offline styles.
        let offlineStyles: KeyValuePairs = [
            "Light": "e01262ef2a4f4d91897d9bbd3a9b1075",
            "Dark": "ce8a34e5d4ca4fa193a097511daa8855"
        ]

        /// The cached vector tiled layers keyed by the label of their associated style.
        private var vectorTiledLayers: [String: ArcGISVectorTiledLayer] = [:]

        /// The URL to the temporary directory for the offline style files.
        private let temporaryDirectoryURL = FileManager.createTemporaryDirectory()

        /// The vector tile cache for creating the offline vector tiled layers.
        private let vectorTileCache = VectorTileCache(name: "dodge_city", bundle: .main)!

        deinit {
            // Removes all of the temporary offline style files used by sample.
            try? FileManager.default.removeItem(at: temporaryDirectoryURL)
        }

        /// Sets the vector tiled layer for a given style on the map.
        /// - Parameter label: The label of the style associated with the layer.
        /// - Returns: A viewpoint framing some of the layer's data.
        func setVectorTiledLayer(label: String) async throws -> Viewpoint {
            // Gets or creates a vector tile layer and adds it to the map as a basemap.
            let vectorTiledLayer = if let cachedVectorTiledLayer = vectorTiledLayers[label] {
                cachedVectorTiledLayer
            } else {
                try await cacheVectorTiledLayer(label: label)
            }
            map.basemap = Basemap(baseLayer: vectorTiledLayer)

            return if vectorTiledLayer.vectorTileCache != nil {
                // Uses a Dodge City, KS viewpoint if the layer was created using the tile cache.
                Viewpoint(latitude: 37.76528, longitude: -100.01766, scale: 4e4)
            } else {
                // Uses a Europe/Africa viewpoint if the layer was created using an online style.
                Viewpoint(latitude: 28.53345, longitude: 17.56488, scale: 1e8)
            }
        }

        /// Creates and caches a vector tiled layer for a given style.
        /// - Parameter label: The label of the style associated with the layer.
        /// - Returns: The cached `ArcGISVectorTiledLayer`.
        private func cacheVectorTiledLayer(label: String) async throws -> ArcGISVectorTiledLayer {
            let vectorTiledLayer: ArcGISVectorTiledLayer
            if let onlineStyle = onlineStyles.first(where: { $0.key == label }) {
                vectorTiledLayer = makeOnlineVectorTiledLayer(itemID: onlineStyle.value)
            } else {
                let offlineStyle = offlineStyles.first(where: { $0.key == label })!
                vectorTiledLayer = try await makeOfflineVectorTiledLayer(itemID: offlineStyle.value)
            }

            try await vectorTiledLayer.load()
            vectorTiledLayers[label] = vectorTiledLayer

            return vectorTiledLayer
        }

        /// Creates a vector tiled layer using a portal item.
        /// - Parameter itemID: The ID of the portal item.
        /// - Returns: A new `ArcGISVectorTiledLayer` object.
        private func makeOnlineVectorTiledLayer(itemID: String) -> ArcGISVectorTiledLayer {
            let portalItem = PortalItem(
                portal: .arcGISOnline(connection: .anonymous),
                id: .init(itemID)!
            )
            return ArcGISVectorTiledLayer(item: portalItem)
        }

        /// Creates a vector tiled layer using a local vector tile cache and an item resource cache.
        /// - Parameter itemID: The ID of the portal item used to create the export vector tiles task.
        /// - Returns: A new `ArcGISVectorTiledLayer` object.
        private func makeOfflineVectorTiledLayer(
            itemID: String
        ) async throws -> ArcGISVectorTiledLayer {
            // Creates a export style resource cache job using a portal item.
            let portalItem = PortalItem(
                portal: .arcGISOnline(connection: .anonymous),
                id: .init(itemID)!
            )
            let exportTask = ExportVectorTilesTask(portalItem: portalItem)

            let temporaryURL = temporaryDirectoryURL.appendingPathComponent(itemID)
            let exportStyleResourceCacheJob = exportTask.makeExportStyleResourceCacheJob(
                itemResourceCacheURL: temporaryURL
            )

            // Gets the item resource cache from the job and uses it to create the layer.
            exportStyleResourceCacheJob.start()
            let output = try await exportStyleResourceCacheJob.output

            return ArcGISVectorTiledLayer(
                vectorTileCache: vectorTileCache,
                itemResourceCache: output.itemResourceCache
            )
        }
    }
}

// MARK: Helper Extensions

private extension FileManager {
    /// Creates a temporary directory.
    /// - Returns: The URL of the created directory
    static func createTemporaryDirectory() -> URL {
        // swiftlint:disable:next force_try
        try! FileManager.default.url(
            for: .itemReplacementDirectory,
            in: .userDomainMask,
            appropriateFor: FileManager.default.temporaryDirectory,
            create: true
        )
    }
}

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