Skip to content

Commit 7f0d557

Browse files
authored
Merge pull request #429 from Esri/chris-webb/New-AddENCExchangeSet
[New] Add ENC exchange set
2 parents f1aa69b + dfe6ebf commit 7f0d557

File tree

5 files changed

+271
-0
lines changed

5 files changed

+271
-0
lines changed

Samples.xcodeproj/project.pbxproj

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,10 @@
183183
883C121929C914E100062FF9 /* DownloadPreplannedMapAreaView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = E070A0A2286F3B6000F2B606 /* DownloadPreplannedMapAreaView.swift */; };
184184
88F93CC129C3D59D0006B28E /* CreateAndEditGeometriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */; };
185185
88F93CC229C4D3480006B28E /* CreateAndEditGeometriesView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */; };
186+
950D5B0D2C2CC35D00DF2E4E /* hydrography in Resources */ = {isa = PBXBuildFile; fileRef = 950D5B0C2C2CC35D00DF2E4E /* hydrography */; settings = {ASSET_TAGS = (AddEncExchangeSet, ); }; };
186187
9529D1942C01676200B5C1A3 /* SelectFeaturesInSceneLayerView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = 954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */; };
188+
9537AFB42C2208B5000923C5 /* AddENCExchangeSetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */; };
189+
9537AFD72C220EF0000923C5 /* ExchangeSetwithoutUpdates in Resources */ = {isa = PBXBuildFile; fileRef = 9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */; settings = {ASSET_TAGS = (AddEncExchangeSet, ); }; };
187190
954AEDEE2C01332600265114 /* SelectFeaturesInSceneLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */; };
188191
955271612C0E6749009B1ED4 /* AddRasterFromServiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955271602C0E6749009B1ED4 /* AddRasterFromServiceView.swift */; };
189192
955AFAC42C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955AFAC32C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift */; };
@@ -812,6 +815,9 @@
812815
79D84D0D2A815C5B00F45262 /* AddCustomDynamicEntityDataSourceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCustomDynamicEntityDataSourceView.swift; sourceTree = "<group>"; };
813816
883C121429C9136600062FF9 /* DownloadPreplannedMapAreaView.MapPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadPreplannedMapAreaView.MapPicker.swift; sourceTree = "<group>"; };
814817
88F93CC029C3D59C0006B28E /* CreateAndEditGeometriesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAndEditGeometriesView.swift; sourceTree = "<group>"; };
818+
950D5B0C2C2CC35D00DF2E4E /* hydrography */ = {isa = PBXFileReference; lastKnownFileType = folder; path = hydrography; sourceTree = "<group>"; };
819+
9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddENCExchangeSetView.swift; sourceTree = "<group>"; };
820+
9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ExchangeSetwithoutUpdates; sourceTree = "<group>"; };
815821
954AEDED2C01332600265114 /* SelectFeaturesInSceneLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectFeaturesInSceneLayerView.swift; sourceTree = "<group>"; };
816822
955271602C0E6749009B1ED4 /* AddRasterFromServiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRasterFromServiceView.swift; sourceTree = "<group>"; };
817823
955AFAC32C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplyMosaicRuleToRastersView.swift; sourceTree = "<group>"; };
@@ -1096,6 +1102,7 @@
10961102
0000FB6D2BBDB17600845921 /* Add 3D tiles layer */,
10971103
79D84D0C2A815BED00F45262 /* Add custom dynamic entity data source */,
10981104
4D2ADC3E29C26D05003B367F /* Add dynamic entity layer */,
1105+
9537AFB52C2208CD000923C5 /* Add ENC exchange set */,
10991106
00D4EF7E2863840D00B9CC30 /* Add feature layers */,
11001107
D7F8C0342B60564D0072BFA7 /* Add features with contingent values */,
11011108
F19A316128906F0D003B7EF9 /* Add raster from file */,
@@ -1322,6 +1329,8 @@
13221329
00CCB8A6285D059300BBAB70 /* Portal Data */ = {
13231330
isa = PBXGroup;
13241331
children = (
1332+
950D5AF62C2CC2F100DF2E4E /* c16f3845b9cc4b93a908d87d28823afd */,
1333+
9537AFC82C220ECB000923C5 /* 9d2987a825c646468b3ce7512fb76e2d */,
13251334
D762AF642BF6A96100ECE3C7 /* 74c0c9fa80f4498c9739cc42531e9948 */,
13261335
004A2B962BED454300C297CE /* 740b663bff5e4198b9b6674af93f638a */,
13271336
D701D7242A37C7E4006FF0C8 /* 07d62a792ab6496d9b772a24efea45d0 */,
@@ -1688,6 +1697,30 @@
16881697
path = "Create and edit geometries";
16891698
sourceTree = "<group>";
16901699
};
1700+
950D5AF62C2CC2F100DF2E4E /* c16f3845b9cc4b93a908d87d28823afd */ = {
1701+
isa = PBXGroup;
1702+
children = (
1703+
950D5B0C2C2CC35D00DF2E4E /* hydrography */,
1704+
);
1705+
path = c16f3845b9cc4b93a908d87d28823afd;
1706+
sourceTree = "<group>";
1707+
};
1708+
9537AFB52C2208CD000923C5 /* Add ENC exchange set */ = {
1709+
isa = PBXGroup;
1710+
children = (
1711+
9537AFB32C2208B5000923C5 /* AddENCExchangeSetView.swift */,
1712+
);
1713+
path = "Add ENC exchange set";
1714+
sourceTree = "<group>";
1715+
};
1716+
9537AFC82C220ECB000923C5 /* 9d2987a825c646468b3ce7512fb76e2d */ = {
1717+
isa = PBXGroup;
1718+
children = (
1719+
9537AFD62C220EF0000923C5 /* ExchangeSetwithoutUpdates */,
1720+
);
1721+
path = 9d2987a825c646468b3ce7512fb76e2d;
1722+
sourceTree = "<group>";
1723+
};
16911724
954AEDEF2C01332F00265114 /* Select features in scene layer */ = {
16921725
isa = PBXGroup;
16931726
children = (
@@ -2738,6 +2771,7 @@
27382771
BuildIndependentTargetsInParallel = 1;
27392772
KnownAssetTags = (
27402773
AddCustomDynamicEntityDataSource,
2774+
AddEncExchangeSet,
27412775
AddFeatureLayers,
27422776
AddFeaturesWithContingentValues,
27432777
AddRasterFromFile,
@@ -2801,10 +2835,12 @@
28012835
buildActionMask = 2147483647;
28022836
files = (
28032837
D7F8C0412B605E720072BFA7 /* FillmoreTopographicMap.vtpk in Resources */,
2838+
9537AFD72C220EF0000923C5 /* ExchangeSetwithoutUpdates in Resources */,
28042839
D7F8C03E2B605AF60072BFA7 /* ContingentValuesBirdNests.geodatabase in Resources */,
28052840
E041AC1E288076A60056009B /* info.css in Resources */,
28062841
D7CE9F9B2AE2F575008F7A5F /* streetmap_SD.tpkx in Resources */,
28072842
D721EEA82ABDFF550040BE46 /* LothianRiversAnno.mmpk in Resources */,
2843+
950D5B0D2C2CC35D00DF2E4E /* hydrography in Resources */,
28082844
D7C16D1F2AC5FE8200689E89 /* Pyrenees.csv in Resources */,
28092845
E041AC1A287F54580056009B /* highlight.min.js in Resources */,
28102846
D7497F402AC4BA4100167AD2 /* Edinburgh_Pylon_Dimensions.mmpk in Resources */,
@@ -3018,6 +3054,7 @@
30183054
E004A6F3284E4FEB002A1FE6 /* ShowResultOfSpatialOperationsView.swift in Sources */,
30193055
955AFAC42C10FD6F009C8FE5 /* ApplyMosaicRuleToRastersView.swift in Sources */,
30203056
D751018E2A2E962D00B8FA48 /* IdentifyLayerFeaturesView.swift in Sources */,
3057+
9537AFB42C2208B5000923C5 /* AddENCExchangeSetView.swift in Sources */,
30213058
F1E71BF1289473760064C33F /* AddRasterFromFileView.swift in Sources */,
30223059
00B04273282EC59E0072E1B4 /* AboutView.swift in Sources */,
30233060
7573E81F29D6134C00BEED9C /* TraceUtilityNetworkView.swift in Sources */,
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
// Copyright 2024 Esri
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import ArcGIS
16+
import SwiftUI
17+
18+
struct AddENCExchangeSetView: View {
19+
/// The tracking status for the loading operation.
20+
@State private var isLoading = false
21+
22+
/// The error shown in the error alert.
23+
@State private var error: Error?
24+
25+
/// The data model for the sample.
26+
@StateObject private var model = Model()
27+
28+
var body: some View {
29+
MapViewReader { mapProxy in
30+
MapView(map: model.map)
31+
.overlay(alignment: .center) {
32+
if isLoading {
33+
ProgressView("Loading…")
34+
.padding()
35+
.background(.ultraThickMaterial)
36+
.cornerRadius(10)
37+
.shadow(radius: 50)
38+
}
39+
}
40+
.task {
41+
do {
42+
isLoading = true
43+
try await model.addENCExchangeSet()
44+
if let extent = model.completeExtent {
45+
await mapProxy.setViewpoint(
46+
Viewpoint(center: extent.center, scale: 67000)
47+
)
48+
}
49+
isLoading = false
50+
} catch {
51+
self.error = error
52+
}
53+
}
54+
}
55+
.errorAlert(presentingError: $error)
56+
}
57+
}
58+
59+
private extension AddENCExchangeSetView {
60+
class Model: ObservableObject {
61+
/// A map with Oceans style.
62+
let map: Map = {
63+
let map = Map(basemapStyle: .arcGISOceans)
64+
map.initialViewpoint = Viewpoint(
65+
latitude: -32.5,
66+
longitude: 60.95,
67+
scale: 67000
68+
)
69+
return map
70+
}()
71+
72+
/// The geometry that represents a rectangular shape that encompasses the area on the
73+
/// map where the ENC data will be rendering.
74+
private(set) var completeExtent: Envelope?
75+
76+
/// A URL to the temporary SENC data directory.
77+
private let temporaryURL: URL = {
78+
let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(
79+
ProcessInfo().globallyUniqueString
80+
)
81+
// Create and return the full, unique URL to the temporary folder.
82+
try? FileManager.default.createDirectory(
83+
at: directoryURL,
84+
withIntermediateDirectories: true
85+
)
86+
return directoryURL
87+
}()
88+
89+
deinit {
90+
// Recursively remove all files in the sample-specific
91+
// temporary folder and the folder itself.
92+
try? FileManager.default.removeItem(at: temporaryURL)
93+
// Reset ENC environment display settings.
94+
let displaySettings = ENCEnvironmentSettings.shared.displaySettings
95+
displaySettings.textGroupVisibilitySettings.resetToDefaults()
96+
displaySettings.viewingGroupSettings.resetToDefaults()
97+
}
98+
99+
/// Gets the ENC exchange set data and loads it and sets the display settings.
100+
func addENCExchangeSet() async throws {
101+
let exchangeSet = ENCExchangeSet(fileURLs: [.exchangeSet])
102+
// URL to the "hydrography" data folder that contains the "S57DataDictionary.xml" file.
103+
let resourceURL = URL.hydrographyDirectory.deletingLastPathComponent()
104+
// Set environment settings for loading the dataset.
105+
let environmentSettings = ENCEnvironmentSettings.shared
106+
environmentSettings.resourceURL = resourceURL
107+
// The SENC data directory is for temporarily storing generated files.
108+
environmentSettings.sencDataURL = temporaryURL
109+
updateDisplaySettings()
110+
try await exchangeSet.load()
111+
try await renderENCData(datasets: exchangeSet.datasets)
112+
}
113+
114+
/// Maps the exchange set data to ENC layer and ENC cells and loads the layers.
115+
/// - Parameter datasets: The ENC datasets previously loaded.
116+
private func renderENCData(datasets: [ENCDataset]) async throws {
117+
let encLayers = datasets.map {
118+
ENCLayer(
119+
cell: ENCCell(
120+
dataset: $0
121+
)
122+
)
123+
}
124+
map.addOperationalLayers(encLayers)
125+
await encLayers.load()
126+
let extents = map.operationalLayers.compactMap(\.fullExtent)
127+
completeExtent = GeometryEngine.combineExtents(of: extents)
128+
}
129+
130+
/// Updates the display settings to make the chart less cluttered.
131+
private func updateDisplaySettings() {
132+
let displaySettings = ENCEnvironmentSettings.shared.displaySettings
133+
let textGroupVisibilitySettings = displaySettings.textGroupVisibilitySettings
134+
textGroupVisibilitySettings.includesGeographicNames = false
135+
textGroupVisibilitySettings.includesNatureOfSeabed = false
136+
let viewingGroupSettings = displaySettings.viewingGroupSettings
137+
viewingGroupSettings.includesBuoysBeaconsAidsToNavigation = false
138+
viewingGroupSettings.includesDepthContours = false
139+
viewingGroupSettings.includesSpotSoundings = false
140+
}
141+
}
142+
}
143+
144+
private extension URL {
145+
static let exchangeSet = Bundle.main.url(
146+
forResource: "CATALOG",
147+
withExtension: "031",
148+
subdirectory: "ExchangeSetwithoutUpdates/ExchangeSetwithoutUpdates/ENC_ROOT"
149+
)!
150+
151+
static let hydrographyDirectory = Bundle.main.url(
152+
forResource: "S57DataDictionary",
153+
withExtension: "xml",
154+
subdirectory: "hydrography"
155+
)!
156+
}
157+
158+
#Preview {
159+
AddENCExchangeSetView()
160+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Add ENC exchange set
2+
3+
Display nautical charts per the ENC specification.
4+
5+
![Image showing the add ENC exchange set app](add-enc-exchange-set.png)
6+
7+
## Use case
8+
9+
The [ENC specification](https://docs.iho.int/iho_pubs/standard/S-57Ed3.1/20ApB1.pdf) describes how hydrographic data should be displayed digitally.
10+
11+
An ENC exchange set is a catalog of data files which can be loaded as cells. The cells contain information on how symbols should be displayed in relation to one another, so as to represent information such as depth and obstacles accurately.
12+
13+
## How to use the sample
14+
15+
Run the sample and view the ENC data. Pan and zoom around the map. Take note of the high level of detail in the data and the smooth rendering of the layer.
16+
17+
## How it works
18+
19+
1. Specify the path to a local CATALOG.031 file to create an `ENCExchangeSet`.
20+
2. After loading the exchange set, get the `ENCDataset` objects asynchronously.
21+
3. Create an `ENCCell` for each dataset. Then create an `ENCLayer` for each cell.
22+
4. Add the ENC layer to a map's operational layers collection to display it.
23+
24+
## Relevant API
25+
26+
* ENCCell
27+
* ENCDataset
28+
* ENCExchangeSet
29+
* ENCLayer
30+
31+
## Offline data
32+
33+
This sample downloads the [ENC Exchange Set without updates](https://www.arcgis.com/home/item.html?id=9d2987a825c646468b3ce7512fb76e2d) item from ArcGIS Online.
34+
35+
The latest [hydrography package](https://developers.arcgis.com/swift/downloads/#hydrography-data) can be downloaded from ArcGIS for Developers. The S57DataDictionary.xml file is contained in it along with many others but a user does not need to know that in order to render ENC data.
36+
37+
## Tags
38+
39+
data, ENC, hydrographic, layers, maritime, nautical chart
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"category": "Layers",
3+
"description": "Display nautical charts per the ENC specification.",
4+
"ignore": false,
5+
"images": [
6+
"add-enc-exchange-set.png"
7+
],
8+
"keywords": [
9+
"ENC",
10+
"data",
11+
"hydrographic",
12+
"layers",
13+
"maritime",
14+
"nautical chart",
15+
"ENCCell",
16+
"ENCDataset",
17+
"ENCExchangeSet",
18+
"ENCLayer"
19+
],
20+
"offline_data": [
21+
"9d2987a825c646468b3ce7512fb76e2d",
22+
"c16f3845b9cc4b93a908d87d28823afd"
23+
],
24+
"redirect_from": [],
25+
"relevant_apis": [
26+
"ENCCell",
27+
"ENCDataset",
28+
"ENCExchangeSet",
29+
"ENCLayer"
30+
],
31+
"snippets": [
32+
"AddENCExchangeSetView.swift"
33+
],
34+
"title": "Add ENC exchange set"
35+
}
20.1 KB
Loading

0 commit comments

Comments
 (0)