Download tiles from an online vector tile service.
Use case
Field workers with limited network connectivity can use exported vector tiles as a basemap for use while offline.
How to use the sample
When the vector tiled layer loads, zoom in to the extent you want to export. The red box shows the extent that will be exported. Tap the "Export Vector Tiles" button to start the job. An error will show if the extent is larger than the maximum limit allowed. When finished, a dialog will show the exported result in a new map view.
How it works
Create an ExportVectorTilesTask instance, passing in the PortalItem for the vector tiled layer. Since vector tiled layers are premium content, you must first authenticate with the Portal.
Create parameters for the export by using the task's method, ExportVectorTilesTask.makeDefaultExportVectorTilesParameters(areaOfInterest:maxScale:), specifying the area of interest and max scale.
Create an ExportVectorTileJob instance by using the task's method, ExportVectorTilesTask.makeExportVectorTilesJob(parameters:vectorTileCacheURL:itemResourceCacheURL:), passing in the parameters and specifying a vector tile cache path and an item resource path. The resource path is required if you want to export the tiles with the style.
Start the job and await its output.
Get the VectorTileCache and ItemResourceCache from the output and create an ArcGISVectorTiledLayer instance.
Create a Map instance, specifying a basemap with a base layer of the vector tiled layer.
Set the map's initial viewpoint to the area of interest and create a map view with the map.
NOTE: Downloading tiles for offline use requires authentication with the web map's server. To use this sample, you will need an ArcGIS Online account.
Vector tiles have high drawing performance and smaller file size compared to regular tiled layers, due to consisting solely of points, lines, and polygons. However, in ArcGIS Maps SDK they cannot be displayed in scenes. Visit ArcGIS for Developers to learn more about the characteristics of ArcGIS vector tiled layers.
Tags
cache, download, offline, vector
Sample Code
DownloadVectorTilesToLocalCacheView.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
// Copyright 2022 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
structDownloadVectorTilesToLocalCacheView: View{
/// A Boolean value indicating whether to download vector tiles.@Stateprivatevar isDownloading =false/// A Boolean value indicating whether to cancel and the job.@Stateprivatevar isCancellingJob =false/// The view model for this sample.@StateObjectprivatevar model =Model()
var body: someView {
GeometryReader { geometry inMapViewReader { mapView inMapView(map: model.map)
.interactionModes(isDownloading ? [] : [.pan, .zoom])
.onScaleChanged { model.maxScale =$0*0.1 }
.alert(isPresented: $model.isShowingAlert, presentingError: model.error)
.task {
await model.initializeVectorTilesTask()
}
.onDisappear {
Task { await model.cancelJob() }
}
.overlay {
Rectangle()
.stroke(.red, lineWidth: 2)
.padding(EdgeInsets(top: 20, leading: 20, bottom: 44, trailing: 20))
.opacity(model.isShowingResults ?0 : 1)
}
.overlay {
if isDownloading,
let progress = model.exportVectorTilesJob?.progress {
VStack(spacing: 16) {
ProgressView(progress)
.progressViewStyle(.linear)
.frame(maxWidth: 200)
Button("Cancel") {
isCancellingJob =true }
.disabled(isCancellingJob)
.task(id: isCancellingJob) {
// Ensures cancelling the job is true.guard isCancellingJob else { return }
// Cancels the job.await model.cancelJob()
// Sets cancelling the job and downloading to false. isCancellingJob =false isDownloading =false }
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 15))
.shadow(radius: 3)
}
}
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button("Download Vector Tiles") {
isDownloading =true }
.disabled(model.isDownloadDisabled || isDownloading)
.task(id: isDownloading) {
// Ensures downloading is true.guard isDownloading else { return }
// Creates a rectangle from the area of interest.let viewRect = geometry.frame(in: .local).inset(
by: UIEdgeInsets(
top: 20,
left: geometry.safeAreaInsets.leading +20,
bottom: 44,
right: -geometry.safeAreaInsets.trailing +20 )
)
// Creates an envelope from the rectangle.guardlet extent = mapView.envelope(fromViewRect: viewRect) else { return }
// Downloads the vector tiles.await model.downloadVectorTiles(extent: extent)
// Sets downloading to false when the download finishes. isDownloading =false }
.sheet(isPresented: $model.isShowingResults) {
// Removes the temporary files when the cover is dismissed. model.removeTemporaryFiles()
} content: {
NavigationView {
MapView(map: model.downloadedVectorTilesMap)
.navigationTitle("Vector tile package")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
model.isShowingResults =false }
}
}
}
.highPriorityGesture(DragGesture())
}
}
}
}
}
}
}
privateextensionDownloadVectorTilesToLocalCacheView{
/// A view model for this sample.@MainActorclassModel: ObservableObject{
/// A Boolean value indicating whether the download button is disabled.@Publishedvar isDownloadDisabled =true/// A Boolean value indicating whether to show the result map.@Publishedvar isShowingResults =false/// A Boolean value indicating whether to show an alert.@Publishedvar isShowingAlert =false/// The error shown in the alert.@Publishedvar error: Error? {
didSet { isShowingAlert = error !=nil }
}
/// A map with a basemap from the vector tiled layer results.@Publishedvar downloadedVectorTilesMap: Map!
/// The export vector tiles job.@Publishedvar exportVectorTilesJob: ExportVectorTilesJob!
/// The export vector tiles task.privatevar exportVectorTilesTask: ExportVectorTilesTask!
/// The vector tiled layer from the downloaded result.privatevar vectorTiledLayerResults: ArcGISVectorTiledLayer!
/// A URL to the directory temporarily storing all items.privatelet temporaryDirectory = makeTemporaryDirectory()
/// A URL to the temporary directory to store the exported vector tile package.privatelet vtpkTemporaryURL: URL/// A URL to the temporary directory to store the style item resources.privatelet styleTemporaryURL: URL/// The max scale for the export vector tiles job.var maxScale: Double?
/// A map with a night streets basemap style and an initial viewpoint.let map: Mapinit() {
// Initializes the map. map =Map(basemapStyle: .arcGISStreetsNight)
map.initialViewpoint =Viewpoint(latitude: 34.049, longitude: -117.181, scale: 1e4)
// Initializes the URL for the directory containing vector tile packages. vtpkTemporaryURL = temporaryDirectory
.appendingPathComponent("myTileCache")
.appendingPathExtension("vtpk")
// Initializes the URL for the directory containing style item resources. styleTemporaryURL = temporaryDirectory
.appendingPathComponent("styleItemResources", isDirectory: true)
}
deinit {
// Removes the temporary directory.try?FileManager.default.removeItem(at: temporaryDirectory)
}
/// Initializes the vector tiles task.funcinitializeVectorTilesTask()async {
do {
// Waits for the map to load.tryawait map.load()
// Gets the map's base layers.guardlet vectorTiledLayer = map.basemap?.baseLayers.first as?ArcGISVectorTiledLayerelse { return }
// Creates the export vector tiles task from the base layers' URL. exportVectorTilesTask =ExportVectorTilesTask(url: vectorTiledLayer.url!)
// Loads the export vector tiles task.tryawait exportVectorTilesTask.load()
// Enables the download button. isDownloadDisabled =false } catch {
self.error = error
}
}
/// Downloads the vector tiles within the area of interest./// - Parameter extent: The area of interest's envelope to download vector tiles.funcdownloadVectorTiles(extent: Envelope)async {
// Ensures that exporting vector tiles is allowed.iflet vectorTileSourceInfo = exportVectorTilesTask.vectorTileSourceInfo,
vectorTileSourceInfo.allowsExportingTiles,
let maxScale = maxScale {
do {
// Creates the parameters for the export vector tiles job.let parameters =tryawait exportVectorTilesTask.makeDefaultExportVectorTilesParameters(
areaOfInterest: extent,
maxScale: maxScale
)
// Creates the export vector tiles job based on the parameters// and temporary URLs. exportVectorTilesJob = exportVectorTilesTask.makeExportVectorTilesJob(
parameters: parameters,
vectorTileCacheURL: vtpkTemporaryURL,
itemResourceCacheURL: styleTemporaryURL
)
// Starts the job. exportVectorTilesJob.start()
defer { exportVectorTilesJob =nil }
// Awaits the output of the job.let output =tryawait exportVectorTilesJob.output
// Gets the vector tile and item resource cache from the output.iflet vectorTileCache = output.vectorTileCache,
let itemResourceCache = output.itemResourceCache {
// Creates a vector tiled layer from the caches. vectorTiledLayerResults =ArcGISVectorTiledLayer(
vectorTileCache: vectorTileCache,
itemResourceCache: itemResourceCache
)
// Creates a map with a basemap from the vector tiled layer results. downloadedVectorTilesMap =Map(basemap: Basemap(baseLayer: vectorTiledLayerResults))
// Sets the initial viewpoint of the result map. downloadedVectorTilesMap.initialViewpoint =Viewpoint(targetExtent: extent.expanded(by: 0.9))
// Shows the downloaded results. isShowingResults =true }
} catchisCancellationError {
// Does nothing if the error is a cancellation error. } catch {
// Shows an alert if any errors occur.self.error = error
}
}
}
/// Cancels the export vector tiles job.funccancelJob()async {
await exportVectorTilesJob?.cancel()
exportVectorTilesJob =nil }
/// Removes any temporary files.funcremoveTemporaryFiles() {
try?FileManager.default.removeItem(at: vtpkTemporaryURL)
try?FileManager.default.removeItem(at: styleTemporaryURL)
}
/// Creates a temporary directory./// - Returns: The URL to the temporary directory.privatestaticfuncmakeTemporaryDirectory() -> URL {
// swiftlint:disable:next force_trytry!FileManager.default.url(
for: .itemReplacementDirectory,
in: .userDomainMask,
appropriateFor: Bundle.main.bundleURL,
create: true )
}
}
}
privateextensionMapViewProxy{
/// Creates an envelope from the given rectangle./// - Parameter viewRect: The rectangle to create an envelope of./// - Returns: An envelope of the given rectangle.funcenvelope(fromViewRectviewRect: CGRect) -> Envelope? {
guardlet min = location(fromScreenPoint: CGPoint(x: viewRect.minX, y: viewRect.minY)),
let max = location(fromScreenPoint: CGPoint(x: viewRect.maxX, y: viewRect.maxY)) else {
returnnil }
returnEnvelope(min: min, max: max)
}
}
privateextensionEnvelope{
/// Expands the envelope by a given factor.funcexpanded(byfactor: Double) -> Envelope {
let builder =EnvelopeBuilder(envelope: self)
builder.expand(factor: factor)
return builder.toGeometry()
}
}