Query with CQL filters

View on GitHub
Sample viewer app

Query data from an OGC API feature service using CQL filters.

Image of Query with CQL Filters

Use case

CQL (Common Query Language) is an OGC-created query language used to query for subsets of features. Use CQL filters to narrow geometry results from an OGC feature table.

How to use the sample

The sample loads displaying all features within the OGC API feature service. Select a sample CQL query from the drop down menu. Optionally, adjust the max features value in the text box, and toggle the time extent check box to query the data with a time extent. Press the "Apply query" button to see the query applied to the OGC API features shown on the map.

How it works

  1. Create an OgcFeatureCollectionTable object using a URL to an OGC API feature service and a collection ID.
  2. Create QueryParameters and set a CQL filter string to it using queryParameters.setWhereClause().
  3. Set the maximum amount of features to be returned with queryParamters.setMaxFeatures().
  4. Create a new TimeExtent from start and end date values, and set it to the queryParameters.setTimeExtent() method.
  5. Populate the OGC feature collection table using .populateFromServiceAsync() with the custom QueryParameters created in the previous steps.
  6. Use mapView.setViewpointGeometryAsync() with the OGC feature collection table extent to view the newly-queried features.

Relevant API

  • OgcFeatureCollectionTable
  • QueryParameters
  • TimeExtent

About the data

The Daraa, Syria test data is OpenStreetMap data converted to the Topographic Data Store schema of NGA.

Additional information

See the OGC API website for more information on the OGC API family of standards. See the CQL documentation to learn more about the common query language.

Tags

browse, catalog, common query language, CQL, feature table, filter, OGC, OGC API, query, service, web

Sample Code

QueryWithCqlFiltersController.javaQueryWithCqlFiltersSample.javamain.fxml
                                                                                                                                                                                                                                                                                                                         
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
311
312
313
/*
 * 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.ColorUtil;
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, ColorUtil.colorToArgb(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("F_CODE = 'AP010'"); // cql-text query
    cqlQueryList.add("{ \"eq\" : [ { \"property\" : \"F_CODE\" }, \"AP010\" ] }"); // cql-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 cql-json to combine "before" and "eq" operators with the logical "and" operator
    cqlQueryList.add("{\"and\":[{\"eq\":[{ \"property\" : \"F_CODE\" }, \"AP010\"]},{ \"before\":" +
      "[{ \"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();
    }
  }

}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.