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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
/*
* 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.
*/
package com.esri.samples.query_with_cql_filters;
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment;
import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.data.FeatureQueryResult;
import com.esri.arcgisruntime.data.OgcFeatureCollectionTable;
import com.esri.arcgisruntime.data.QueryParameters;
import com.esri.arcgisruntime.data.ServiceFeatureTable;
import com.esri.arcgisruntime.geometry.Envelope;
import com.esri.arcgisruntime.geometry.Geometry;
import com.esri.arcgisruntime.geometry.GeometryEngine;
import com.esri.arcgisruntime.layers.FeatureLayer;
import com.esri.arcgisruntime.loadable.LoadStatus;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.BasemapStyle;
import com.esri.arcgisruntime.mapping.TimeExtent;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.symbology.SimpleLineSymbol;
import com.esri.arcgisruntime.symbology.SimpleRenderer;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.paint.Color;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class QueryWithCqlFiltersController {
@FXML private Button applyQueryButton;
@FXML private Button revertToInitialQueryButton;
@FXML private CheckBox timeExtentCheckBox;
@FXML private ComboBox<String> comboBox;
@FXML private DatePicker endDatePicker;
@FXML private DatePicker startDatePicker;
@FXML private Label featureNumberLabel;
@FXML private MapView mapView;
@FXML private ProgressIndicator progressIndicator;
@FXML private TextField textField;
private Envelope datasetExtent;
private OgcFeatureCollectionTable ogcFeatureCollectionTable; // keep loadable in scope to avoid garbage collection
public void initialize() {
try {
// authentication with an API key or named user is required to access basemaps and other location services
String yourAPIKey = System.getProperty("apiKey");
ArcGISRuntimeEnvironment.setApiKey(yourAPIKey);
// create a map with the topographic basemap style
ArcGISMap map = new ArcGISMap(BasemapStyle.ARCGIS_TOPOGRAPHIC);
// set the map to the mapview
mapView.setMap(map);
// prepare UI for interaction
populateUiWithCqlQueriesAndDateValues();
// define strings for the service URL and collection id
// note that the service defines the collection id which can be accessed via OgcFeatureCollectionInfo.getCollectionId().
String serviceUrl = "https://demo.ldproxy.net/daraa";
String collectionId = "TransportationGroundCrv";
// create an OGC feature collection table from the service url and collection id
ogcFeatureCollectionTable = new OgcFeatureCollectionTable(serviceUrl, collectionId);
// set the feature request mode to manual
// in this mode, the table must be manually populated - panning and zooming won't request features automatically
ogcFeatureCollectionTable.setFeatureRequestMode(ServiceFeatureTable.FeatureRequestMode.MANUAL_CACHE);
// create a feature layer from the feature collection table and
var featureLayer = new FeatureLayer(ogcFeatureCollectionTable);
// add the layer to the map
map.getOperationalLayers().add(featureLayer);
// once the feature layer has loaded, set a renderer to it to visualize the OGC API features and zoom to features
featureLayer.addDoneLoadingListener(() -> {
if (featureLayer.getLoadStatus() == LoadStatus.LOADED) {
var simpleRenderer = new SimpleRenderer(new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.DARKMAGENTA, 3));
featureLayer.setRenderer(simpleRenderer);
// get the extent of the dataset
datasetExtent = ogcFeatureCollectionTable.getExtent();
// display result of query on the map
setInitialQueryOnOgcFeatureTable();
} else {
// show an alert dialog if there is a loading failure
new Alert(Alert.AlertType.ERROR, "Failed to load feature layer: " +
featureLayer.getLoadError().getCause().getMessage()).show();
}
});
} catch (Exception e) {
// on any error, display the stack trace.
e.printStackTrace();
}
}
/**
* Sets the map view's viewpoint with the extent of the OGC feature collection table, and uses a basic query parameters to return 3000 features
* from the service.
*/
@FXML
private void setInitialQueryOnOgcFeatureTable() {
progressIndicator.setVisible(true);
// zoom to the dataset extent
if (datasetExtent != null && !datasetExtent.isEmpty()) {
mapView.setViewpointGeometryAsync(datasetExtent);
}
QueryParameters ogcFeatureExtentQueryParameters = new QueryParameters();
// create a query based on the current visible extent
ogcFeatureExtentQueryParameters.setGeometry(datasetExtent);
// set a limit of 3000 on the number of returned features per request, the default on some services could be as low as 10
ogcFeatureExtentQueryParameters.setMaxFeatures(3000);
try {
// populate and load the table with the query parameters
// set the clearCache parameter to true to clear existing table entries, set the outfields parameter to null to request all fields
ogcFeatureCollectionTable.populateFromServiceAsync(ogcFeatureExtentQueryParameters, true, null).addDoneListener(() -> {
progressIndicator.setVisible(false);
applyQueryButton.setDisable(false);
// display number of features returned
setFeatureNumberLabelText();
});
// handle UI
revertToInitialQueryButton.setDisable(true);
textField.setText("3000");
// reset combo box and show original prompt text
comboBox.getSelectionModel().clearSelection();
comboBox.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(String item, boolean empty) {
setText(item);
}
});
} catch (Exception exception) {
exception.printStackTrace();
new Alert(Alert.AlertType.ERROR, exception.getMessage()).show();
}
}
/**
* Populates features from query parameters, and displays the result on the map.
*/
@FXML
private void query() {
QueryParameters cqlQueryParameters = new QueryParameters();
// set the query parameter's where clause with the CQL query in the combo box
cqlQueryParameters.setWhereClause(comboBox.getSelectionModel().getSelectedItem());
// set the max features to the number entered in the text field
try {
cqlQueryParameters.setMaxFeatures(Integer.parseInt(textField.getText()));
} catch (NumberFormatException e) {
new Alert(Alert.AlertType.ERROR, "Value entered must be numerical").show();
}
// if the time extent checkbox is selected, retrieve the date selected from the date picker and set it to the
// query parameters time extent
if (timeExtentCheckBox.isSelected()) {
// get the value selected from the date picker
LocalDate startDate = startDatePicker.getValue();
LocalDate endDate = endDatePicker.getValue();
// convert the value into a calendar
Calendar start = new Calendar.Builder().setDate(startDate.getYear(), startDate.getMonthValue(), startDate.getDayOfMonth()).build();
Calendar end = new Calendar.Builder().setDate(endDate.getYear(), endDate.getMonthValue(), endDate.getDayOfMonth()).build();
// set the query parameters time extent
cqlQueryParameters.setTimeExtent(new TimeExtent(start, end));
}
// populate and load the table with the query parameters
// set the clearCache parameter to true to clear existing table entries and set the outfields parameter to null to request all fields
ListenableFuture<FeatureQueryResult> result = ogcFeatureCollectionTable.populateFromServiceAsync(cqlQueryParameters, true, null);
result.addDoneListener(() -> {
// display number of features returned
setFeatureNumberLabelText();
try {
// create a new list to store returned geometries in
List<Geometry> featureGeometryList = new ArrayList<>();
// iterate through each result to get its geometry and add it to the geometry list
result.get().iterator().forEachRemaining(feature -> featureGeometryList.add(feature.getGeometry()));
if (!featureGeometryList.isEmpty()) {
// zoom to the total extent of the geometries returned by the query
var totalExtent = GeometryEngine.combineExtents(featureGeometryList);
mapView.setViewpointGeometryAsync(totalExtent, 20);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
revertToInitialQueryButton.setDisable(false);
});
}
/**
* Handles user interaction with the date picker to ensure the end date time is not earlier than the start date.
*/
@FXML
private void handleDatePickerInteraction() {
if (startDatePicker.getValue().toEpochDay() > endDatePicker.getValue().toEpochDay()) {
endDatePicker.setValue(startDatePicker.getValue());
}
}
/**
* Binds the enabled status of the start and end date pickers with the checkbox selected property.
*/
@FXML
private void handleCheckBoxInteraction() {
startDatePicker.disableProperty().bind(timeExtentCheckBox.selectedProperty().not());
endDatePicker.disableProperty().bind(timeExtentCheckBox.selectedProperty().not());
}
/**
* Gets the total feature count from the OGC feature collection table and displays the number on the label.
*/
private void setFeatureNumberLabelText() {
// display number of features returned
featureNumberLabel.setText("Query returned: " + ogcFeatureCollectionTable.getTotalFeatureCount() + " features");
}
/**
* Add CQL query examples relevant to the sample data to the combobox and set the date picker to dates
* relevant to the data.
*/
private void populateUiWithCqlQueriesAndDateValues() {
ObservableList<String> cqlQueryList = FXCollections.observableArrayList();
// sample query 1: query for features with an F_CODE attribute property of "AP010".
cqlQueryList.add("{ \"op\": \"=\", \"args\": [ { \"property\": \"F_CODE\" }, \"AP010\" ] }"); // cql2-json query
// sample query 2: cql-text query for features with an F_CODE attribute property similar to "AQ".
cqlQueryList.add("F_CODE LIKE 'AQ%'");
// sample query 3: use cql2-json to combine "before" and "eq" operators with the logical "and" operator
cqlQueryList.add("{\"op\": \"and\", \"args\":[{ \"op\": \"=\", \"args\":[{ \"property\" : \"F_CODE\" }, \"AP010\"]}, { \"op\": \"t_before\", \"args\":[{ \"property\" : \"ZI001_SDV\"},\"2013-01-01\"]}]}");
// add sample CQL queries to the UI combobox
comboBox.getItems().addAll(cqlQueryList);
// set the date picker to a date relevant to the data
startDatePicker.setValue(LocalDate.of(2011, 6, 13));
endDatePicker.setValue(LocalDate.of(2012, 1, 7));
}
/**
* Disposes application resources.
*/
void terminate() {
if (mapView != null) {
mapView.dispose();
}
}
}