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. Tap 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

MainActivity.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
/* 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.arcgisruntime.sample.mapimagelayertables;

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

import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

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.Envelope;
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.Basemap;
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.Symbol;

public class MainActivity extends AppCompatActivity {

  private static final String TAG = MainActivity.class.getSimpleName();

  private MapView mMapView;
  private ListView mCommentListView;
  // objects that implement Loadable must be class fields to prevent being garbage collected before loading
  private ArcGISFeature mServiceRequestFeature;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // authentication with an API key or named user is required to access basemaps and other
    // location services
    ArcGISRuntimeEnvironment.setApiKey(BuildConfig.API_KEY);

    // inflate views from layout
    mMapView = findViewById(R.id.mapView);
    mCommentListView = findViewById(R.id.comment_list);

    // initialize list that will hold the comments
    List<String> commentList = new ArrayList<>();
    // initialize a feature list that will hold the corresponding features for each comment
    List<Feature> featureList = new ArrayList<>();
    // create a map with a topographic basemap
    ArcGISMap map = new ArcGISMap(BasemapStyle.ARCGIS_STREETS);

    // create a new ArcGISMapImageLayer with a Service Request Map Server and load tables and layers
    ArcGISMapImageLayer serviceRequestMapImageLayer = new ArcGISMapImageLayer(getString(R.string.map_service));
    serviceRequestMapImageLayer.loadTablesAndLayersAsync();

    // initialize graphics overlay
    GraphicsOverlay graphicsOverlay = new GraphicsOverlay();

    serviceRequestMapImageLayer.addDoneLoadingListener(() -> {
      if (serviceRequestMapImageLayer.getLoadStatus() == LoadStatus.LOADED) {
        // set initial viewpoint
        Envelope extent = serviceRequestMapImageLayer.getFullExtent();
        mMapView.setViewpoint(new Viewpoint(extent));

        // get the service request comments table from the map image layer
        ServiceFeatureTable commentsTable = serviceRequestMapImageLayer.getTables().get(0);
        // create query parameters to get all non-null service request comment records (features) from tables.
        QueryParameters queryParameters = new QueryParameters();
        queryParameters.setWhereClause("requestid <> '' AND comments <> ''");
        // query the table to get non-null records
        ListenableFuture<FeatureQueryResult> commentQueryResultFuture = commentsTable
            .queryFeaturesAsync(queryParameters, ServiceFeatureTable.QueryFeatureFields.LOAD_ALL);

        // get the feature query result when it is done
        commentQueryResultFuture.addDoneListener(() -> {
          try {
            FeatureQueryResult commentQueryResult = commentQueryResultFuture.get();
            // loop through the results to add the comments and features to the corresponding list
            for (Feature feature : commentQueryResult) {
              featureList.add(feature);
              commentList.add(feature.getAttributes().get("comments").toString());
            }
            // create array adapter with the queried comments
            ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, commentList);
            // add the adapter to the List View
            mCommentListView.setAdapter(adapter);
          } catch (InterruptedException | ExecutionException e) {
            Log.e(TAG, "Error getting  feature query result: " + e.getMessage());
          }
        });
      } else {
        Log.e(TAG, "Service request failed to load");
        Toast.makeText(this, "Service request failed to load", Toast.LENGTH_LONG).show();
      }
    });

    mCommentListView.setOnItemClickListener((parent, view, position, id) -> {
      // clear previous selections
      graphicsOverlay.getGraphics().clear();
      // get the comment clicked
      Feature selectedComment = featureList.get(position);

      // create a service feature table of the comments
      ServiceFeatureTable commentsTable = serviceRequestMapImageLayer.getTables().get(0);

      // get the relationship that defines related service request for features in the comments table
      RelationshipInfo commentsRelationshipInfo = commentsTable.getLayerInfo().getRelationshipInfos().get(0);
      // create query parameters to get the service request for features in the comments table
      RelatedQueryParameters relatedQueryParameters = new RelatedQueryParameters(commentsRelationshipInfo);
      relatedQueryParameters.setReturnGeometry(true);

      // query the comments table for related features
      ListenableFuture<List<RelatedFeatureQueryResult>> relatedRequestResult = commentsTable
          .queryRelatedFeaturesAsync((ArcGISFeature) selectedComment, relatedQueryParameters);
      relatedRequestResult.addDoneListener(() -> {
        try {
          // get the first result
          RelatedFeatureQueryResult result = relatedRequestResult.get().get(0);
          // get the first feature from the result and make sure it has a valid geometry
          mServiceRequestFeature = null;
          for (Feature relatedFeature : result) {
            if (!relatedFeature.getGeometry().isEmpty()) {
              mServiceRequestFeature = (ArcGISFeature) relatedFeature;
              break;
            }
          }
          // if a valid related feature is not found, warn the user and return
          if (mServiceRequestFeature == null) {
            Toast.makeText(this, "Related Feature not found", Toast.LENGTH_SHORT).show();
            return;
          }

          // load the related service feature request (so geometry is available)
          mServiceRequestFeature.loadAsync();
          mServiceRequestFeature.addDoneLoadingListener(() -> {
            if (mServiceRequestFeature.getLoadStatus() == LoadStatus.LOADED) {

              // get the service request geometry
              Point serviceRequestPoint = (Point) mServiceRequestFeature.getGeometry();
              // create a marker symbol to display the related feature
              Symbol selectedRequestedSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.CIRCLE,
                  Color.CYAN, 14);
              // create a graphic using the service request point and marker symbol
              Graphic requestGraphic = new Graphic(serviceRequestPoint, selectedRequestedSymbol);
              // add graphic to the map and zoom the map view
              graphicsOverlay.getGraphics().add(requestGraphic);
              mMapView.setViewpointCenterAsync(serviceRequestPoint, 150000);
            }
          });
        } catch (InterruptedException | ExecutionException e) {
          Log.e(TAG, "Related Request Failure: " + e.getMessage());
        }
      });
    });

    //add the map layer to the map
    map.getOperationalLayers().add(serviceRequestMapImageLayer);

    // add graphics overlay to map view
    mMapView.getGraphicsOverlays().add(graphicsOverlay);

    // set the map to be displayed in this view
    mMapView.setMap(map);
  }

  @Override
  protected void onPause() {
    super.onPause();
    mMapView.pause();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mMapView.resume();
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mMapView.dispose();
  }
}

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