<commit_before>/*
 * Copyright (c) 2014, 2015 Data4All
 * 
 * <p>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
 * 
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 * 
 * <p>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 io.github.data4all.model.map;

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

import io.github.data4all.R;
import io.github.data4all.activity.AbstractActivity;
import io.github.data4all.activity.MapPreviewActivity;
import io.github.data4all.activity.MapViewActivity;
import io.github.data4all.logger.Log;
import io.github.data4all.model.data.AbstractDataElement;
import io.github.data4all.model.data.ClassifiedTag;
import io.github.data4all.model.data.Node;
import io.github.data4all.model.data.PolyElement;
import io.github.data4all.model.data.Tag;
import io.github.data4all.util.MapUtil;
import io.github.data4all.util.MathUtil;
import io.github.data4all.view.D4AMapView;

import org.osmdroid.bonuspack.overlays.Polygon;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.Projection;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.location.Location;
import android.preference.PreferenceManager;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ZoomControls;

/**
 * Polygon which is editable. It has an InfoWindow opened with a single tap, if
 * it is not editable and it is movable and rotatable with a TouchEvent, if it
 * is editable.
 * 
 * @author <NAME>
 * @author sbollen
 * @author burghardt
 *
 */
public class MapPolygon extends Polygon {

    private static final String TAG = "MapPolygon";
    private AbstractActivity activity;
    private D4AMapView mapView;
    private AbstractDataElement element;
    private boolean editable;

    // start time for touch event action_down
    private long timeStart;

    // True when the edit mode is active
    private boolean active;

    // the maximum time difference between action_down and action_up, so that
    // the mode will be changed
    private static final int TIME_DIFF = 200;

    // Default Stroke Color
    protected static final int DEFAULT_STROKE_COLOR = Color.BLUE;
    // Active Stroke Color
    protected static final int ACTIVE_STROKE_COLOR = Color.GREEN;

    // Fill Color for Polygons
    protected static final int DEFAULT_FILL_COLOR = Color.argb(100, 0, 0, 255);
    // Fill Color for activated Polygons
    protected static final int ACTIVE_FILL_COLOR = Color.argb(100, 50, 255, 50);

    // Maximum tolerance for touch events in meter to the element center
    private static final int TOLERANCE = 1;

    /**
     * Modes for edits which differ from touch events.
     */
    private static final int NONE = 0;
    private static final int MOVE = 1;
    private static final int ROTATE = 2;
    private int mode = NONE;

    /**
     * Start values for any movements.
     */
    private List<Point> startPos;

    /**
     * List of the geopoints in a coordinate system with the center as the
     * average of all Points.
     */
    private List<double[]> pointCoords;

    /**
     * the Parameter of the 3 start points to scale
     */
    private float startPosParameter;

    /**
     * Average of all Points saved as a Location.
     */
    private Location midLocation;

    /**
     * List of GeoPoints for editing the MapPolygon.
     */
    private List<GeoPoint> geoPointList;

    /**
     * Projection of the mapView.
     */
    private Projection pj;

    /**
     * MapCenter values.
     */
    private GeoPoint newMapcenter;
    private GeoPoint oldMapcenter;

    /**
     * Default constructor.
     * 
     * @param ctx
     *            the Context for the Overlay
     * 
     * @param mv
     *            the Mapview
     * 
     * @param ele
     *            the associated OsmElement
     */
    public MapPolygon(AbstractActivity ctx, D4AMapView mv,
            AbstractDataElement ele) {
        super(ctx);
        this.element = ele;
        this.activity = ctx;
        this.mapView = mv;
        this.editable = false;
        if (activity instanceof MapViewActivity) {
            mInfoWindow = new CustomInfoWindow(this.mapView, ele, this,
                    activity);
        } else {
            mInfoWindow = null;
        }
        this.setInfo();
    }

    /**
     * Set the info of the MapPolygon for the InfoWindow.
     */
    public void setInfo() {
        if (!element.getTags().keySet().isEmpty()
                && !element.getTags().values().isEmpty()) {
            Log.i(TAG, element.getTags().toString());
            final Tag tag = (Tag) element.getTags().keySet().toArray()[0];
            final String key = tag.getKey();
            final String value = element.getTags().get(tag);
            Log.i(TAG, tag.toString());
            setTitle(activity.getString(tag.getNameRessource()));
            if (tag instanceof ClassifiedTag) {
                setSubDescription(this.getLocalizedName(activity, key, value));
            } else {
                setSubDescription(element.getTags().get(tag));
            }
        }
    }

    /**
     * Get the localized name of the element to show in the InfoWindow.
     * 
     * @param context
     *            the context of the application
     * @param key
     *            the tag key
     * @param value
     *            the tag value
     * @return the localized name
     */
    public String getLocalizedName(Context context, String key, String value) {
        final Resources resources = context.getResources();
        final String s = "name_" + key + "_" + value;
        final int id = resources.getIdentifier(s.replace(":", "_"), "string",
                context.getPackageName());
        if (id == 0) {
            return null;
        } else {
            return resources.getString(id);
        }
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event, final MapView mapView) {
        super.onTouchEvent(event, mapView);

        if (editable) {
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                pj = mapView.getProjection();
                timeStart = System.currentTimeMillis();
                if (active) {
                    mode = MOVE;
                    geoPointList = this.getPoints();
                    oldMapcenter = (GeoPoint) mapView.getMapCenter();
                    startPos = new ArrayList<Point>();
                    startPos.add(new Point((int) event.getX(0), (int) event
                            .getY(0)));
                    Log.d(TAG, "action_down at point: " + event.getX(0) + " "
                            + event.getY(0));
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.d(TAG, "more than one pointer on screen");
                if (active) {
                    mode = ROTATE;
                    saveGeoPoints();
                    startPos = new ArrayList<Point>();
                    startPos.add(new Point((int) event.getX(0), (int) event
                            .getY(0)));
                    startPos.add(new Point((int) event.getX(1), (int) event
                            .getY(1)));
                    if (event.getPointerCount() == 3) {
                        startPos.add(new Point((int) event.getX(2), (int) event
                                .getY(2)));
                        startPosParameter = MathUtil.perimeter(startPos);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "action_up");
                if (active) {
                    // set the new information to the element
                    ((PolyElement) element).setNodesFromGeoPoints(geoPointList);
                }
                if (Math.abs(timeStart - System.currentTimeMillis()) < TIME_DIFF
                        && isTapped(event)) {
                    changeMode();
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "action_move");
                if (active) {
                    if (mode == MOVE) {
                        Log.d(TAG, "move polygon");
                        this.moveToNewPos(event);
                    } else if (mode == ROTATE) {
                        if (event.getPointerCount() == 3) {
                            Log.d(TAG, "scale polygon");
                            this.scalePolygon(event);
                        } else {
                            Log.d(TAG, "rotate polygon");
                            this.rotatePolygon(event);
                        }
                    }
                }
                break;
            default:
                Log.d(TAG, "detected another touch event");
            }
            return active;
        } else {
            if (mInfoWindow != null && mInfoWindow.isOpen()) {
                mInfoWindow.close();
            }
            return super.onTouchEvent(event, mapView);
        }
    }

    /**
     * change the mode whether the edit function is active or not.
     */
    public void changeMode() {
        if (!active) {
            // change mode to active, polygon is now rotatable and movable
            this.setFillColor(ACTIVE_FILL_COLOR);
            this.setStrokeColor(ACTIVE_STROKE_COLOR);
            pj = mapView.getProjection();
            mapView.invalidate();
            active = true;
        } else {
            // change mode to not active, polygon is not modifiable now
            this.setFillColor(CustomInfoWindow.MARKED_FILL_COLOR);
            this.setStrokeColor(CustomInfoWindow.MARKED_STROKE_COLOR);
            mapView.invalidate();
            active = false;
        }
        Log.d(TAG, "actual activity mode: " + active);
        if (activity instanceof MapPreviewActivity) {
            ZoomControls zoomControls = (ZoomControls) activity
                    .findViewById(R.id.zoomcontrols);
            int v = active ? View.VISIBLE : View.GONE;
            zoomControls.setVisibility(v);
        }
    }

    /**
     * Move this polygon to the new position handling the touch events.
     * 
     * @param event
     *            the current MotionEvent from onTouchEvent
     */
    public void moveToNewPos(final MotionEvent event) {
        // set the end coordinates of the movement
        Point endPoint = new Point((int) event.getX(0), (int) event.getY(0));
        int x = endPoint.x - startPos.get(0).x;
        int y = endPoint.y - startPos.get(0).y;
        pj = mapView.getProjection();
        List<GeoPoint> returnList = new ArrayList<GeoPoint>();
        for (GeoPoint geoPoint : geoPointList) {
            Point point = pj.toPixels(geoPoint, null);
            point = new Point(point.x + x, point.y + y);
            returnList.add((GeoPoint) pj.fromPixels(point.x, point.y));
        }
        Point point = pj.toPixels(oldMapcenter, null);
        point = new Point(point.x + x, point.y + y);
        newMapcenter = (GeoPoint) pj.fromPixels(point.x, point.y);

        this.setPoints(returnList);
        mapView.invalidate();
    }

    /**
     * Scale the given Polygon with a 3 Pointer MotionEvent.
     * 
     * @param event
     *            the current MotionEvent from onTouchEvent
     */
    private void scalePolygon(MotionEvent event) {
        // set end values for the next rotation action
        List<Point> endPos = new ArrayList<Point>();
        endPos.add(new Point((int) event.getX(0), (int) event.getY(0)));
        endPos.add(new Point((int) event.getX(1), (int) event.getY(1)));
        endPos.add(new Point((int) event.getX(2), (int) event.getY(2)));
        float scaleFactor = MathUtil.perimeter(endPos) / startPosParameter;
        geoPointList = new ArrayList<GeoPoint>();
        // rotate all coordinates
        for (double[] preCoord : pointCoords) {
            double[] coord = new double[2];
            coord[0] = preCoord[0] * scaleFactor;
            coord[1] = preCoord[1] * scaleFactor;
            // transfer coordinates to gpsPoints
            final Node node = MathUtil.calculateGPSPoint(midLocation, coord);<commit_msg>Refactored the polygon movement from pixel to GPS-Points<commit_after>/*
 * Copyright (c) 2014, 2015 Data4All
 * 
 * <p>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
 * 
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 * 
 * <p>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 io.github.data4all.model.map;

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

import io.github.data4all.R;
import io.github.data4all.activity.AbstractActivity;
import io.github.data4all.activity.MapPreviewActivity;
import io.github.data4all.activity.MapViewActivity;
import io.github.data4all.logger.Log;
import io.github.data4all.model.data.AbstractDataElement;
import io.github.data4all.model.data.ClassifiedTag;
import io.github.data4all.model.data.Node;
import io.github.data4all.model.data.PolyElement;
import io.github.data4all.model.data.Tag;
import io.github.data4all.util.MapUtil;
import io.github.data4all.util.MathUtil;
import io.github.data4all.view.D4AMapView;

import org.osmdroid.bonuspack.overlays.Polygon;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import org.osmdroid.views.Projection;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.location.Location;
import android.preference.PreferenceManager;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ZoomControls;

/**
 * Polygon which is editable. It has an InfoWindow opened with a single tap, if
 * it is not editable and it is movable and rotatable with a TouchEvent, if it
 * is editable.
 * 
 * @author <NAME>
 * @author sbollen
 * @author burghardt
 *
 */
public class MapPolygon extends Polygon {

    private static final String TAG = "MapPolygon";
    private AbstractActivity activity;
    private D4AMapView mapView;
    private AbstractDataElement element;
    private boolean editable;

    // start time for touch event action_down
    private long timeStart;

    // True when the edit mode is active
    private boolean active;

    // the maximum time difference between action_down and action_up, so that
    // the mode will be changed
    private static final int TIME_DIFF = 200;

    // Default Stroke Color
    protected static final int DEFAULT_STROKE_COLOR = Color.BLUE;
    // Active Stroke Color
    protected static final int ACTIVE_STROKE_COLOR = Color.GREEN;

    // Fill Color for Polygons
    protected static final int DEFAULT_FILL_COLOR = Color.argb(100, 0, 0, 255);
    // Fill Color for activated Polygons
    protected static final int ACTIVE_FILL_COLOR = Color.argb(100, 50, 255, 50);

    // Maximum tolerance for touch events in meter to the element center
    private static final int TOLERANCE = 1;

    /**
     * Modes for edits which differ from touch events.
     */
    private static final int NONE = 0;
    private static final int MOVE = 1;
    private static final int ROTATE = 2;
    private int mode = NONE;

    /**
     * Start values for any movements.
     */
    private List<Point> startPos;

    /**
     * List of the geopoints in a coordinate system with the center as the
     * average of all Points.
     */
    private List<double[]> pointCoords;

    /**
     * the Parameter of the 3 start points to scale
     */
    private float startPosParameter;

    /**
     * Average of all Points saved as a Location.
     */
    private Location midLocation;

    /**
     * List of GeoPoints for editing the MapPolygon.
     */
    private List<GeoPoint> geoPointList;

    /**
     * Projection of the mapView.
     */
    private Projection pj;

    /**
     * MapCenter values.
     */
    private GeoPoint newMapcenter;
    private double[] oldMapcenter;

    /**
     * Default constructor.
     * 
     * @param ctx
     *            the Context for the Overlay
     * 
     * @param mv
     *            the Mapview
     * 
     * @param ele
     *            the associated OsmElement
     */
    public MapPolygon(AbstractActivity ctx, D4AMapView mv,
            AbstractDataElement ele) {
        super(ctx);
        this.element = ele;
        this.activity = ctx;
        this.mapView = mv;
        this.editable = false;
        if (activity instanceof MapViewActivity) {
            mInfoWindow = new CustomInfoWindow(this.mapView, ele, this,
                    activity);
        } else {
            mInfoWindow = null;
        }
        this.setInfo();
    }

    /**
     * Set the info of the MapPolygon for the InfoWindow.
     */
    public void setInfo() {
        if (!element.getTags().keySet().isEmpty()
                && !element.getTags().values().isEmpty()) {
            Log.i(TAG, element.getTags().toString());
            final Tag tag = (Tag) element.getTags().keySet().toArray()[0];
            final String key = tag.getKey();
            final String value = element.getTags().get(tag);
            Log.i(TAG, tag.toString());
            setTitle(activity.getString(tag.getNameRessource()));
            if (tag instanceof ClassifiedTag) {
                setSubDescription(this.getLocalizedName(activity, key, value));
            } else {
                setSubDescription(element.getTags().get(tag));
            }
        }
    }

    /**
     * Get the localized name of the element to show in the InfoWindow.
     * 
     * @param context
     *            the context of the application
     * @param key
     *            the tag key
     * @param value
     *            the tag value
     * @return the localized name
     */
    public String getLocalizedName(Context context, String key, String value) {
        final Resources resources = context.getResources();
        final String s = "name_" + key + "_" + value;
        final int id = resources.getIdentifier(s.replace(":", "_"), "string",
                context.getPackageName());
        if (id == 0) {
            return null;
        } else {
            return resources.getString(id);
        }
    }

    @Override
    public boolean onTouchEvent(final MotionEvent event, final MapView mapView) {
        super.onTouchEvent(event, mapView);

        if (editable) {
            switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                pj = mapView.getProjection();
                timeStart = System.currentTimeMillis();
                if (active) {
                    mode = MOVE;
                    saveGeoPoints();
                    GeoPoint mapCenter = (GeoPoint) mapView.getMapCenter();
                    this.oldMapcenter = MathUtil.calculateCoordFromGPS(
                            midLocation,
                            new Node(0, mapCenter.getLatitude(), 
                                    mapCenter.getLongitude()));
                    startPos = new ArrayList<Point>();
                    startPos.add(new Point((int) event.getX(0), (int) event
                            .getY(0)));
                    Log.d(TAG, "action_down at point: " + event.getX(0) + " "
                            + event.getY(0));
                }
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                Log.d(TAG, "more than one pointer on screen");
                if (active) {
                    mode = ROTATE;
                    saveGeoPoints();
                    startPos = new ArrayList<Point>();
                    startPos.add(new Point((int) event.getX(0), (int) event
                            .getY(0)));
                    startPos.add(new Point((int) event.getX(1), (int) event
                            .getY(1)));
                    if (event.getPointerCount() == 3) {
                        startPos.add(new Point((int) event.getX(2), (int) event
                                .getY(2)));
                        startPosParameter = MathUtil.perimeter(startPos);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "action_up");
                if (active) {
                    // set the new information to the element
                    ((PolyElement) element).setNodesFromGeoPoints(geoPointList);
                }
                if (Math.abs(timeStart - System.currentTimeMillis()) < TIME_DIFF
                        && isTapped(event)) {
                    changeMode();
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                mode = NONE;
                break;
            case MotionEvent.ACTION_MOVE:
                Log.d(TAG, "action_move");
                if (active) {
                    if (mode == MOVE) {
                        Log.d(TAG, "move polygon");
                        this.moveToNewPos(event);
                    } else if (mode == ROTATE) {
                        if (event.getPointerCount() == 3) {
                            Log.d(TAG, "scale polygon");
                            this.scalePolygon(event);
                        } else {
                            Log.d(TAG, "rotate polygon");
                            this.rotatePolygon(event);
                        }
                    }
                }
                break;
            default:
                Log.d(TAG, "detected another touch event");
            }
            return active;
        } else {
            if (mInfoWindow != null && mInfoWindow.isOpen()) {
                mInfoWindow.close();
            }
            return super.onTouchEvent(event, mapView);
        }
    }

    /**
     * change the mode whether the edit function is active or not.
     */
    public void changeMode() {
        if (!active) {
            // change mode to active, polygon is now rotatable and movable
            this.setFillColor(ACTIVE_FILL_COLOR);
            this.setStrokeColor(ACTIVE_STROKE_COLOR);
            pj = mapView.getProjection();
            mapView.invalidate();
            active = true;
        } else {
            // change mode to not active, polygon is not modifiable now
            this.setFillColor(CustomInfoWindow.MARKED_FILL_COLOR);
            this.setStrokeColor(CustomInfoWindow.MARKED_STROKE_COLOR);
            mapView.invalidate();
            active = false;
        }
        Log.d(TAG, "actual activity mode: " + active);
        if (activity instanceof MapPreviewActivity) {
            ZoomControls zoomControls = (ZoomControls) activity
                    .findViewById(R.id.zoomcontrols);
            int v = active ? View.VISIBLE : View.GONE;
            zoomControls.setVisibility(v);
        }
    }

    /**
     * Move this polygon to the new position handling the touch events.
     * 
     * @param event
     *            the current MotionEvent from onTouchEvent
     */
    public void moveToNewPos(final MotionEvent event) {
        // set the end coordinates of the movement
        Point endPoint = new Point((int) event.getX(0), (int) event.getY(0));
        pj = mapView.getProjection();
        GeoPoint startGeo = (GeoPoint) pj.fromPixels(startPos.get(0).x,
                startPos.get(0).y);
        double[] startCoord = MathUtil.calculateCoordFromGPS(midLocation,
                new Node(0, startGeo.getLatitude(), startGeo.getLongitude()));
        GeoPoint endGeo = (GeoPoint) pj.fromPixels(endPoint.x, endPoint.y);
        double[] endCoord = MathUtil.calculateCoordFromGPS(midLocation,
                new Node(0, endGeo.getLatitude(), endGeo.getLongitude()));
        double x = endCoord[0] - startCoord[0];
        double y = endCoord[1] - startCoord[1];
        List<GeoPoint> returnList = new ArrayList<GeoPoint>();
        for (double[] preCoord : pointCoords) {
            double[] returnCoord = { preCoord[0] + x, preCoord[1] + y, };
            Node node = MathUtil.calculateGPSPoint(midLocation, returnCoord);
            returnList.add(new GeoPoint(node.getLat(), node.getLon()));
        }
        double[] returnCoord = { oldMapcenter[0] + x, oldMapcenter[1] + y, };
        Node node = MathUtil.calculateGPSPoint(midLocation, returnCoord);
        newMapcenter = new GeoPoint(node.getLat(), node.getLon());

        this.setPoints(returnList);
        mapView.invalidate();
    }

    /**
     * Scale the given Polygon with a 3 Pointer MotionEvent.
     * 
     * @param event
     *            the current MotionEvent from onTouchEvent
     */
    private void scalePolygon(MotionEvent event) {
        // set end values for the next rotation action
        List<Point> endPos = new ArrayList<Point>();
        endPos.add(new Point((int) event.getX(0), (int) event.getY(0)));
        endPos.add(new Point((int) event.getX(1), (int) event.getY(1)));
        endPos.add(new Point((int) event.getX(2), (int) event.getY(2)));
        float scaleFactor = MathUtil.perimeter(endPos) / startPosParameter;
        geoPointList = new ArrayList<GeoPoint>();
        // rotate all coordinates
        for (double[] preCoord : pointCoords) {
            double[] coord = new double[2];
            coord[0] = preCoord[0] * scaleFactor;
            coord[1] = preCoord[1] * scaleFactor;
            // transfer coordinates to gpsPoints
            final Node node = MathUtil.calculateGPSPoint(midLocation, coord);
{'fraction_non_alphanumeric': 0.09867801589393997, 'fraction_numerical': 0.006892395766637791, 'mean_word_length': 1.8854596826776788, 'pattern_counts': {'":': 2, '<': 33, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5214876', 'n_tokens_mistral': 7461, 'n_tokens_neox': 7129, 'n_words': 1961}