Skip to content

Commit 7e8b35c

Browse files
committed
Add source code.
1 parent f0c8612 commit 7e8b35c

File tree

1 file changed

+236
-2
lines changed

1 file changed

+236
-2
lines changed

Shared/Samples/Query with CQL filters/QueryWithCQLFiltersView.swift

Lines changed: 236 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,244 @@ import ArcGIS
1616
import SwiftUI
1717

1818
struct QueryWithCQLFiltersView: View {
19-
@State private var map = Map(basemapStyle: .arcGISTopographic)
19+
/// The view model for the sample.
20+
@StateObject private var model = Model()
21+
22+
/// The count of feature from the last feature query result.
23+
@State private var resultFeatureCount = 0
24+
25+
/// A Boolean value indicating whether the OCG feature collection table is currently being populated.
26+
@State private var isPopulatingFeatureTable = true
27+
28+
/// A Boolean value indicating whether the CQL Filters form is presented.
29+
@State private var isShowingCQLFiltersForm = false
30+
31+
/// The error shown in the error alert.
32+
@State private var error: Error?
2033

2134
var body: some View {
22-
MapView(map: map)
35+
MapViewReader { mapViewProxy in
36+
MapView(map: model.map)
37+
.overlay(alignment: .top) {
38+
Text(
39+
isPopulatingFeatureTable
40+
? "Populating the feature table..."
41+
: "Populated \(resultFeatureCount) features(s)."
42+
)
43+
.multilineTextAlignment(.center)
44+
.frame(maxWidth: .infinity, alignment: .center)
45+
.padding(8)
46+
.background(.regularMaterial, ignoresSafeAreaEdges: .horizontal)
47+
}
48+
.toolbar {
49+
ToolbarItem(placement: .bottomBar) {
50+
Button("CQL Filters") {
51+
isShowingCQLFiltersForm = true
52+
}
53+
.popover(isPresented: $isShowingCQLFiltersForm) {
54+
CQLQueryFiltersForm(model: model) {
55+
isPopulatingFeatureTable = true
56+
}
57+
.presentationDetents([.fraction(0.5)])
58+
.frame(idealWidth: 320, idealHeight: 380)
59+
}
60+
}
61+
}
62+
.task(id: isPopulatingFeatureTable) {
63+
guard isPopulatingFeatureTable else {
64+
return
65+
}
66+
defer { isPopulatingFeatureTable = false }
67+
68+
do {
69+
// Queries the feature table using the query parameters.
70+
let featureQueryResult = try await model.ogcFeatureCollectionTable
71+
.populateFromService(using: model.queryParameters, clearCache: true)
72+
73+
let queryResultFeatures = Array(featureQueryResult.features())
74+
resultFeatureCount = queryResultFeatures.count
75+
76+
// Sets the viewpoint to the extent of the query result.
77+
let geometries = queryResultFeatures.compactMap(\.geometry)
78+
if let combinedExtent = GeometryEngine.combineExtents(of: geometries) {
79+
await mapViewProxy.setViewpointGeometry(combinedExtent, padding: 20)
80+
}
81+
} catch {
82+
self.error = error
83+
}
84+
}
85+
.errorAlert(presentingError: $error)
86+
}
87+
}
88+
}
89+
90+
// MARK: - CQLQueryFiltersForm
91+
92+
private extension QueryWithCQLFiltersView {
93+
/// A form with filter controls for a CQL query.
94+
struct CQLQueryFiltersForm: View {
95+
/// The view model for the sample.
96+
@ObservedObject var model: Model
97+
98+
/// The action to perform when the "Apply" button is pressed.
99+
let onApply: () -> Void
100+
101+
/// The action to dismiss the view.
102+
@Environment(\.dismiss) private var dismiss
103+
104+
/// The attribute expression that defines features to be included in the query.
105+
@State private var selectedWhereClause = ""
106+
107+
/// The maximum number of features the query should return.
108+
@State private var maxFeatures = 1000
109+
110+
/// A Boolean value indicating whether the query includes a time extent.
111+
@State private var includesDateFilter = false
112+
113+
/// The start date of the query's time extent.
114+
@State private var selectedStartDate: Date = {
115+
let components = DateComponents(year: 2011, month: 6, day: 13)
116+
return Calendar.current.date(from: components)!
117+
}()
118+
119+
/// The end date of the query's time extent.
120+
@State private var selectedEndDate: Date = {
121+
let components = DateComponents(year: 2012, month: 1, day: 7)
122+
return Calendar.current.date(from: components)!
123+
}()
124+
125+
var body: some View {
126+
NavigationStack {
127+
Form {
128+
Picker("Where Clause", selection: $selectedWhereClause) {
129+
ForEach(model.sampleWhereClauses, id: \.self) { whereClause in
130+
Text(whereClause)
131+
}
132+
}
133+
.pickerStyle(.navigationLink)
134+
135+
LabeledContent("Max Features") {
136+
TextField("1000", value: $maxFeatures, format: .number)
137+
.multilineTextAlignment(.trailing)
138+
.onChange(of: maxFeatures) { newValue in
139+
maxFeatures = newValue == 0 ? 1 : abs(newValue)
140+
}
141+
}
142+
143+
Section {
144+
Toggle("Date Filter", isOn: $includesDateFilter)
145+
DatePicker(
146+
"Start Date",
147+
selection: $selectedStartDate,
148+
in: ...selectedEndDate,
149+
displayedComponents: [.date]
150+
)
151+
.disabled(!includesDateFilter)
152+
DatePicker(
153+
"End Date",
154+
selection: $selectedEndDate,
155+
in: selectedStartDate...,
156+
displayedComponents: [.date]
157+
)
158+
.disabled(!includesDateFilter)
159+
}
160+
}
161+
.navigationTitle("CQL Filters")
162+
.navigationBarTitleDisplayMode(.inline)
163+
.toolbar {
164+
ToolbarItem(placement: .cancellationAction) {
165+
Button("Cancel", role: .cancel) {
166+
dismiss()
167+
}
168+
}
169+
ToolbarItem(placement: .confirmationAction) {
170+
Button("Apply") {
171+
updateQueryParameters()
172+
onApply()
173+
dismiss()
174+
}
175+
}
176+
}
177+
}
178+
.onAppear {
179+
// Sets the control's initial state using the values from the last query.
180+
selectedWhereClause = model.queryParameters.whereClause
181+
maxFeatures = model.queryParameters.maxFeatures
182+
183+
if let timeExtent = model.queryParameters.timeExtent {
184+
includesDateFilter = true
185+
selectedStartDate = timeExtent.startDate!
186+
selectedEndDate = timeExtent.endDate!
187+
}
188+
}
189+
}
190+
191+
/// Updates the model's query parameters using the values from the form.
192+
private func updateQueryParameters() {
193+
model.queryParameters.whereClause = selectedWhereClause
194+
model.queryParameters.maxFeatures = maxFeatures
195+
196+
model.queryParameters.timeExtent = includesDateFilter
197+
? TimeExtent(startDate: selectedStartDate, endDate: selectedEndDate)
198+
: nil
199+
}
200+
}
201+
}
202+
203+
// MARK: - Model
204+
205+
private extension QueryWithCQLFiltersView {
206+
/// The view model for the sample.
207+
final class Model: ObservableObject {
208+
/// A map with a topographic basemap.
209+
let map: Map = {
210+
let map = Map(basemapStyle: .arcGISTopographic)
211+
map.initialViewpoint = Viewpoint(latitude: 32.62, longitude: 36.10, scale: 20_000)
212+
return map
213+
}()
214+
215+
/// An OGC API - Features feature collection table for the "Daraa" test dataset.
216+
let ogcFeatureCollectionTable: OGCFeatureCollectionTable = {
217+
let table = OGCFeatureCollectionTable(
218+
url: URL(string: "https://demo.ldproxy.net/daraa")!,
219+
collectionID: "TransportationGroundCrv"
220+
)
221+
// Sets the feature request mode to manual. In this mode, the table must be populated
222+
// manually. Panning and zooming won't request features automatically.
223+
table.featureRequestMode = .manualCache
224+
return table
225+
}()
226+
227+
/// The sample where clause expressions to use with the query parameters.
228+
let sampleWhereClauses: [String] = [
229+
// An empty query.
230+
"",
231+
// A CQL2 TEXT query for features with an F_CODE property of "AP010".
232+
"F_CODE = 'AP010'",
233+
// A CQL2 JSON query for features with an F_CODE property of "AP010".
234+
#"{ "op": "=", "args": [ { "property": "F_CODE" }, "AP010" ] }"#,
235+
// A CQL2 TEXT query for features with an F_CODE attribute property similar to "AQ".
236+
"F_CODE LIKE 'AQ%'",
237+
// A CQL2 JSON query that combines the "before" and "eq" operators
238+
// with the logical "and" operator.
239+
#"{"op": "and", "args":[{ "op": "=", "args":[{ "property" : "F_CODE" }, "AP010"]}, { "op": "t_before", "args":[{ "property" : "ZI001_SDV"},"2013-01-01"]}]}"#
240+
]
241+
242+
/// The parameters for filtering the features returned form a query.
243+
let queryParameters: QueryParameters = {
244+
let queryParameters = QueryParameters()
245+
queryParameters.maxFeatures = 1000
246+
return queryParameters
247+
}()
248+
249+
init() {
250+
// Creates a feature layer to visualize the OGC API features and adds it to the map.
251+
let ogcFeatureLayer = FeatureLayer(featureTable: ogcFeatureCollectionTable)
252+
let redLineSymbol = SimpleLineSymbol(style: .solid, color: .red, width: 3)
253+
ogcFeatureLayer.renderer = SimpleRenderer(symbol: redLineSymbol)
254+
255+
map.addOperationalLayer(ogcFeatureLayer)
256+
}
23257
}
24258
}
25259

0 commit comments

Comments
 (0)