Skip to content
This repository was archived by the owner on Jan 26, 2023. It is now read-only.
This repository was archived by the owner on Jan 26, 2023. It is now read-only.

sketch-editor, drag freehand polyline, no drop... #528

Open
@celoftis

Description

@celoftis

The sketch-editor appears to have a bug when dragging previously captured freehand polylines.

Problem summary: When editing an existing (i.e., previously saved) freehand polyline - it is never "dropped" after being dragged.

Problem:
Start the sketch-editor by passing in an existing freehand polyline graphic from the GraphicsOverlay. (mSketchEditor.start(existingFreehandPolygonGraphic.getGeometry(),sketchCreationMode.FREEHAND_LINE)) Tap to select the freehand polyline. Tap and hold to drag it to another location. Lift your finger to stop dragging - the polyline is never "dropped", i.e., it is still in a dragged state (dashed lines).

The code below was based on the sketch-editor example:

package com.esri.arcgisruntime.sample.sketcheditor;

import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.ImageButton;
import android.widget.TextView;

import com.esri.arcgisruntime.concurrent.ListenableFuture;
import com.esri.arcgisruntime.geometry.Geometry;
import com.esri.arcgisruntime.geometry.GeometryEngine;
import com.esri.arcgisruntime.geometry.GeometryType;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.geometry.SpatialReferences;
import com.esri.arcgisruntime.mapping.ArcGISMap;
import com.esri.arcgisruntime.mapping.Basemap;
import com.esri.arcgisruntime.mapping.popup.Popup;
import com.esri.arcgisruntime.mapping.view.Callout;
import com.esri.arcgisruntime.mapping.view.DefaultMapViewOnTouchListener;
import com.esri.arcgisruntime.mapping.view.Graphic;
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
import com.esri.arcgisruntime.mapping.view.IdentifyGraphicsOverlayResult;
import com.esri.arcgisruntime.mapping.view.MapView;
import com.esri.arcgisruntime.mapping.view.SketchCreationMode;
import com.esri.arcgisruntime.mapping.view.SketchEditConfiguration;
import com.esri.arcgisruntime.mapping.view.SketchEditor;
import com.esri.arcgisruntime.mapping.view.SketchGeometryChangedEvent;
import com.esri.arcgisruntime.mapping.view.SketchGeometryChangedListener;
import com.esri.arcgisruntime.symbology.SimpleFillSymbol;
import com.esri.arcgisruntime.symbology.SimpleLineSymbol;
import com.esri.arcgisruntime.symbology.SimpleMarkerSymbol;

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

public class MainActivity extends AppCompatActivity {

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

    private SimpleMarkerSymbol mPointSymbol;
    private SimpleLineSymbol mLineSymbol, mLineSymbol2;
    private SimpleFillSymbol mFillSymbol, mFillSymbol2;
    private MapView mMapView;
    private SketchEditor mSketchEditor;
    private GraphicsOverlay mGraphicsOverlay;
    private MenuItem cancel, stop, redo, undo;
    private Callout mCallout;
    private boolean bSketchEdit = false;
    private Graphic originalGraphic;

    private ImageButton mPointButton;
    private ImageButton mMultiPointButton;
    private ImageButton mPolylineButton;
    private ImageButton mPolygonButton;
    private ImageButton mFreehandLineButton;
    private ImageButton mFreehandPolygonButton;

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

        // define symbols
        mPointSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.Style.SQUARE, Color.RED, 20);
        mLineSymbol2 = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, 0xFFFF8800, 4);
        mLineSymbol = new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.MAGENTA, 4, SimpleLineSymbol.MarkerStyle.ARROW, SimpleLineSymbol.MarkerPlacement.END);
        mFillSymbol = new SimpleFillSymbol(SimpleFillSymbol.Style.CROSS, Color.CYAN, new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, Color.BLUE, 4));
        mFillSymbol2 = new SimpleFillSymbol(SimpleFillSymbol.Style.CROSS, Color.GREEN, mLineSymbol2);

        // inflate map view from layout
        mMapView = findViewById(R.id.mapView);
        // create a map with the Basemap Type topographic
        ArcGISMap map = new ArcGISMap(Basemap.Type.LIGHT_GRAY_CANVAS, 34.056295, -117.195800, 16);
        // set the map to be displayed in this view
        mMapView.setMap(map);

        mGraphicsOverlay = new GraphicsOverlay();
        mMapView.getGraphicsOverlays().add(mGraphicsOverlay);
        mMapView.setOnTouchListener(new MapSingleTapListener(this, mMapView));

        // create a new sketch editor and add it to the map view
        mSketchEditor = new SketchEditor();
        mMapView.setSketchEditor(mSketchEditor);
        mSketchEditor.addGeometryChangedListener(new SketchGeometryChangedListener() {
            @Override
            public void geometryChanged(SketchGeometryChangedEvent sketchGeometryChangedEvent) {
                if (mSketchEditor != null) {
                    cancel.setEnabled(true);
                    stop.setEnabled(bSketchEdit || mSketchEditor.isSketchValid()); //TODO better way?
                    undo.setEnabled(mSketchEditor.canUndo());
                    redo.setEnabled(mSketchEditor.canRedo());
                }

            }
        });

        // get buttons from layouts
        mPointButton = findViewById(R.id.pointButton);
        mMultiPointButton = findViewById(R.id.pointsButton);
        mPolylineButton = findViewById(R.id.polylineButton);
        mPolygonButton = findViewById(R.id.polygonButton);
        mFreehandLineButton = findViewById(R.id.freehandLineButton);
        mFreehandPolygonButton = findViewById(R.id.freehandPolygonButton);

        // add click listeners
        mPointButton.setOnClickListener(view -> createModePoint());
        mMultiPointButton.setOnClickListener(view -> createModeMultipoint());
        mPolylineButton.setOnClickListener(view -> createModePolyline());
        mPolygonButton.setOnClickListener(view -> createModePolygon());
        mFreehandLineButton.setOnClickListener(view -> createModeFreehandLine());
        mFreehandPolygonButton.setOnClickListener(view -> createModeFreehandPolygon());
    }

    /**
     * When the point button is clicked, reset other buttons, show the point button as selected, and start point
     * drawing mode.
     */
    private void createModePoint() {
        resetButtons();
        mPointButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.POINT);
    }

    /**
     * When the multipoint button is clicked, reset other buttons, show the multipoint button as selected, and start
     * multipoint drawing mode.
     */
    private void createModeMultipoint() {
        resetButtons();
        mMultiPointButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.MULTIPOINT);
    }

    /**
     * When the polyline button is clicked, reset other buttons, show the polyline button as selected, and start
     * polyline drawing mode.
     */
    private void createModePolyline() {
        resetButtons();
        mPolylineButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.POLYLINE);

    }

    /**
     * When the polygon button is clicked, reset other buttons, show the polygon button as selected, and start polygon
     * drawing mode.
     */
    private void createModePolygon() {
        resetButtons();
        mPolygonButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.POLYGON);
    }

    /**
     * When the freehand line button is clicked, reset other buttons, show the freehand line button as selected, and
     * start freehand line drawing mode.
     */
    private void createModeFreehandLine() {
        resetButtons();
        mFreehandLineButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.FREEHAND_LINE);
    }

    /**
     * When the freehand polygon button is clicked, reset other buttons, show the freehand polygon button as selected,
     * and enable freehand polygon drawing mode.
     */
    private void createModeFreehandPolygon() {
        resetButtons();
        mFreehandPolygonButton.setSelected(true);
        mSketchEditor.stop(); // will this fix a bug with changing draw modes w/o stopping
        mSketchEditor.start(SketchCreationMode.FREEHAND_POLYGON);
    }

    /**
     * When the undo button is clicked, undo the last event on the SketchEditor.
     */
    private void undo() {
        if (mSketchEditor.canUndo()) {
            mSketchEditor.undo();
        }
    }

    /**
     * When the redo button is clicked, redo the last undone event on the SketchEditor.
     */
    private void redo() {
        if (mSketchEditor.canRedo()) {
            mSketchEditor.redo();
        }
    }

    /**
     * When the stop button is clicked, check that sketch is valid. If so, get the geometry from the sketch, set its
     * symbol and add it to the graphics overlay.
     */
    private void stop() {
        bSketchEdit = false;
        originalGraphic = null;

        if (!mSketchEditor.isSketchValid()) {
            reportNotValid();
            mSketchEditor.stop();
            resetButtons();
            disableMenuOptions();
            return;
        }

        // get the geometry from sketch editor
        Geometry sketchGeometry = mSketchEditor.getGeometry();
//        mSketchEditor.stop();
//        resetButtons();

        if (sketchGeometry != null) {
            // create a graphic from the sketch editor geometry
            Map<String, Object> mapAttributes = new HashMap<String, Object>();
            mapAttributes.put("GeometryType", sketchGeometry.getGeometryType().name());
            mapAttributes.put("SketchCreationMode", mSketchEditor.getSketchCreationMode().name());
            Graphic graphic = new Graphic(sketchGeometry, mapAttributes);

            // assign a symbol based on geometry type
            if (graphic.getGeometry().getGeometryType() == GeometryType.POLYGON) {
                graphic.setSymbol(mFillSymbol);
            } else if (graphic.getGeometry().getGeometryType() == GeometryType.POLYGON
                    && mSketchEditor.getSketchCreationMode().equals(SketchCreationMode.FREEHAND_POLYGON)) {
                graphic.setSymbol(mFillSymbol2);
            } else if (graphic.getGeometry().getGeometryType() == GeometryType.POLYLINE
                    && mSketchEditor.getSketchCreationMode().equals(SketchCreationMode.FREEHAND_LINE)) {
                graphic.setSymbol(mLineSymbol2);
            } else if (graphic.getGeometry().getGeometryType() == GeometryType.POLYLINE) {
                graphic.setSymbol(mLineSymbol);
            } else if (graphic.getGeometry().getGeometryType() == GeometryType.POINT ||
                    graphic.getGeometry().getGeometryType() == GeometryType.MULTIPOINT) {
                graphic.setSymbol(mPointSymbol);
            }
            // add the graphic to the graphics overlay
            mGraphicsOverlay.getGraphics().add(graphic);
        }
        mSketchEditor.stop();
        resetButtons();
        disableMenuOptions();
    }

    /**
     * When the cancel button is clicked, cancel the SketchEditor.
     */
    private void cancel() {
        mSketchEditor.stop();
        resetButtons();
        if (bSketchEdit) {
            // add the graphic to the graphics overlay
            mGraphicsOverlay.getGraphics().add(originalGraphic);
            originalGraphic = null;
        }
        bSketchEdit = false;
        disableMenuOptions();
    }

    /**
     * Called if sketch is invalid. Reports to user why the sketch was invalid.
     */
    private void reportNotValid() {
        String validIf;
        if (mSketchEditor.getSketchCreationMode() == SketchCreationMode.POINT) {
            validIf = "Point only valid if it contains an x & y coordinate.";
        } else if (mSketchEditor.getSketchCreationMode() == SketchCreationMode.MULTIPOINT) {
            validIf = "Multipoint only valid if it contains at least one vertex.";
        } else if (mSketchEditor.getSketchCreationMode() == SketchCreationMode.POLYLINE
                || mSketchEditor.getSketchCreationMode() == SketchCreationMode.FREEHAND_LINE) {
            validIf = "Polyline only valid if it contains at least one part of 2 or more vertices.";
        } else if (mSketchEditor.getSketchCreationMode() == SketchCreationMode.POLYGON
                || mSketchEditor.getSketchCreationMode() == SketchCreationMode.FREEHAND_POLYGON) {
            validIf = "Polygon only valid if it contains at least one part of 3 or more vertices which form a closed ring.";
        } else {
            validIf = "No sketch creation mode selected.";
        }
        String report = "Sketch geometry invalid:\n" + validIf;
        Snackbar reportSnackbar = Snackbar.make(findViewById(R.id.toolbarInclude), report, Snackbar.LENGTH_INDEFINITE);
        reportSnackbar.setAction("Dismiss", view -> reportSnackbar.dismiss());
        TextView snackbarTextView = reportSnackbar.getView().findViewById(android.support.design.R.id.snackbar_text);
        snackbarTextView.setSingleLine(false);
        reportSnackbar.show();
        Log.e(TAG, report);
    }

    /**
     * De-selects all buttons.
     */
    private void resetButtons() {
        mPointButton.setSelected(false);
        mMultiPointButton.setSelected(false);
        mPolylineButton.setSelected(false);
        mPolygonButton.setSelected(false);
        mFreehandLineButton.setSelected(false);
        mFreehandPolygonButton.setSelected(false);
    }

    /**
     * Disable menu options
     */
    private void disableMenuOptions() {
        this.cancel.setEnabled(false);
        if (bSketchEdit) {
            this.stop.setEnabled(true); //TODO maybe do nothing here?
        } else {
            this.stop.setEnabled(false);
        }
        this.undo.setEnabled(false);
        this.redo.setEnabled(false);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.undo_redo_stop_menu, menu);
        this.cancel = menu.findItem(R.id.cancel);
        this.stop = menu.findItem(R.id.stop);
        this.undo = menu.findItem(R.id.undo);
        this.redo = menu.findItem(R.id.redo);
        disableMenuOptions();
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.undo) {
            undo();
        } else if (id == R.id.redo) {
            redo();
        } else if (id == R.id.stop) {
            stop();
        } else if (id == R.id.cancel) {
            cancel();
        }
        return super.onOptionsItemSelected(item);
    }

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

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

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

    class MapSingleTapListener extends DefaultMapViewOnTouchListener {

        public MapSingleTapListener(Context context, MapView mapView) {
            super(context, mapView);
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent e) {
            // get the screen point where user tapped
            android.graphics.Point screenPoint = new android.graphics.Point((int) e.getX(), (int) e.getY());
            final Point mapPoint = mMapView.screenToLocation(screenPoint);

            // convert to WGS84 for lat/lon format
            final Point wgs84Point = (Point) GeometryEngine.project(mapPoint, SpatialReferences.getWgs84());
            Log.d(TAG, String.format("Lat: %.6f, Lon: %.6f)", wgs84Point.getY(), wgs84Point.getX()));

            // create a selection tolerance
            int tolerance = 10;
            double mapTolerance = tolerance * mMapView.getUnitsPerDensityIndependentPixel();

            // identify graphics on the graphics overlay
            final ListenableFuture<IdentifyGraphicsOverlayResult> identifyGraphic = mMapView.identifyGraphicsOverlayAsync(mGraphicsOverlay, screenPoint, tolerance, false, 4);
            identifyGraphic.addDoneListener(new Runnable() {
                @Override
                public void run() {
                    try {
                        IdentifyGraphicsOverlayResult grOverlayResult = identifyGraphic.get();
                        List<Popup> popup = grOverlayResult.getPopups();
                        // get the list of graphics returned by identify graphic overlay
                        List<Graphic> graphic = grOverlayResult.getGraphics();
                        // get size of list in results
                        int identifyResultSize = graphic.size();
                        String message = "Tapped on " + identifyResultSize + " graphic%1$s";
                        if (!graphic.isEmpty()) { // show a toast message if graphic was returned
                            message = String.format(message, (identifyResultSize == 1 ? "" : "s"));
                            Log.d(TAG, message);
                            int i = 0;
                            for (Graphic gr : graphic) {
//                                Log.d(TAG, "(" + (i + 1) + ") " + gr.getGeometry().toJson());
//                                message += "\n(" + (i + 1) + "): " + gr.getAttributes().get("name").toString();
                                for (Map.Entry<String, Object> entry : gr.getAttributes().entrySet()) {
                                    message += "\n(" + (i + 1) + ") " + entry.getKey() + " = " + entry.getValue().toString();
                                }
                                i++;
//	                            Log.d(TAG, message);
                            }
                            // For now, just work with 1st Graphic object...
                            originalGraphic = graphic.get(0);
                            Map<String, Object> mapAttributes = originalGraphic.getAttributes();
                            if (mapAttributes != null && mapAttributes.containsKey("GeometryType") && mapAttributes.containsKey("SketchCreationMode")) {
                                Log.d(TAG, "GeometryType: " + mapAttributes.get("GeometryType").toString());
                                Log.d(TAG, "SketchCreationMode: " + mapAttributes.get("SketchCreationMode").toString());
                                GeometryType geometryType = GeometryType.valueOf(mapAttributes.get("GeometryType").toString());
                                SketchCreationMode sketchCreationMode = SketchCreationMode.valueOf(mapAttributes.get("sketchCreationMode").toString());
                                if (sketchCreationMode.equals(SketchCreationMode.POINT)) {
                                    mPointButton.setSelected(true);
                                } else if (sketchCreationMode.equals(SketchCreationMode.MULTIPOINT)) {
                                    mMultiPointButton.setSelected(true);
                                } else if (sketchCreationMode.equals(SketchCreationMode.POLYLINE)) {
                                    mPolylineButton.setSelected(true);
                                } else if (sketchCreationMode.equals(SketchCreationMode.POLYGON)) {
                                    mPolygonButton.setSelected(true);
                                } else if (sketchCreationMode.equals(SketchCreationMode.FREEHAND_LINE)) {
                                    mFreehandLineButton.setSelected(true);
                                } else if (sketchCreationMode.equals(SketchCreationMode.FREEHAND_POLYGON)) {
                                    mFreehandPolygonButton.setSelected(true);
                                }
                                SketchEditConfiguration sketchEditConfiguration = new SketchEditConfiguration();
                                mSketchEditor.start(originalGraphic.getGeometry(), sketchCreationMode, sketchEditConfiguration);
//                                mSketchEditor.start(originalGraphic.getGeometry(), sketchCreationMode);
//                                mSketchEditor.start(originalGraphic.getGeometry());
                                mGraphicsOverlay.getGraphics().remove(originalGraphic);
                                bSketchEdit = true;
                            }
                        }
                    } catch (InterruptedException | ExecutionException ie) {
                        ie.printStackTrace();
                    }
                }
            });
            return super.onSingleTapConfirmed(e);
        }
    }
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions