Select ENC features

View on GitHubSample viewer app

Select features in an ENC layer.

Image of select ENC features

Use case

You can use selection to identify a feature and learn more about it. ENC layers often have many overlapping features and features of mixed geometry in a single layer, so the sample includes code for identifying the most relevant feature.

How to use the sample

Tap to select ENC features. Feature acronym and description will be displayed in a callout.

How it works

  1. Load and display the ENC layers.
  2. Identify the layers tapped on the map view with AGSGeoView.identifyLayers(atScreenPoint:tolerance:returnPopupsOnly:completion:).
  3. Filter the list of identified layers to include only results where the layerContent is an AGSENCLayer.
  4. Get the first result.
  5. Get the first feature in the result.
  6. Select that feature by calling AGSENCLayer.select(_:).
  7. Show the feature's acronym and description in a callout.

Relevant API

  • AGSENCFeature
  • AGSENCLayer
  • AGSIdentifyLayerResult

Offline data

This sample downloads the ENC Exchange Set without updates item from ArcGIS Online.

The latest hydrography package can be downloaded from ArcGIS Developer website (login is required). 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.

Additional information

Read more about Electronic Navigational Charts and its deployment on the ArcGIS Developer website.

Tags

chart, hydrography, identify, IHO, maritime, S-57, S57, select, selection

Sample Code

SelectENCFeaturesViewController.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
// Copyright 2021 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
//
//   http://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 UIKit
import ArcGIS

class SelectENCFeaturesViewController: UIViewController {
    /// The map view managed by the view controller.
    @IBOutlet var mapView: AGSMapView! {
        didSet {
            mapView.map = AGSMap(basemapStyle: .arcGISOceans)
            mapView.setViewpoint(AGSViewpoint(latitude: -32.5, longitude: 60.95, scale: 1e5))
            mapView.touchDelegate = self
            mapView.callout.isAccessoryButtonHidden = true
        }
    }

    /// The ENC layer that contains the current selected feature.
    var currentENCLayer: AGSENCLayer?
    /// A reference to the cancelable identify layer operation.
    var identifyOperation: AGSCancelable?

    /// A URL to the temporary SENC data directory.
    let temporaryURL: URL = {
        let directoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(ProcessInfo().globallyUniqueString)
        // Create and return the full, unique URL to the temporary folder.
        try? FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
        return directoryURL
    }()

    /// Load the ENC dataset and add it to the map view.
    func addENCExchangeSet() {
        // Load catalog file in ENC exchange set from bundle.
        let catalogURL = Bundle.main.url(
            forResource: "CATALOG",
            withExtension: "031",
            subdirectory: "ExchangeSetWithoutUpdates/ENC_ROOT"
        )!
        let encExchangeSet = AGSENCExchangeSet(fileURLs: [catalogURL])

        // URL to the "hydrography" data folder that contains the "S57DataDictionary.xml" file.
        let hydrographyDirectory = Bundle.main.url(
            forResource: "S57DataDictionary",
            withExtension: "xml",
            subdirectory: "hydrography"
        )!
        .deletingLastPathComponent()
        // Set environment settings for loading the dataset.
        let environmentSettings = AGSENCEnvironmentSettings.shared()
        environmentSettings.resourceDirectory = hydrographyDirectory
        // The SENC data directory is for temporarily storing generated files.
        environmentSettings.sencDataDirectory = temporaryURL
        // Update the display settings to make the chart less cluttered.
        updateDisplaySettings()

        encExchangeSet.load { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                self.presentAlert(error: error)
            } else {
                // Create a list of ENC layers from datasets.
                let encLayers = encExchangeSet.datasets.map { AGSENCLayer(cell: AGSENCCell(dataset: $0)) }
                // Add layers to the map.
                self.mapView.map?.operationalLayers.addObjects(from: encLayers)
            }
        }
    }

    /// Update the display settings to make the chart less cluttered.
    func updateDisplaySettings() {
        let displaySettings = AGSENCEnvironmentSettings.shared().displaySettings

        let textGroupVisibilitySettings = displaySettings.textGroupVisibilitySettings
        textGroupVisibilitySettings.geographicNames = false
        textGroupVisibilitySettings.natureOfSeabed = false

        let viewingGroupSettings = displaySettings.viewingGroupSettings
        viewingGroupSettings.buoysBeaconsAidsToNavigation = false
        viewingGroupSettings.depthContours = false
        viewingGroupSettings.spotSoundings = false
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Add the source code button item to the right of navigation bar.
        (navigationItem.rightBarButtonItem as? SourceCodeBarButtonItem)?.filenames = ["SelectENCFeaturesViewController"]
        addENCExchangeSet()
    }

    deinit {
        // Recursively remove all files in the sample-specific
        // temporary folder and the folder itself.
        try? FileManager.default.removeItem(at: temporaryURL)
        // Reset ENC environment display settings.
        let displaySettings = AGSENCEnvironmentSettings.shared().displaySettings
        displaySettings.textGroupVisibilitySettings.resetToDefaults()
        displaySettings.viewingGroupSettings.resetToDefaults()
    }
}

// MARK: - AGSGeoViewTouchDelegate

extension SelectENCFeaturesViewController: AGSGeoViewTouchDelegate {
    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        // Dismiss any presenting callout.
        mapView.callout.dismiss()
        // Clear selection before identifying layers.
        currentENCLayer?.clearSelection()
        // Clear in-progress identify operation.
        identifyOperation?.cancel()

        // Identify the tapped feature.
        identifyOperation = mapView.identifyLayers(atScreenPoint: screenPoint, tolerance: 10, returnPopupsOnly: false) { [weak self] identifyResults, _ in
            guard let self = self else { return }
            self.identifyOperation = nil
            guard let results = identifyResults,
                  let firstResult = results.first(where: { $0.layerContent is AGSENCLayer }),
                  let containingLayer = firstResult.layerContent as? AGSENCLayer,
                  let firstFeature = firstResult.geoElements.first as? AGSENCFeature else {
                      return
                  }
            self.currentENCLayer = containingLayer
            containingLayer.select(firstFeature)
            self.mapView.callout.title = firstFeature.acronym
            self.mapView.callout.detail = firstFeature.featureDescription
            self.mapView.callout.show(at: mapPoint, screenOffset: .zero, rotateOffsetWithMap: false, animated: true)
        }
    }
}

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