@@ -16,10 +16,244 @@ import ArcGIS
1616import SwiftUI
1717
1818struct 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