Skip to content

Commit 311a7bc

Browse files
committed
update with Ting recommendations
1 parent 8ac0ad0 commit 311a7bc

File tree

1 file changed

+80
-114
lines changed

1 file changed

+80
-114
lines changed

Shared/Samples/Browse OGC API feature service/BrowseOGCAPIFeatureServiceView.swift

Lines changed: 80 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
104101
private 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

Comments
 (0)