@@ -20,82 +20,79 @@ struct BrowseOGCAPIFeatureServiceView: View {
2020 /// The error shown in the error alert.
2121 @State private var error : Error ?
2222
23- /// A Boolean value indicating whether the alert is presented.
24- @State private var alertIsPresented = true
25-
23+ /// A Boolean value indicating whether the textfield alert should be presented.
24+ @State private var textfieldAlertIsPresented = true
25+
2626 /// The data model for the sample.
2727 @StateObject private var model = Model ( )
2828
29- /// The input obtained from the user for the OGC service URL .
30- @State private var featureServiceURL = " https://demo.ldproxy.net/daraa "
29+ /// The user input for the OGC service resource .
30+ @State private var userInput = URL . daraaService . absoluteString
3131
32- /// The selected layer name .
33- @State private var selection : String = " "
32+ /// The selected feature collection's title .
33+ @State private var selectedTitle = " "
3434
3535 var body : some View {
3636 MapViewReader { mapProxy in
3737 MapView ( map: model. map)
3838 . toolbar {
3939 ToolbarItemGroup ( placement: . bottomBar) {
40- // The button in toolbar that allows user to launch the load alert.
4140 Button ( " Open Service " ) {
42- alertIsPresented = true
41+ textfieldAlertIsPresented = true
4342 }
43+
4444 Spacer ( )
45- // The layers button will not appear until selection is set.
46- if !selection . isEmpty {
47- Picker ( " Layers " , selection: $selection ) {
48- ForEach ( model. layerNames , id: \. self) { title in
45+
46+ if !model . featureCollectionTitles . isEmpty {
47+ Picker ( " Layers " , selection: $selectedTitle ) {
48+ ForEach ( model. featureCollectionTitles , id: \. self) { title in
4949 Text ( title)
5050 }
5151 }
52- . task ( id: selection) {
53- model. update ( for: selection)
54- if let selection = model. selectedInfo {
55- do {
56- try await model. displayLayer ( with: selection)
57- if let extent = model. completeExtent {
58- await mapProxy. setViewpointGeometry (
59- extent,
60- padding: 100
61- )
62- }
63- } catch {
64- self . error = error
52+ . task ( id: selectedTitle) {
53+ guard !selectedTitle. isEmpty else { return }
54+ let featureCollectionInfo = model. featureCollectionInfos [ selectedTitle] !
55+ do {
56+ try await model. displayLayer ( with: featureCollectionInfo)
57+ if let extent = featureCollectionInfo. extent {
58+ await mapProxy. setViewpointGeometry ( extent, padding: 100 )
6559 }
60+ } catch {
61+ self . error = error
6662 }
6763 }
68- . pickerStyle ( . automatic)
6964 }
7065 }
7166 }
72- . alert ( " Load OGC API feature service " , isPresented: $alertIsPresented, actions: {
73- TextField ( " URL: " , text: $featureServiceURL)
67+ . alert ( " Load OGC API feature service " , isPresented: $textfieldAlertIsPresented) {
68+ // Textfield has a default OGC API URL.
69+ TextField ( " URL " , text: $userInput)
70+ . keyboardType ( . URL)
71+ . textContentType ( . URL)
7472 Button ( " Load " ) {
75- alertIsPresented = false
73+ guard let url = URL ( string : userInput ) else { return }
7674 Task {
7775 do {
78- try await model. loadOGCFeatureData ( url: URL ( string: featureServiceURL) )
79- // This selects the first layer in the layer name list. This is
80- // needed so that the layer selection picker is set in the toolbar.
81- selection = model. layerNames. first ?? " "
82- if let extent = model. completeExtent {
83- await mapProxy. setViewpointGeometry (
84- extent,
85- padding: 100
86- )
76+ try await model. loadOGCFeatureData ( url: url)
77+ // Set the picker selection to the first title in the title list.
78+ if let title = model. featureCollectionTitles. first,
79+ let extent = model. featureCollectionInfos [ title] ? . extent {
80+ selectedTitle = title
81+ await mapProxy. setViewpointGeometry ( extent, padding: 100 )
8782 }
8883 } catch {
8984 self . error = error
9085 }
9186 }
9287 }
88+ . disabled ( userInput. isEmpty)
9389 Button ( " Cancel " , role: . cancel) {
94- alertIsPresented = false
90+ // Reset the default value of the textfield.
91+ userInput = URL . daraaService. absoluteString
9592 }
96- } , message: {
93+ } message: {
9794 Text ( " Please provide a URL to an OGC API feature service. " )
98- } )
95+ }
9996 . errorAlert ( presentingError: $error)
10097 }
10198 }
@@ -104,32 +101,23 @@ struct BrowseOGCAPIFeatureServiceView: View {
104101private extension BrowseOGCAPIFeatureServiceView {
105102 @MainActor
106103 class Model : ObservableObject {
104+ /// A map with a topographic basemap of the Daraa, Syria.
107105 let map : Map = {
108106 let map = Map ( basemapStyle: . arcGISTopographic)
109107 map. initialViewpoint = Viewpoint (
110- center: Point (
111- latitude: 32.62 ,
112- longitude: 36.10
113- ) ,
108+ center: Point ( latitude: 32.62 , longitude: 36.10 ) ,
114109 scale: 200_000
115110 )
116111 return map
117112 } ( )
118113
119- /// When the information on the layers is returned, we map the titles into an array
120- /// to use as the datasource for the picker.
121- @Published var layerNames : [ String ] = [ ]
122-
123- /// The geometry that represents a rectangular shape that encompasses the area.
124- private( set) var completeExtent : Envelope ?
114+ /// The titles of the feature collection infos in the OGC API.
115+ @Published private( set) var featureCollectionTitles : [ String ] = [ ]
125116
126- /// This is a reference to the currently selected layer .
127- private( set) var selectedInfo : OGCFeatureCollectionInfo ?
117+ /// The OGC feature collection info from the OCG API .
118+ private( set) var featureCollectionInfos : [ String : OGCFeatureCollectionInfo ] = [ : ]
128119
129- /// This holds the data sent back by the server.
130- private var featureCollectionInfos : [ OGCFeatureCollectionInfo ] = [ ]
131-
132- /// This is a reference to the service that is loading the OGC API data into the application.
120+ /// The OGC API feature service.
133121 private var service : OGCFeatureService !
134122
135123 /// The query parameters to populate features from the OGC API service.
@@ -141,82 +129,57 @@ private extension BrowseOGCAPIFeatureServiceView {
141129 return queryParameters
142130 } ( )
143131
144- /// Returns a renderer for a specified geometry type.
132+ /// Returns a renderer with the appropriate symbol type for a geometry type.
145133 /// - Parameter geometryType: The geometry type.
146- /// - Returns: Returns a `SimpleRenderer` optional with the correct settings for the given geometry.
147- private func getRenderer ( withType geometryType: Geometry . Type ) -> SimpleRenderer ? {
148- var renderer : SimpleRenderer ?
134+ /// - Returns: A `SimpleRenderer` with the correct symbol for the given geometry.
135+ private func makeRenderer ( withType geometryType: Geometry . Type ) -> SimpleRenderer ? {
136+ let symbol : Symbol
149137 switch geometryType {
150- case is Point . Type :
151- renderer = SimpleRenderer (
152- symbol: SimpleMarkerSymbol (
153- style: . circle,
154- color: . blue,
155- size: 5
156- )
157- )
158- case is Multipoint . Type :
159- renderer = SimpleRenderer (
160- symbol: SimpleMarkerSymbol (
161- style: . circle,
162- color: . blue,
163- size: 5
164- )
165- )
138+ case is Point . Type , is Multipoint . Type :
139+ symbol = SimpleMarkerSymbol ( style: . circle, color: . blue, size: 5 )
166140 case is Polyline . Type :
167- renderer = SimpleRenderer (
168- symbol: SimpleLineSymbol (
169- style: . solid,
170- color: . blue,
171- width: 1
172- )
173- )
174- case is Polygon . Type :
175- renderer = SimpleRenderer (
176- symbol: SimpleFillSymbol (
177- style: . solid,
178- color: . blue,
179- outline: nil
180- )
181- )
141+ symbol = SimpleLineSymbol ( style: . solid, color: . blue, width: 1 )
142+ case is Polygon . Type , is Envelope . Type :
143+ symbol = SimpleFillSymbol ( style: . solid, color: . blue)
182144 default :
183- // This should never execute.
184- break
145+ return nil
185146 }
186- return renderer
147+ return SimpleRenderer ( symbol : symbol )
187148 }
188149
189150 /// Creates and loads the OGC API features service from a URL.
190151 /// - Parameter url: The URL of the OGC service.
191- /// - Returns: Returns a `OCGFeatureService` that has been loaded and initialized .
152+ /// - Returns: Returns a loaded `OCGFeatureService`.
192153 private func makeService( url: URL ) async throws -> OGCFeatureService {
193154 let service = OGCFeatureService ( url: url)
194155 try await service. load ( )
195156 if let serviceInfo = service. serviceInfo {
196- featureCollectionInfos = serviceInfo. featureCollectionInfos
197- layerNames = featureCollectionInfos. map ( \. title)
157+ let infos = serviceInfo. featureCollectionInfos
158+ featureCollectionTitles = infos. map ( \. title)
159+ // The sample assumes there is no duplicate titles in the service.
160+ // Collections with duplicate titles will be discarded.
161+ featureCollectionInfos = Dictionary (
162+ infos. map { ( $0. title, $0) } ,
163+ uniquingKeysWith: { ( title, _) in title }
164+ )
198165 }
199166 return service
200167 }
201168
202169 /// Loads OGC service for a URL so that it can be rendered on the map.
203- /// - Parameters:
204- /// - url: The URL of the OGC service.
205- func loadOGCFeatureData( url: URL ? ) async throws {
206- guard let url = url else { return }
170+ /// - Parameter url: The URL of the OGC service.
171+ func loadOGCFeatureData( url: URL ) async throws {
207172 service = try await makeService ( url: url)
208- try await displayLayer ( with: featureCollectionInfos [ 0 ] )
209- }
210-
211- /// Updates the selected info property for the users selection.
212- /// - Parameter selection: String with the name of the selected layer to display.
213- func update( for selection: String ) {
214- selectedInfo = featureCollectionInfos. first ( where: { $0. title == selection } )
173+ if let firstFeatureCollectionTitle = featureCollectionTitles. first,
174+ let info = featureCollectionInfos [ firstFeatureCollectionTitle] {
175+ try await displayLayer ( with: info)
176+ }
215177 }
216178
217- /// Loads and displays a feature layer from the OGC feature collection table.
179+ /// Populates and displays a feature layer from an OGC feature collection table.
218180 /// - Parameter info: The `OGCFeatureCollectionInfo` selected by user.
219181 func displayLayer( with info: OGCFeatureCollectionInfo ) async throws {
182+ map. removeAllOperationalLayers ( )
220183 let table = OGCFeatureCollectionTable ( featureCollectionInfo: info)
221184 // Set the feature request mode to manual (only manual is currently
222185 // supported). In this mode, you must manually populate the table -
@@ -226,17 +189,20 @@ private extension BrowseOGCAPIFeatureServiceView {
226189 using: queryParameters,
227190 clearCache: false
228191 )
229- completeExtent = info. extent
230192 let featureLayer = FeatureLayer ( featureTable: table)
231193 if let geometryType = table. geometryType {
232- featureLayer. renderer = getRenderer ( withType: geometryType)
194+ featureLayer. renderer = makeRenderer ( withType: geometryType)
233195 map. addOperationalLayer ( featureLayer)
234- selectedInfo = info
235196 }
236197 }
237198 }
238199}
239200
201+ private extension URL {
202+ /// The Daraa, Syria OGC API feature service URL.
203+ static var daraaService : URL { URL ( string: " https://demo.ldproxy.net/daraa " ) ! }
204+ }
205+
240206#Preview {
241207 BrowseOGCAPIFeatureServiceView ( )
242208}
0 commit comments