Map image layer tables

View on GitHubSample viewer app

Find features in a spatial table related to features in a non-spatial table.

Image of map image layer tables

Use case

The non-spatial tables contained by a map service may contain additional information about sublayer features. Such information can be accessed by traversing table relationships defined in the service.

How to use the sample

Once the map image layer loads, a list view will be populated with comment data from non-spatial features. Click on one of the comments to query related spatial features and display the first result on the map.

How it works

  1. Create an ArcGISMapImageLayer with the URL of a map image service.
  2. Load the layer and get one of its tables with imageLayer.getTables().get(index).
  3. To query the table, create a QueryParameters object. You can use queryParameters.setWhereClause(sqlQueryString) to filter the requested features.
  4. Use table.queryFeaturesAsync(parameters) to get a FeatureQueryResult object.
  5. The FeatureQueryResult is an iterable, so simply loop through it to get each result Feature.
  6. To query for related features, get the table's relationship info with table.getLayerInfo().getRelationshipInfos(). This returns a list of RelationshipInfo objects. Choose which one to base your query on.
  7. Now create RelatedQueryParameters passing in the RelationshipInfo. To query related features, use table.queryRelatedFeaturesAsync(feature, relatedQueryParameters).
  8. This returns a list of RelatedFeatureQueryResult objects, each containing a set of related features.

Relevant API

  • ArcGISFeature
  • ArcGISMapImageLayer
  • Feature
  • FeatureQueryResult
  • QueryParameters
  • RelatedFeatureQueryResult
  • RelatedQueryParameters
  • RelationshipInfo
  • ServiceFeatureTable

Additional information

You can use ArcGISMapImageLayer.loadTablesAndLayersAsync() to recursively load all sublayers and tables associated with a map image layer.

Tags

features, query, related features, search

Sample Code

MapImageLayerTablesSample.java
Use dark colors for code blocksCopy
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
/*
 * Copyright 2018 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.map_image_layer_tables;

import java.util.List;
import java.util.concurrent.ExecutionException;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import com.esri.arcgisruntime.ArcGISRuntimeEnvironment;
import com.esri.arcgisruntime.arcgisservices.RelationshipInfo;
import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.data.ArcGISFeature;
import com.esri.arcgisruntime.data.Feature;
import com.esri.arcgisruntime.data.FeatureQueryResult;
import com.esri.arcgisruntime.data.QueryParameters;
import com.esri.arcgisruntime.data.RelatedFeatureQueryResult;
import com.esri.arcgisruntime.data.RelatedQueryParameters;
import com.esri.arcgisruntime.data.ServiceFeatureTable;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.layers.ArcGISMapImageLayer;
import com.esri.arcgisruntime.loadable.LoadStatus;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.BasemapStyle;
import com.esri.arcgisruntime.mapping.Viewpoint;
import com.esri.arcgisruntime.mapping.view.Graphic;
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol;
import com.esri.arcgisruntime.symbology.SimpleRenderer;

public class MapImageLayerTablesSample extends Application {

  private MapView mapView;
  private GraphicsOverlay graphicsOverlay;
  private ServiceFeatureTable commentsTable;
  private ListView<Feature> commentsListView;
  private ArcGISFeature relatedFeature; // keep loadable in scope to avoid garbage collection

  /**
   * Starting point of this application.
   *
   * @param args arguments to this application.
   */
  public static void main(String[] args) {

    Application.launch(args);
  }

  @Override
  public void start(Stage stage) {

    try {
      // create a stack pane and application scene
      StackPane stackPane = new StackPane();
      Scene scene = new Scene(stackPane);
      scene.getStylesheets().add(getClass().getResource("/map_image_layer_tables/style.css").toExternalForm());

      // size the stage and add a title
      stage.setTitle("Map Image Layer Tables Sample");
      stage.setWidth(800);
      stage.setHeight(700);
      stage.setScene(scene);
      stage.show();

      // 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 streets basemap style
      ArcGISMap map = new ArcGISMap(BasemapStyle.ARCGIS_STREETS);

      // create and add a map image layer to the map's operational layers
      // the map image layer contains a feature table with related spatial and non-spatial comment features
      ArcGISMapImageLayer imageLayer = new ArcGISMapImageLayer(
          "https://sampleserver6.arcgisonline.com/arcgis/rest/services/ServiceRequest/MapServer");
      map.getOperationalLayers().add(imageLayer);

      // create a map view and set the map to it
      mapView = new MapView();
      mapView.setMap(map);

      // create a graphics overlay to show the related spatial features in
      graphicsOverlay = new GraphicsOverlay();
      mapView.getGraphicsOverlays().add(graphicsOverlay);

      // show the related graphics as cyan circles
      SimpleRenderer renderer = new SimpleRenderer();
      renderer.setSymbol(new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE, 0xFF00FFFF, 14));
      graphicsOverlay.setRenderer(renderer);

      // create a list view to show the non-spatial comment features
      commentsListView = new ListView<>();
      commentsListView.setMaxSize(200.0, 150.0);
      // show the comments attribute of the feature in the list
      commentsListView.setCellFactory(listView -> new ListCell<Feature>() {
        @Override
        protected void updateItem(Feature item, boolean empty) {
          super.updateItem(item, empty);
          if (item != null) {
            ArcGISFeature feature = (ArcGISFeature) item;
            setText((String) feature.getAttributes().get("comments"));
          }
        }
      });

      // when a comment is selected, query its related spatial features and show the first result on the map
      commentsListView.getSelectionModel().selectedItemProperty().addListener(observable -> showRelatedRequests());

      // when the layer is loaded, get the comment features
      imageLayer.addDoneLoadingListener(() -> {
        if (imageLayer.getLoadStatus() == LoadStatus.LOADED) {
          // zoom to the layer's extent
          mapView.setViewpoint(new Viewpoint(imageLayer.getFullExtent()));

          // get the comments feature table
          commentsTable = imageLayer.getTables().get(0);

          // create query parameters to get features that have non-empty comments
          QueryParameters queryParameters = new QueryParameters();
          queryParameters.setWhereClause("requestid <> '' AND comments <> ''");

          // query the comments table for features
          ListenableFuture<FeatureQueryResult> featureQuery = commentsTable.queryFeaturesAsync(queryParameters);
          featureQuery.addDoneListener(() -> {
            try {
              // add the returned features to the list view
              FeatureQueryResult results = featureQuery.get();
              for (Feature f : results) {
                commentsListView.getItems().addAll(f);
              }
            } catch (InterruptedException | ExecutionException ex) {
              new Alert(Alert.AlertType.ERROR, "Error querying comment features");
            }
          });
        } else {
          new Alert(Alert.AlertType.ERROR, imageLayer.getLoadError().getMessage()).show();
        }
      });

      // add the map view and list view to the stack pane
      stackPane.getChildren().addAll(mapView, commentsListView);
      StackPane.setAlignment(commentsListView, Pos.TOP_LEFT);
      StackPane.setMargin(commentsListView, new Insets(10, 0, 0, 10));

    } catch (Exception e) {
      // on any error, display the stack trace.
      e.printStackTrace();
    }
  }

  /**
   * Queries for spatial features related to the selected comment in the list view and shows the first result on the
   * map as a graphic.
   */
  private void showRelatedRequests() {
    // clear any previous results
    graphicsOverlay.getGraphics().clear();

    // get the selected comment feature from the list view
    Feature selectedCommentFeature = commentsListView.getSelectionModel().getSelectedItem();
    if (selectedCommentFeature != null) {

      // get the relationships info between layers in the table
      ArcGISFeature feature = (ArcGISFeature) selectedCommentFeature;
      List<RelationshipInfo> relationshipInfos = commentsTable.getLayerInfo().getRelationshipInfos();
      if (!relationshipInfos.isEmpty()) {

        // use the first relationship for the related query parameters
        RelationshipInfo commentsRelationshipInfo = relationshipInfos.get(0);
        RelatedQueryParameters relatedQueryParameters = new RelatedQueryParameters(commentsRelationshipInfo);
        relatedQueryParameters.setReturnGeometry(true);

        // query the table for related features using the parameters
        ListenableFuture<List<RelatedFeatureQueryResult>> relatedFeaturesRequest = commentsTable
            .queryRelatedFeaturesAsync(feature, relatedQueryParameters);
        relatedFeaturesRequest.addDoneListener(() -> {
          try {
            // loop through the returned related features
            List<RelatedFeatureQueryResult> results = relatedFeaturesRequest.get();
            if (!results.isEmpty()) {
              RelatedFeatureQueryResult relatedResult = results.get(0);
              if (relatedResult.iterator().hasNext()) {
                // get the first related feature
                relatedFeature = (ArcGISFeature) relatedResult.iterator().next();
                // load the feature and get its geometry to show as a graphic on the map
                relatedFeature.loadAsync();
                relatedFeature.addDoneLoadingListener(() -> {
                  if (relatedFeature.getLoadStatus() == LoadStatus.LOADED) {
                    Point point = (Point) relatedFeature.getGeometry();
                    Graphic graphic = new Graphic(point);
                    graphicsOverlay.getGraphics().add(graphic);
                    // zoom to the graphic
                    mapView.setViewpointCenterAsync(point, 40000);
                  }
                });
              }
            } else {
              new Alert(Alert.AlertType.INFORMATION, "No related features found").show();
            }
          } catch (InterruptedException | ExecutionException ex) {
            new Alert(Alert.AlertType.ERROR, "Failed to query relationships").show();
          }
        });
      }
    }

  }

  @Override
  public void stop() {

    // releases resources when the application closes
    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.