-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #529 from Esri/Caleb/New-AddVectorTiledLayerFromCu…
…stomStyle [New] Add vector tiled layer from custom style
- Loading branch information
Showing
6 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
201 changes: 201 additions & 0 deletions
201
...les/Add vector tiled layer from custom style/AddVectorTiledLayerFromCustomStyleView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// 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 | ||
) | ||
} | ||
} | ||
|
||
private extension URL { | ||
/// The URL to the local vector tile package file with Dodge City, KS data. | ||
static var dodgeCityVectorTilePackage: URL { | ||
Bundle.main.url(forResource: "dodge_city", withExtension: "vtpk")! | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
Shared/Samples/Add vector tiled layer from custom style/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Add vector tiled layer from custom style | ||
|
||
Load an ArcGIS vector tiled layers using custom styles. | ||
|
||
![Screenshot of Add vector tiled layer from custom style sample with online style](add-vector-tiled-layer-from-custom-style-1.png) | ||
![Screenshot of Add vector tiled layer from custom style sample with offline style](add-vector-tiled-layer-from-custom-style-2.png) | ||
|
||
## 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](https://www.arcgis.com/home/item.html?id=f4b742a57af344988b02227e2824ca5f) vector tile package. It is downloaded from ArcGIS Online automatically. | ||
|
||
## Tags | ||
|
||
tiles, vector, vector basemap, vector tile package, vector tiled layer, vector tiles, vtpk |
38 changes: 38 additions & 0 deletions
38
Shared/Samples/Add vector tiled layer from custom style/README.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"category": "Visualization", | ||
"description": "Load an ArcGIS vector tiled layers using custom styles.", | ||
"ignore": false, | ||
"images": [ | ||
"add-vector-tiled-layer-from-custom-style-1.png", | ||
"add-vector-tiled-layer-from-custom-style-2.png" | ||
], | ||
"keywords": [ | ||
"tiles", | ||
"vector", | ||
"vector basemap", | ||
"vector tile package", | ||
"vector tiled layer", | ||
"vector tiles", | ||
"vtpk", | ||
"ArcGISVectorTiledLayer", | ||
"ExportVectorTilesTask", | ||
"ItemResourceCache", | ||
"Map", | ||
"VectorTileCache" | ||
], | ||
"offline_data": [ | ||
"f4b742a57af344988b02227e2824ca5f" | ||
], | ||
"redirect_from": [], | ||
"relevant_apis": [ | ||
"ArcGISVectorTiledLayer", | ||
"ExportVectorTilesTask", | ||
"ItemResourceCache", | ||
"Map", | ||
"VectorTileCache" | ||
], | ||
"snippets": [ | ||
"AddVectorTiledLayerFromCustomStyleView.swift" | ||
], | ||
"title": "Add vector tiled layer from custom style" | ||
} |
Binary file added
BIN
+136 KB
...or tiled layer from custom style/add-vector-tiled-layer-from-custom-style-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+115 KB
...or tiled layer from custom style/add-vector-tiled-layer-from-custom-style-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.