Feature layer update geometry

View on GitHubSample viewer app

Update a feature's location in an online feature service.

Image of feature layer update geometry

Use case

Sometimes users may want to edit features in an online feature service by moving them.

How to use the sample

Tap a feature to select it. Tap again to set the updated location for that feature. An alert will be shown confirming success or failure.

How it works

  1. Create a ServiceFeatureTable object from a URL.
  2. Create a FeatureLayer object from the ServiceFeatureTable.
  3. Identify layers from the feature layer to find features, and select a feature with selectFeatures(identifiedFeature).
  4. Change the selected feature's location using identifiedFeature.setGeometry(...).
  5. After the change, update the table on the server using applyEditsAsync().

Relevant API

  • Feature
  • FeatureLayer
  • ServiceFeatureTable

Tags

editing, feature layer, feature table, moving, service, updating

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
/* Copyright 2016 ESRI
 *
 * All rights reserved under the copyright laws of the United States
 * and applicable international laws, treaties, and conventions.
 *
 * You may freely redistribute and use this sample code, with or
 * without modification, provided you include the original copyright
 * notice and use restrictions.
 *
 * See the Sample code usage restrictions document for further information.
 *
 */
package com.esri.arcgisruntime.samples.featurelayerupdategeometry;

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

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import com.esri.arcgisruntime.ArcGISRuntimeEnvironment;
import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.data.ArcGISFeature;
import com.esri.arcgisruntime.data.FeatureTableEditResult;
import com.esri.arcgisruntime.data.ServiceFeatureTable;
import com.esri.arcgisruntime.data.ServiceGeodatabase;
import com.esri.arcgisruntime.geometry.GeometryEngine;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.layers.FeatureLayer;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.BasemapStyle;
import com.esri.arcgisruntime.mapping.GeoElement;
import com.esri.arcgisruntime.mapping.Viewpoint;
import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener;
import com.esri.arcgisruntime.mapping.view.IdentifyLayerResult;
import com.esri.arcgisruntime.mapping.view.MapView;

public class MainActivity extends AppCompatActivity {

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

  private MapView mMapView;
  private FeatureLayer mFeatureLayer;
  private boolean mFeatureSelected = true;
  // objects that implement Loadable must be class fields to prevent being garbage collected before loading
  private ArcGISFeature mIdentifiedFeature;

  @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);

    // get a reference to the map view
    mMapView = findViewById(R.id.mapView);

    // create a map with the streets basemap
    ArcGISMap map = new ArcGISMap(BasemapStyle.ARCGIS_STREETS);

    // set the map to be displayed in the MapView
    mMapView.setMap(map);

    // set an initial viewpoint
    mMapView.setViewpoint(new Viewpoint(34.057386, -117.191455, 100000000));

    // create and load the service geodatabase
    ServiceGeodatabase serviceGeodatabase =  new ServiceGeodatabase(getString(R.string.sample_service_url));
    serviceGeodatabase.loadAsync();
    serviceGeodatabase.addDoneLoadingListener(() -> {
      // create a feature layer using the first layer in the ServiceFeatureTable
      ServiceFeatureTable serviceFeatureTable = serviceGeodatabase.getTable(0);
      // create a feature layer from table
      mFeatureLayer = new FeatureLayer(serviceFeatureTable);
      // add the layer to the map
      mMapView.getMap().getOperationalLayers().add(mFeatureLayer);
    });

    Toast.makeText(getApplicationContext(), "Tap on a feature to select it.", Toast.LENGTH_LONG).show();

    // set an on touch listener to listen for click events
    mMapView.setOnTouchListener(new DefaultMapViewOnTouchListener(this, mMapView) {
      @Override
      public boolean onSingleTapConfirmed(MotionEvent motionEvent) {

        if (mFeatureSelected) {
          android.graphics.Point screenCoordinate = new android.graphics.Point(Math.round(motionEvent.getX()),
              Math.round(motionEvent.getY()));
          double tolerance = 20;

          // identify Layers to find features
          final ListenableFuture<IdentifyLayerResult> identifyFuture = mMapView
              .identifyLayerAsync(mFeatureLayer, screenCoordinate, tolerance, false, 1);
          identifyFuture.addDoneListener(() -> {
            try {
              // call get on the future to get the result
              IdentifyLayerResult layerResult = identifyFuture.get();
              List<GeoElement> resultGeoElements = layerResult.getElements();

              if (!resultGeoElements.isEmpty()) {
                if (resultGeoElements.get(0) instanceof ArcGISFeature) {
                  mIdentifiedFeature = (ArcGISFeature) resultGeoElements.get(0);
                  // select the identified feature
                  mFeatureLayer.selectFeature(mIdentifiedFeature);
                  mFeatureSelected = false;
                  Toast.makeText(getApplicationContext(), "Feature Selected. Tap on map to update its geometry.",
                      Toast.LENGTH_LONG).show();
                } else {
                  Toast.makeText(getApplicationContext(), "No Features Selected. Tap on a feature.", Toast.LENGTH_LONG)
                      .show();
                }
              }
            } catch (InterruptedException | ExecutionException e) {
              Log.e(TAG, "Update feature failed: " + e.getMessage());
            }
          });
        } else {
          Point movedPoint = mMapView.screenToLocation(new android.graphics.Point(Math.round(motionEvent.getX()), Math.round(motionEvent.getY())));
          final Point normalizedPoint = (Point) GeometryEngine.normalizeCentralMeridian(movedPoint);
          mIdentifiedFeature.addDoneLoadingListener(() -> {
            mIdentifiedFeature.setGeometry(normalizedPoint);
            final ListenableFuture<Void> updateFuture = mFeatureLayer.getFeatureTable().updateFeatureAsync(mIdentifiedFeature);
            updateFuture.addDoneListener(() -> {
              try {
                // track the update
                updateFuture.get();
                // apply edits once the update has completed
                if (updateFuture.isDone()) {
                  applyEditsToServer();
                  mFeatureLayer.clearSelection();
                  mFeatureSelected = true;
                } else {
                  Log.e(TAG, "Update feature failed!");
                }
              } catch (InterruptedException | ExecutionException e1) {
                Log.e(TAG, "Update feature failed: " + e1.getMessage());
              }
            });
          });
          mIdentifiedFeature.loadAsync();
        }
        return super.onSingleTapConfirmed(motionEvent);
      }
    });
  }

  /**
   * Applies edits to the FeatureService
   */
  private void applyEditsToServer() {
    final ListenableFuture<List<FeatureTableEditResult>> applyEditsFuture = ((ServiceFeatureTable) mFeatureLayer
        .getFeatureTable()).getServiceGeodatabase().applyEditsAsync();
    applyEditsFuture.addDoneListener(() -> {
      try {
        // get results of edit
        List<FeatureTableEditResult> featureEditResultsList = applyEditsFuture.get();
        Toast.makeText(this,
                "Applied Geometry Edits to Server. ObjectID: " + featureEditResultsList.get(0).getEditResult().get(0).getObjectId(),
                Toast.LENGTH_SHORT).show();
      } catch (InterruptedException | ExecutionException e) {
        Log.e(TAG, "Update feature failed: " + e.getMessage());
      }
    });
  }

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

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

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

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