com.nextgis.maplibui.overlay.EditLayerOverlay.java Source code

Java tutorial

Introduction

Here is the source code for com.nextgis.maplibui.overlay.EditLayerOverlay.java

Source

/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), bishop.dev@gmail.com
 * Author:   NikitaFeodonit, nfeodonit@yandex.com
 * Author:   Stanislav Petriakov, becomeglory@gmail.com
 * *****************************************************************************
 * Copyright (c) 2012-2016 NextGIS, info@nextgis.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nextgis.maplibui.overlay;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.widget.Toast;

import com.nextgis.maplib.api.IGISApplication;
import com.nextgis.maplib.datasource.Feature;
import com.nextgis.maplib.datasource.GeoEnvelope;
import com.nextgis.maplib.datasource.GeoGeometry;
import com.nextgis.maplib.datasource.GeoGeometryCollection;
import com.nextgis.maplib.datasource.GeoLineString;
import com.nextgis.maplib.datasource.GeoLinearRing;
import com.nextgis.maplib.datasource.GeoMultiLineString;
import com.nextgis.maplib.datasource.GeoMultiPoint;
import com.nextgis.maplib.datasource.GeoMultiPolygon;
import com.nextgis.maplib.datasource.GeoPoint;
import com.nextgis.maplib.datasource.GeoPolygon;
import com.nextgis.maplib.location.GpsEventSource;
import com.nextgis.maplib.map.MapDrawable;
import com.nextgis.maplib.map.VectorLayer;
import com.nextgis.maplib.util.Constants;
import com.nextgis.maplib.util.GeoConstants;
import com.nextgis.maplibui.R;
import com.nextgis.maplibui.api.DrawItem;
import com.nextgis.maplibui.api.EditEventListener;
import com.nextgis.maplibui.api.MapViewEventListener;
import com.nextgis.maplibui.api.Overlay;
import com.nextgis.maplibui.api.OverlayItem;
import com.nextgis.maplibui.fragment.BottomToolbar;
import com.nextgis.maplibui.mapui.MapViewOverlays;
import com.nextgis.maplibui.service.WalkEditService;
import com.nextgis.maplibui.util.ConstantsUI;
import com.nextgis.maplibui.util.ControlHelper;
import com.nextgis.maplibui.util.SettingsConstantsUI;

import java.util.ArrayList;
import java.util.List;

/**
 * The class for edit vector features
 */
public class EditLayerOverlay extends Overlay implements MapViewEventListener {
    /**
     * overlay mode constants
     */
    public final static int MODE_NONE = 0;
    public final static int MODE_HIGHLIGHT = 1;
    public final static int MODE_EDIT = 2;
    public final static int MODE_CHANGE = 3;
    public final static int MODE_EDIT_BY_WALK = 4;
    public final static int MODE_EDIT_BY_TOUCH = 5;

    /**
     * edit feature style
     */
    protected final static int LINE_WIDTH = 4;

    protected static final int mType = 3;

    /**
     * Store keys
     */
    protected static final String BUNDLE_KEY_MODE = "mode";
    protected static final String BUNDLE_KEY_HAS_EDITS = "has_edits";
    protected static final String BUNDLE_KEY_OVERLAY_POINT = "overlay_point";

    protected Paint mPaint;

    protected final float mTolerancePX;
    protected float mCanvasCenterX, mCanvasCenterY;

    protected MapDrawable mMap;
    protected Toolbar mTopToolbar;
    protected BottomToolbar mBottomToolbar;

    protected VectorLayer mLayer;
    protected Feature mFeature;

    protected List<DrawItem> mDrawItems;
    protected DrawItem mSelectedItem;

    protected int mMode;
    protected boolean mHasEdits;

    protected PointF mTempPointOffset;
    protected OverlayItem mOverlayPoint;

    protected List<EditEventListener> mListeners;
    protected WalkEditReceiver mReceiver;

    public EditLayerOverlay(Context context, MapViewOverlays mapViewOverlays) {
        super(context, mapViewOverlays);
        mLayer = null;
        mMode = MODE_NONE;

        mTolerancePX = context.getResources().getDisplayMetrics().density * ConstantsUI.TOLERANCE_DP;

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(LINE_WIDTH / 2);

        mDrawItems = new ArrayList<>();
        mListeners = new ArrayList<>();

        mMap = mMapViewOverlays.getMap();
        mMapViewOverlays.addListener(this);
        mOverlayPoint = new OverlayItem(mMap, 0, 0, getMarker());
        DrawItem.init(context);
    }

    public void setTopToolbar(final Toolbar toolbar) {
        mTopToolbar = toolbar;
    }

    public void setBottomToolbar(final BottomToolbar toolbar) {
        mBottomToolbar = toolbar;
    }

    public void addListener(EditEventListener listener) {
        if (mListeners != null && !mListeners.contains(listener)) {
            mListeners.add(listener);
        }
    }

    public void removeListener(EditEventListener listener) {
        if (mListeners != null) {
            mListeners.remove(listener);
        }
    }

    public void setSelectedLayer(VectorLayer layer) {
        clearDrawItems();
        clearGeometry();
        mLayer = layer;
    }

    public void setSelectedFeature(long featureId) {
        clearDrawItems();

        if (mLayer != null && featureId > Constants.NOT_FOUND) {
            mFeature = new Feature(featureId, mLayer.getFields());
            mFeature.setGeometry(mLayer.getGeometryForId(featureId));
        } else
            mFeature = null;

        updateMap();
    }

    public void setSelectedFeature(Feature feature) {
        clearDrawItems();
        mFeature = feature;
        updateMap();
    }

    public Feature getSelectedFeature() {
        return mFeature;
    }

    public long getSelectedFeatureId() {
        return mFeature == null ? Constants.NOT_FOUND : mFeature.getId();
    }

    public GeoGeometry getSelectedFeatureGeometry() {
        return mFeature == null ? null : mFeature.getGeometry();
    }

    public void showAllFeatures() {
        if (mLayer != null)
            mLayer.showAllFeatures();
    }

    protected void hideNavigationButton() {
        mBottomToolbar.setNavigationIcon(null);
        mBottomToolbar.setNavigationContentDescription(null);
    }

    protected void clearDrawItems() {
        mDrawItems.clear();
        mSelectedItem = null;
    }

    protected void clearGeometry() {
        mLayer = null;
        mFeature = null;
    }

    protected void clearAll() {
        clearGeometry();
        clearDrawItems();
    }

    protected void update() {
        setHasEdits(true);
        fillGeometry();
        updateMap();
    }

    protected void updateMap() {
        mMapViewOverlays.buffer();
        mMapViewOverlays.postInvalidate();
    }

    public boolean hasEdits() {
        return mHasEdits;
    }

    public void setHasEdits(boolean hasEdits) {
        mHasEdits = hasEdits;

        MenuItem item;
        if (mTopToolbar != null) {
            item = mTopToolbar.getMenu().findItem(R.id.menu_edit_save);
            if (item != null)
                ControlHelper.setEnabled(item, hasEdits);
        }

        if (mBottomToolbar != null && mSelectedItem != null) {
            // polygon rings
            boolean isOuterRingSelected = mSelectedItem.getSelectedRingId() == 0;
            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_add_new_inner_ring);
            if (item != null)
                ControlHelper.setEnabled(item, isOuterRingSelected);

            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_delete_inner_ring);
            if (item != null)
                ControlHelper.setEnabled(item, !isOuterRingSelected);

            // delete buttons
            boolean onlyOneItem = mDrawItems.size() > 1;
            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_delete_line);
            if (item != null)
                ControlHelper.setEnabled(item, onlyOneItem);

            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_delete_polygon);
            if (item != null)
                ControlHelper.setEnabled(item, onlyOneItem && isOuterRingSelected);

            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_delete_point);
            if (item != null) {
                boolean moreThanMin = true;
                int size = mSelectedItem.getSelectedRing() == null ? 0 : mSelectedItem.getSelectedRing().length;
                int minPoints = DrawItem.getMinPointCount(mLayer.getGeometryType()) * 2;

                switch (mLayer.getGeometryType()) {
                case GeoConstants.GTMultiPoint:
                    moreThanMin = onlyOneItem;
                    break;
                case GeoConstants.GTLineString:
                case GeoConstants.GTMultiLineString:
                    moreThanMin = size > minPoints;
                    break;
                case GeoConstants.GTPolygon:
                case GeoConstants.GTMultiPolygon:
                    moreThanMin = size > minPoints;
                    break;
                }

                ControlHelper.setEnabled(item, moreThanMin);
            }

            item = mBottomToolbar.getMenu().findItem(R.id.menu_edit_by_walk);
            if (item != null)
                ControlHelper.setEnabled(item, !hasEdits);
        }
    }

    public int getMode() {
        return mMode;
    }

    public void setMode(int mode) {
        if (mode != MODE_NONE && mLayer == null)
            return;

        mMode = mode;
        switch (mMode) {
        case MODE_NONE:
            clearAll();
            break;
        case MODE_HIGHLIGHT:
            if (mFeature != null)
                mLayer.showFeature(mFeature.getId());
            break;
        case MODE_EDIT:
            if (mFeature == null)
                break;

            mBottomToolbar.setTitle(null);
            mBottomToolbar.getMenu().clear();
            switch (mLayer.getGeometryType()) {
            case GeoConstants.GTPoint:
                mBottomToolbar.inflateMenu(R.menu.edit_point);
                break;
            case GeoConstants.GTMultiPoint:
                mBottomToolbar.inflateMenu(R.menu.edit_multipoint);
                break;
            case GeoConstants.GTLineString:
                mBottomToolbar.inflateMenu(R.menu.edit_line);
                break;
            case GeoConstants.GTMultiLineString:
                mBottomToolbar.inflateMenu(R.menu.edit_multiline);
                break;
            case GeoConstants.GTPolygon:
                mBottomToolbar.inflateMenu(R.menu.edit_polygon);
                break;
            case GeoConstants.GTMultiPolygon:
                mBottomToolbar.inflateMenu(R.menu.edit_multipolygon);
                break;
            case GeoConstants.GTGeometryCollection:
            default:
                break;
            }

            hideNavigationButton();

            for (EditEventListener listener : mListeners)
                listener.onStartEditSession();

            mLayer.hideFeature(mFeature.getId());
            break;
        case MODE_EDIT_BY_WALK:
            hideNavigationButton();

            for (EditEventListener listener : mListeners)
                listener.onStartEditSession();

            mBottomToolbar.setTitle(R.string.title_edit_by_walk);
            mBottomToolbar.getMenu().clear();
            mBottomToolbar.inflateMenu(R.menu.edit_by_walk);
            mBottomToolbar.setOnMenuItemClickListener(new BottomToolbar.OnMenuItemClickListener() {
                @Override
                public boolean onMenuItemClick(MenuItem menuItem) {
                    if (menuItem.getItemId() == R.id.menu_settings) {
                        IGISApplication app = (IGISApplication) ((Activity) mContext).getApplication();
                        app.showSettings(SettingsConstantsUI.ACTION_PREFS_LOCATION);
                    }

                    return true;
                }
            });

            startGeometryByWalk();
            break;
        case MODE_EDIT_BY_TOUCH:
            hideNavigationButton();
            mBottomToolbar.setTitle(R.string.title_edit_by_touch);
            mBottomToolbar.getMenu().clear();
            MenuItem apply = mBottomToolbar.getMenu().add(0, 0, 0, R.string.ok);
            apply.setIcon(R.drawable.ic_action_apply_dark);
            MenuItemCompat.setShowAsAction(apply, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
            mMapViewOverlays.setLockMap(true);
            break;
        }

        hideOverlayPoint();
        updateMap();
    }

    public void setOverlayPoint(MotionEvent event) {
        GeoPoint mapPoint = mMap.screenToMap(new GeoPoint(event.getX(), event.getY()));
        mapPoint.setCRS(GeoConstants.CRS_WEB_MERCATOR);
        mapPoint.project(GeoConstants.CRS_WGS84);
        mOverlayPoint.setCoordinates(mapPoint);
        mOverlayPoint.setVisible(true);
    }

    public void hideOverlayPoint() {
        mOverlayPoint.setVisible(false);
    }

    public void createPointFromOverlay() {
        clearDrawItems();

        float[] coordinates = new float[] { mOverlayPoint.getScreenX(), mOverlayPoint.getScreenY() };
        mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, coordinates);
        mDrawItems.add(mSelectedItem);

        update();
    }

    public boolean onOptionsItemSelected(int id) {
        if (mLayer == null || mSelectedItem == null)
            return false;

        boolean result = false;
        if (id == R.id.menu_edit_move_point_to_center) {
            result = moveSelectedPoint(mCanvasCenterX, mCanvasCenterY);
        } else if (id == R.id.menu_edit_move_point_to_current_location) {
            result = movePointToLocation();
        } else if (id == R.id.menu_edit_add_new_point) {
            result = addGeometryToMulti(GeoConstants.GTPoint);
        } else if (id == R.id.menu_edit_add_new_line) {
            result = addGeometryToMulti(GeoConstants.GTLineString);
        } else if (id == R.id.menu_edit_add_new_polygon) {
            result = addGeometryToMulti(GeoConstants.GTPolygon);
        } else if (id == R.id.menu_edit_add_new_inner_ring) {
            result = addInnerRing();
        } else if (id == R.id.menu_edit_delete_inner_ring) {
            result = deleteInnerRing();
        } else if (id == R.id.menu_edit_delete_line || id == R.id.menu_edit_delete_polygon) {
            result = deleteGeometry();
        } else if (id == R.id.menu_edit_delete_point) {
            result = deletePoint();
        } else if (id == R.id.menu_edit_by_walk) {
            result = true;
        } else if (id == R.id.menu_edit_by_touch) {
            result = true;
        }

        if (result)
            update();

        return result;
    }

    public void createNewGeometry() {
        clearDrawItems();

        float[] geoPoints = getNewGeometry(mLayer.getGeometryType());
        mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, geoPoints);
        mDrawItems.add(mSelectedItem);

        update();
    }

    protected float[] getNewGeometry(int geometryType) {
        float[] geoPoints;
        float add = mTolerancePX * 2;
        GeoPoint center = mMap.getFullScreenBounds().getCenter();

        switch (geometryType) {
        case GeoConstants.GTPoint:
        case GeoConstants.GTMultiPoint:
            geoPoints = new float[2];
            geoPoints[0] = (float) center.getX();
            geoPoints[1] = (float) center.getY();
            return geoPoints;
        case GeoConstants.GTLineString:
        case GeoConstants.GTMultiLineString:
            geoPoints = new float[4];
            geoPoints[0] = (float) center.getX() - add;
            geoPoints[1] = (float) center.getY() - add;
            geoPoints[2] = (float) center.getX() + add;
            geoPoints[3] = (float) center.getY() + add;
            return geoPoints;
        case GeoConstants.GTPolygon:
        case GeoConstants.GTMultiPolygon:
            geoPoints = new float[6];
            geoPoints[0] = (float) center.getX() - add;
            geoPoints[1] = (float) center.getY() - add;
            geoPoints[2] = (float) center.getX() - add;
            geoPoints[3] = (float) center.getY() + add;
            geoPoints[4] = (float) center.getX() + add;
            geoPoints[5] = (float) center.getY() + add;
            return geoPoints;
        case GeoConstants.GTLinearRing:
            geoPoints = new float[6];
            geoPoints[0] = (float) center.getX() + add;
            geoPoints[1] = (float) center.getY() + add;
            geoPoints[2] = (float) center.getX() - add;
            geoPoints[3] = (float) center.getY() + add;
            geoPoints[4] = (float) center.getX() - add;
            geoPoints[5] = (float) center.getY() - add;
            return geoPoints;
        default:
            return null;
        }
    }

    protected boolean moveSelectedPoint(float x, float y) {
        mSelectedItem.setSelectedPointCoordinates(x, y);
        return true;
    }

    protected boolean movePointToLocation() {
        Activity parent = (Activity) mContext;
        GpsEventSource gpsEventSource = ((IGISApplication) parent.getApplication()).getGpsEventSource();
        Location location = gpsEventSource.getLastKnownLocation();

        if (null != location) {
            //change to screen coordinates
            GeoPoint pt = new GeoPoint(location.getLongitude(), location.getLatitude());
            pt.setCRS(GeoConstants.CRS_WGS84);
            pt.project(GeoConstants.CRS_WEB_MERCATOR);
            GeoPoint screenPt = mMap.mapToScreen(pt);
            return moveSelectedPoint((float) screenPt.getX(), (float) screenPt.getY());
        } else
            Toast.makeText(parent, R.string.error_no_location, Toast.LENGTH_SHORT).show();

        return false;
    }

    protected boolean addGeometryToMulti(int geometryType) {
        //insert geometry in appropriate position
        switch (geometryType) {
        case GeoConstants.GTPoint:
        case GeoConstants.GTLineString:
        case GeoConstants.GTPolygon:
            float[] geoPoints = getNewGeometry(geometryType);
            mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, geoPoints);
            mDrawItems.add(mSelectedItem);
            break;
        }

        return true;
    }

    protected boolean addInnerRing() {
        mSelectedItem.addVertices(getNewGeometry(GeoConstants.GTLinearRing));
        mSelectedItem.setSelectedRing(mSelectedItem.getRingCount() - 1);
        mSelectedItem.setSelectedPoint(0);

        return true;
    }

    protected boolean deleteInnerRing() {
        if (mSelectedItem.getSelectedRingId() != 0) {
            mSelectedItem.deleteSelectedRing();
            return true;
        }

        return false;
    }

    protected boolean deleteGeometry() {
        mDrawItems.remove(mSelectedItem);
        selectLastItem();
        return true;
    }

    protected boolean deletePoint() {
        mSelectedItem.deleteSelectedPoint(mLayer);
        if (mSelectedItem.getRingCount() == 0) {
            mDrawItems.remove(mSelectedItem);
            selectLastItem();
        }

        return true;
    }

    protected void selectLastItem() {
        if (mDrawItems.size() > 0)
            mSelectedItem = mDrawItems.get(mDrawItems.size() - 1);
        else
            mSelectedItem = null;
    }

    public void newGeometryByWalk() {
        GeoGeometry geometry;
        switch (mLayer.getGeometryType()) {
        case GeoConstants.GTLineString:
            geometry = new GeoLineString();
            break;
        case GeoConstants.GTPolygon:
            geometry = new GeoPolygon();
            break;
        case GeoConstants.GTMultiLineString:
            GeoMultiLineString multiLine = new GeoMultiLineString();
            multiLine.add(new GeoLineString());
            geometry = multiLine;
            break;
        case GeoConstants.GTMultiPolygon:
            GeoMultiPolygon multiPolygon = new GeoMultiPolygon();
            multiPolygon.add(new GeoPolygon());
            geometry = multiPolygon;
            break;
        default:
            return;
        }

        mFeature = new Feature();
        mFeature.setGeometry(geometry);

        mDrawItems.clear();
        mSelectedItem = new DrawItem();
        mDrawItems.add(mSelectedItem);
    }

    protected void startGeometryByWalk() {
        // register broadcast events
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WalkEditService.WALKEDIT_CHANGE);
        mReceiver = new WalkEditReceiver();
        mContext.registerReceiver(mReceiver, intentFilter);

        if (WalkEditService.isServiceRunning(mContext))
            return;

        // start service if not started yet
        GeoGeometry geometry = mFeature.getGeometry();
        int selectedRing = mSelectedItem.getSelectedRingId();
        int selectedGeometry = mDrawItems.indexOf(mSelectedItem);

        switch (mLayer.getGeometryType()) {
        case GeoConstants.GTLineString:
            break;
        case GeoConstants.GTPolygon:
            GeoPolygon polygon = ((GeoPolygon) geometry);
            geometry = selectedRing == 0 ? polygon.getOuterRing() : polygon.getInnerRing(selectedRing - 1);
            break;
        case GeoConstants.GTMultiLineString:
            geometry = ((GeoMultiLineString) geometry).get(selectedGeometry);
            break;
        case GeoConstants.GTMultiPolygon:
            GeoPolygon selectedPolygon = ((GeoMultiPolygon) geometry).get(selectedGeometry);
            geometry = selectedRing == 0 ? selectedPolygon.getOuterRing()
                    : selectedPolygon.getInnerRing(selectedRing - 1);
            break;
        default:
            return;
        }

        Intent trackerService = new Intent(mContext, WalkEditService.class);
        trackerService.setAction(WalkEditService.ACTION_START);
        trackerService.putExtra(ConstantsUI.KEY_LAYER_ID, mLayer.getId());
        trackerService.putExtra(ConstantsUI.KEY_GEOMETRY, geometry);
        trackerService.putExtra(ConstantsUI.TARGET_CLASS, mContext.getClass().getName());
        mContext.startService(trackerService);
    }

    public void stopGeometryByWalk() {
        // stop service
        Intent trackerService = new Intent(mContext, WalkEditService.class);
        trackerService.setAction(WalkEditService.ACTION_STOP);
        mContext.stopService(trackerService);

        // unregister events
        if (null != mReceiver) {
            mContext.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }

    protected void fillGeometry() {
        GeoGeometry geometry;
        if (mLayer == null || mDrawItems.isEmpty() || mSelectedItem == null)
            return;

        switch (mLayer.getGeometryType()) {
        case GeoConstants.GTPoint:
            geometry = getBaseGeometry(GeoConstants.GTPoint, mSelectedItem);
            break;
        case GeoConstants.GTMultiPoint:
            geometry = new GeoMultiPoint();
            for (DrawItem drawItem : mDrawItems)
                ((GeoMultiPoint) geometry).add(getBaseGeometry(GeoConstants.GTPoint, drawItem));
            break;
        case GeoConstants.GTLineString:
            geometry = getBaseGeometry(GeoConstants.GTLineString, mSelectedItem);
            break;
        case GeoConstants.GTMultiLineString:
            geometry = new GeoMultiLineString();
            for (DrawItem drawItem : mDrawItems)
                ((GeoMultiLineString) geometry).add(getBaseGeometry(GeoConstants.GTLineString, drawItem));
            break;
        case GeoConstants.GTPolygon:
            geometry = getBaseGeometry(GeoConstants.GTPolygon, mSelectedItem);
            break;
        case GeoConstants.GTMultiPolygon:
            geometry = new GeoMultiPolygon();
            for (DrawItem drawItem : mDrawItems)
                ((GeoMultiPolygon) geometry).add(getBaseGeometry(GeoConstants.GTPolygon, drawItem));
            break;
        default:
            geometry = null;
            break;
        }

        mFeature.setGeometry(geometry);
    }

    protected GeoGeometry getBaseGeometry(int geometryType, DrawItem drawItem) {
        GeoPoint[] geoPoints;
        GeoGeometry geometry;

        switch (geometryType) {
        case GeoConstants.GTPoint:
            geoPoints = mMap.screenToMap(drawItem.getRing(0));
            geometry = new GeoPoint(geoPoints[0].getX(), geoPoints[0].getY());
            break;
        case GeoConstants.GTLineString:
            geometry = new GeoLineString();
            geoPoints = mMap.screenToMap(drawItem.getRing(0));
            for (GeoPoint geoPoint : geoPoints)
                ((GeoLineString) geometry).add(geoPoint);
            break;
        case GeoConstants.GTPolygon:
            geometry = new GeoPolygon();
            geoPoints = mMap.screenToMap(drawItem.getRing(0));
            for (GeoPoint geoPoint : geoPoints)
                ((GeoPolygon) geometry).add(geoPoint);

            for (int i = 1; i < drawItem.getRingCount(); i++) {
                geoPoints = mMap.screenToMap(drawItem.getRing(i));
                GeoLinearRing ring = new GeoLinearRing();
                for (GeoPoint geoPoint : geoPoints)
                    ring.add(geoPoint);

                ((GeoPolygon) geometry).addInnerRing(ring);
            }
            break;
        default:
            geometry = null;
            break;
        }

        return geometry;
    }

    protected float[] mapToScreen(GeoPoint[] geoPoints) {
        return mMapViewOverlays.getMap().mapToScreen(geoPoints);
    }

    @Override
    public void draw(Canvas canvas, MapDrawable mapDrawable) {
        if (mOverlayPoint.isVisible())
            drawOverlayItem(canvas, mOverlayPoint);

        if (mMode == MODE_CHANGE || mFeature == null)
            return;

        fillDrawItems(mFeature.getGeometry());

        for (DrawItem drawItem : mDrawItems) {
            boolean isSelected = mSelectedItem == drawItem;
            drawItem(drawItem, canvas, isSelected);
        }

        drawCross(canvas);
    }

    @Override
    public void drawOnPanning(Canvas canvas, PointF currentMouseOffset) {
        if (mOverlayPoint.isVisible())
            drawOnPanning(canvas, currentMouseOffset, mOverlayPoint);

        List<DrawItem> drawItems = mDrawItems;
        for (DrawItem drawItem : drawItems) {
            boolean isSelected = mSelectedItem == drawItem;

            if (mMode != MODE_CHANGE && mMode != MODE_EDIT_BY_TOUCH) {
                drawItem = drawItem.pan(currentMouseOffset);

                if (isSelected) {
                    drawItem.setSelectedRing(mSelectedItem.getSelectedRingId());
                    drawItem.setSelectedPoint(mSelectedItem.getSelectedPointId());
                }
            }

            drawItem(drawItem, canvas, isSelected);
        }

        drawCross(canvas);
    }

    @Override
    public void drawOnZooming(Canvas canvas, PointF currentFocusLocation, float scale) {
        if (mOverlayPoint.isVisible())
            drawOnZooming(canvas, currentFocusLocation, scale, mOverlayPoint, false);

        List<DrawItem> drawItems = mDrawItems;
        for (DrawItem drawItem : drawItems) {
            boolean isSelected = mSelectedItem == drawItem;
            drawItem = drawItem.zoom(currentFocusLocation, scale);

            if (isSelected) {
                drawItem.setSelectedRing(mSelectedItem.getSelectedRingId());
                drawItem.setSelectedPoint(mSelectedItem.getSelectedPointId());
            }

            drawItem(drawItem, canvas, isSelected);
        }

        drawCross(canvas);
    }

    public void fillDrawItems(GeoGeometry geom) {
        int lastItemsCount = mDrawItems.size();
        int lastSelectedItemPosition = mDrawItems.indexOf(mSelectedItem);
        DrawItem lastSelectedItem = mSelectedItem;
        mDrawItems.clear();

        if (null == geom) {
            Log.w(Constants.TAG, "the geometry is null in fillDrawItems method");
            return;
        }

        GeoPoint[] geoPoints = new GeoPoint[1];
        switch (geom.getType()) {
        case GeoConstants.GTPoint:
            geoPoints[0] = (GeoPoint) geom;
            mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, mapToScreen(geoPoints));
            mDrawItems.add(mSelectedItem);
            break;
        case GeoConstants.GTMultiPoint:
            GeoMultiPoint geoMultiPoint = (GeoMultiPoint) geom;
            for (int i = 0; i < geoMultiPoint.size(); i++) {
                geoPoints[0] = geoMultiPoint.get(i);
                mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, mapToScreen(geoPoints));
                mDrawItems.add(mSelectedItem);
            }
            break;
        case GeoConstants.GTLineString:
            fillDrawLine((GeoLineString) geom);
            break;
        case GeoConstants.GTMultiLineString:
            GeoMultiLineString multiLineString = (GeoMultiLineString) geom;
            for (int i = 0; i < multiLineString.size(); i++)
                fillDrawLine(multiLineString.get(i));
            break;
        case GeoConstants.GTPolygon:
            fillDrawPolygon((GeoPolygon) geom);
            break;
        case GeoConstants.GTMultiPolygon:
            GeoMultiPolygon multiPolygon = (GeoMultiPolygon) geom;
            for (int i = 0; i < multiPolygon.size(); i++)
                fillDrawPolygon(multiPolygon.get(i));
            break;
        case GeoConstants.GTGeometryCollection:
            GeoGeometryCollection collection = (GeoGeometryCollection) geom;
            for (int i = 0; i < collection.size(); i++) {
                GeoGeometry geoGeometry = collection.get(i);
                fillDrawItems(geoGeometry);
            }
            break;
        default:
            break;
        }

        if (mDrawItems.size() == lastItemsCount && lastSelectedItem != null
                && lastSelectedItemPosition != Constants.NOT_FOUND) {
            mSelectedItem = mDrawItems.get(lastSelectedItemPosition);
            mSelectedItem.setSelectedRing(lastSelectedItem.getSelectedRingId());
            mSelectedItem.setSelectedPoint(lastSelectedItem.getSelectedPointId());
        } else {
            mSelectedItem = mDrawItems.get(0);
        }
    }

    protected void fillDrawPolygon(GeoPolygon polygon) {
        mSelectedItem = new DrawItem();

        fillDrawRing(polygon.getOuterRing());
        for (int i = 0; i < polygon.getInnerRingCount(); i++)
            fillDrawRing(polygon.getInnerRing(i));

        mDrawItems.add(mSelectedItem);
    }

    protected void fillDrawLine(GeoLineString lineString) {
        GeoPoint[] geoPoints = lineString.getPoints().toArray(new GeoPoint[lineString.getPointCount()]);
        float[] points = mapToScreen(geoPoints);

        mSelectedItem = new DrawItem(DrawItem.TYPE_VERTEX, points);
        mDrawItems.add(mSelectedItem);

        if (points.length < 2)
            return;

        float[] edgePoints = new float[points.length - 2];
        for (int i = 0; i < points.length - 2; i++)
            edgePoints[i] = (points[i] + points[i + 2]) * .5f;

        mSelectedItem.addEdges(edgePoints);
    }

    protected void fillDrawRing(GeoLinearRing geoLinearRing) {
        GeoPoint[] geoPoints = geoLinearRing.getPoints().toArray(new GeoPoint[geoLinearRing.getPointCount()]);
        float[] points = mapToScreen(geoPoints);
        float[] edgePoints = new float[points.length];

        if (points.length == 0 || edgePoints.length < 2)
            return;

        for (int i = 0; i < points.length - 2; i++)
            edgePoints[i] = (points[i] + points[i + 2]) * .5f;

        edgePoints[edgePoints.length - 2] = (points[0] + points[points.length - 2]) * .5f;
        edgePoints[edgePoints.length - 1] = (points[1] + points[points.length - 1]) * .5f;

        mSelectedItem.addVertices(points);
        mSelectedItem.addEdges(edgePoints);
    }

    protected void drawCross(Canvas canvas) {
        if (mMode != MODE_EDIT) {
            return;
        }
        mCanvasCenterX = canvas.getWidth() / 2;
        mCanvasCenterY = canvas.getHeight() / 2;

        canvas.drawLine(mCanvasCenterX - mTolerancePX, mCanvasCenterY, mCanvasCenterX + mTolerancePX,
                mCanvasCenterY, mPaint);
        canvas.drawLine(mCanvasCenterX, mCanvasCenterY - mTolerancePX, mCanvasCenterX,
                mCanvasCenterY + mTolerancePX, mPaint);
    }

    protected void drawItem(DrawItem drawItem, Canvas canvas, boolean isSelected) {
        isSelected = isSelected && mMode == MODE_EDIT;
        switch (mFeature.getGeometry().getType()) {
        case GeoConstants.GTPoint:
        case GeoConstants.GTMultiPoint:
            drawItem.drawPoints(canvas, isSelected);
            break;
        case GeoConstants.GTLineString:
        case GeoConstants.GTMultiLineString:
        case GeoConstants.GTPolygon:
        case GeoConstants.GTMultiPolygon:
            boolean closed = mFeature.getGeometry().getType() == GeoConstants.GTPolygon
                    || mFeature.getGeometry().getType() == GeoConstants.GTMultiPolygon;
            drawItem.drawLines(canvas, isSelected, mMode == MODE_EDIT || mMode == MODE_CHANGE, mMode == MODE_EDIT,
                    closed);
            break;
        default:
            break;
        }
    }

    @Override
    public Bundle onSaveState() {
        Bundle bundle = super.onSaveState();
        bundle.putInt(BUNDLE_KEY_TYPE, mType);
        bundle.putInt(BUNDLE_KEY_MODE, mMode);
        bundle.putBoolean(BUNDLE_KEY_HAS_EDITS, mHasEdits);

        if (mOverlayPoint.isVisible())
            bundle.putSerializable(BUNDLE_KEY_OVERLAY_POINT, mOverlayPoint.getCoordinates(GeoConstants.CRS_WGS84));

        return bundle;
    }

    @Override
    public void onRestoreState(Bundle bundle) {
        if (null != bundle && mType == bundle.getInt(BUNDLE_KEY_TYPE, 0)) {
            mMode = bundle.getInt(BUNDLE_KEY_MODE);
            mHasEdits = bundle.getBoolean(BUNDLE_KEY_HAS_EDITS);

            if (bundle.containsKey(BUNDLE_KEY_OVERLAY_POINT)) {
                GeoPoint point = (GeoPoint) bundle.getSerializable(BUNDLE_KEY_OVERLAY_POINT);

                if (point != null) {
                    point.setCRS(GeoConstants.CRS_WGS84);
                    mOverlayPoint.setCoordinates(point);
                    mOverlayPoint.setVisible(true);
                }
            }
        }

        super.onRestoreState(bundle);
    }

    @Override
    public void onLongPress(MotionEvent event) {
        //TODO: do we need some actions on long press on point or geometry?
    }

    /**
     * Select point in current geometry or new geometry from current layer
     *
     * @param event Motion event
     */
    @Override
    public void onSingleTapUp(MotionEvent event) {
    }

    public boolean selectGeometryInScreenCoordinates(float x, float y) {
        if (null == mLayer)
            return false;

        double dMinX = x - mTolerancePX;
        double dMaxX = x + mTolerancePX;
        double dMinY = y - mTolerancePX;
        double dMaxY = y + mTolerancePX;
        GeoEnvelope screenEnv = new GeoEnvelope(dMinX, dMaxX, dMinY, dMaxY);

        //1. search current geometry point
        if (null != mFeature && null != mFeature.getGeometry()) {
            for (DrawItem drawItem : mDrawItems) {
                if (drawItem.intersectsVertices(screenEnv)) {
                    mSelectedItem = drawItem;
                    setHasEdits(mHasEdits);
                    updateMap();
                    return false;
                }

                if (drawItem.intersectsEdges(screenEnv)) {
                    mSelectedItem = drawItem;
                    update();
                    return true;
                }
            }

            if (mHasEdits) // prevent select another geometry before saving current edited one. TODO toast?
                return false;
        }

        //2. select another geometry
        GeoEnvelope mapEnv = mMapViewOverlays.screenToMap(screenEnv);
        if (null == mapEnv)
            return false;

        List<Long> items = mLayer.query(mapEnv);
        if (items.isEmpty())
            return false;

        long previousFeatureId = Constants.NOT_FOUND;
        if (null != mFeature)
            previousFeatureId = mFeature.getId();

        for (int i = 0; i < items.size(); i++) { // FIXME hack for bad RTree cache
            long featureId = items.get(i);
            GeoGeometry geometry = mLayer.getGeometryForId(featureId);
            if (geometry != null && previousFeatureId != featureId) {
                mFeature = new Feature(featureId, mLayer.getFields());
                mFeature.setGeometry(mLayer.getGeometryForId(featureId));
            }
        }

        if (mFeature == null || previousFeatureId == mFeature.getId())
            return false;

        if (mMode == MODE_HIGHLIGHT) {
            mMapViewOverlays.invalidate();
            return false;
        }

        // this part should execute only in edit mode
        if (previousFeatureId == Constants.NOT_FOUND)
            mLayer.hideFeature(mFeature.getId());
        else
            mLayer.swapFeaturesVisibility(previousFeatureId, mFeature.getId());

        return false;
    }

    @Override
    public void panStart(MotionEvent event) {
        if (mMode == MODE_EDIT) {
            if (null != mFeature && null != mFeature.getGeometry()) {
                //check if we are near selected point
                double dMinX = event.getX() - mTolerancePX * 2 - DrawItem.mAnchorTolerancePX;
                double dMaxX = event.getX() + mTolerancePX;
                double dMinY = event.getY() - mTolerancePX * 2 - DrawItem.mAnchorTolerancePX;
                double dMaxY = event.getY() + mTolerancePX;
                GeoEnvelope screenEnv = new GeoEnvelope(dMinX, dMaxX, dMinY, dMaxY);

                if (mSelectedItem.isTapNearSelectedPoint(screenEnv)) {
                    PointF tempPoint = mSelectedItem.getSelectedPoint();
                    mTempPointOffset = new PointF(tempPoint.x - event.getX(), tempPoint.y - event.getY());
                    mMapViewOverlays.setLockMap(true);
                    mMode = MODE_CHANGE;
                }
            }
        }
    }

    @Override
    public void panMoveTo(MotionEvent e) {
        if (mMode == MODE_CHANGE) {
            mSelectedItem.setSelectedPointCoordinates(e.getX() + mTempPointOffset.x, e.getY() + mTempPointOffset.y);
        }

        if (mMode == MODE_EDIT_BY_TOUCH) {
            mSelectedItem.insertNewPoint(mSelectedItem.getSelectedPointId(), e.getX(), e.getY());
        }
    }

    @Override
    public void panStop() {
        if (mMode == MODE_CHANGE) {
            mMapViewOverlays.setLockMap(false);
            mMode = MODE_EDIT;

            update();
        }

        if (mMode == MODE_EDIT_BY_TOUCH)
            fillGeometry();
    }

    @Override
    public void onLayerAdded(int id) {

    }

    @Override
    public void onLayerDeleted(int id) { // TODO do we need this?
        //if delete edited layer cancel edit session
        if (null != mLayer && mLayer.getId() == id) {
            setHasEdits(false);
            setMode(MODE_NONE);
        }
    }

    @Override
    public void onLayerChanged(int id) {

    }

    @Override
    public void onExtentChanged(float zoom, GeoPoint center) {

    }

    @Override
    public void onLayersReordered() {

    }

    @Override
    public void onLayerDrawFinished(int id, float percent) {

    }

    @Override
    public void onLayerDrawStarted() {

    }

    protected Bitmap getMarker() {
        float scaledDensity = mContext.getResources().getDisplayMetrics().scaledDensity;
        int size = (int) (12 * scaledDensity);
        Bitmap marker = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(marker);
        Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
        //noinspection deprecation
        p.setColor(mContext.getResources().getColor(R.color.accent));
        p.setAlpha(128);
        c.drawOval(new RectF(0, 0, size * 3 / 4, size * 3 / 4), p);
        return marker;
    }

    public class WalkEditReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            GeoGeometry geometry = (GeoGeometry) intent.getSerializableExtra(ConstantsUI.KEY_GEOMETRY);
            setGeometryFromWalkEdit(geometry);
            mMapViewOverlays.postInvalidate();
        }
    }

    public void setGeometryFromWalkEdit(GeoGeometry geometry) {
        int selectedGeometry = mDrawItems.indexOf(mSelectedItem);
        int selectedRing = mSelectedItem.getSelectedRingId();

        switch (mLayer.getGeometryType()) {
        case GeoConstants.GTLineString:
            mFeature.setGeometry(geometry);
            break;
        case GeoConstants.GTMultiLineString:
            GeoMultiLineString multiLine = (GeoMultiLineString) mFeature.getGeometry();
            multiLine.set(selectedGeometry, geometry);
            mFeature.setGeometry(multiLine);
            break;
        case GeoConstants.GTPolygon:
            GeoPolygon polygon = (GeoPolygon) mFeature.getGeometry();

            if (selectedRing == 0)
                polygon.setOuterRing((GeoLinearRing) geometry);
            else
                polygon.setInnerRing(selectedRing - 1, (GeoLinearRing) geometry);

            mFeature.setGeometry(polygon);
            break;
        case GeoConstants.GTMultiPolygon:
            GeoMultiPolygon multiPolygon = (GeoMultiPolygon) mFeature.getGeometry();
            GeoPolygon selectedPolygon = multiPolygon.get(selectedGeometry);
            selectedPolygon.setOuterRing((GeoLinearRing) geometry);

            if (selectedRing == 0)
                selectedPolygon.setOuterRing((GeoLinearRing) geometry);
            else
                selectedPolygon.setInnerRing(selectedRing - 1, (GeoLinearRing) geometry);

            multiPolygon.set(selectedGeometry, selectedPolygon);
            mFeature.setGeometry(multiPolygon);
            break;
        }
    }
}