/*
* Geopaparazzi - Digital field mapping on Android based devices
* Copyright (C) 2016 HydroloGIS (www.hydrologis.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.geopaparazzi.core.maptools.tools;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.AsyncTask;
import android.view.MotionEvent;
import android.widget.Toast;
import com.vividsolutions.jts.android.PointTransformation;
import com.vividsolutions.jts.android.ShapeWriter;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.mapsforge.android.maps.MapView;
import org.mapsforge.android.maps.MapViewPosition;
import org.mapsforge.android.maps.Projection;
import org.mapsforge.core.model.GeoPoint;
import java.util.List;
import eu.geopaparazzi.library.style.ToolColors;
import eu.geopaparazzi.library.database.GPLog;
import eu.geopaparazzi.library.features.EditManager;
import eu.geopaparazzi.library.features.Feature;
import eu.geopaparazzi.library.features.ILayer;
import eu.geopaparazzi.library.features.ToolGroup;
import eu.geopaparazzi.library.style.ColorUtilities;
import eu.geopaparazzi.library.util.GPDialogs;
import eu.geopaparazzi.library.util.LibraryConstants;
import eu.geopaparazzi.spatialite.database.spatial.core.layers.SpatialVectorTableLayer;
import eu.geopaparazzi.spatialite.database.spatial.core.tables.SpatialVectorTable;
import eu.geopaparazzi.spatialite.database.spatial.util.SpatialiteUtilities;
import eu.geopaparazzi.core.R;
import eu.geopaparazzi.core.maptools.FeatureUtilities;
import eu.geopaparazzi.core.maptools.MapTool;
import eu.geopaparazzi.core.mapview.overlays.MapsforgePointTransformation;
import eu.geopaparazzi.core.mapview.overlays.SliderDrawProjection;
import static java.lang.Math.abs;
import static java.lang.Math.round;
/**
* A tool to cut or extend features.
*
* @author Andrea Antonello (www.hydrologis.com)
*/
public class PolygonCutExtendTool extends MapTool {
private final Paint drawingPaintStroke = new Paint();
private final Paint drawingPaintFill = new Paint();
private final Paint selectedPreviewGeometryPaintStroke = new Paint();
private final Paint selectedPreviewGeometryPaintFill = new Paint();
private float currentX = 0;
private float currentY = 0;
private float lastX = -1;
private float lastY = -1;
private final Point tmpP = new Point();
private final Point startP = new Point();
private final Point endP = new Point();
private Path drawingPath = new Path();
private GeoPoint startGeoPoint;
private Geometry previewGeometry = null;
/**
* Stores the top-left map position at which the redraw should happen.
*/
private final Point point;
/**
* Stores the map position after drawing is finished.
*/
private Point positionBeforeDraw;
// private ProgressDialog infoProgressDialog;
private SliderDrawProjection editingViewProjection;
private Feature startFeature;
private Feature endFeature;
private boolean doCut;
/**
* Constructor.
*
* @param mapView the mapview reference.
* @param doCut if <code>true</code>, do cut as opposed to extend.
*/
public PolygonCutExtendTool(MapView mapView, boolean doCut) {
super(mapView);
this.doCut = doCut;
editingViewProjection = new SliderDrawProjection(mapView, EditManager.INSTANCE.getEditingView());
point = new Point();
positionBeforeDraw = new Point();
// Context context = GeopaparazziApplication.getInstance().getApplicationContext();
// SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
drawingPaintFill.setAntiAlias(true);
drawingPaintFill.setColor(Color.RED);
// drawingPaintFill.setAlpha(80);
drawingPaintFill.setStyle(Paint.Style.FILL);
drawingPaintStroke.setAntiAlias(true);
drawingPaintStroke.setStrokeWidth(5f);
drawingPaintStroke.setColor(Color.RED);
drawingPaintStroke.setStyle(Paint.Style.STROKE);
int previewStroke = ColorUtilities.toColor(ToolColors.preview_stroke.getHex());
int previewFill = ColorUtilities.toColor(ToolColors.preview_fill.getHex());
selectedPreviewGeometryPaintFill.setAntiAlias(true);
selectedPreviewGeometryPaintFill.setColor(previewFill);
selectedPreviewGeometryPaintFill.setAlpha(180);
selectedPreviewGeometryPaintFill.setStyle(Paint.Style.FILL);
selectedPreviewGeometryPaintStroke.setAntiAlias(true);
selectedPreviewGeometryPaintStroke.setStrokeWidth(5f);
selectedPreviewGeometryPaintStroke.setColor(previewStroke);
selectedPreviewGeometryPaintStroke.setStyle(Paint.Style.STROKE);
}
public void activate() {
if (mapView != null)
mapView.setClickable(false);
}
public void onToolDraw(Canvas canvas) {
if (startP.x == 0 && startP.y == 0) return;
canvas.drawCircle(startP.x, startP.y, 15, drawingPaintFill);
canvas.drawPath(drawingPath, drawingPaintStroke);
canvas.drawCircle(endP.x, endP.y, 15, drawingPaintFill);
if (previewGeometry != null) {
Projection projection = editingViewProjection;
byte zoomLevelBeforeDraw;
synchronized (mapView) {
zoomLevelBeforeDraw = mapView.getMapPosition().getZoomLevel();
positionBeforeDraw = projection.toPoint(mapView.getMapPosition().getMapCenter(), positionBeforeDraw,
zoomLevelBeforeDraw);
}
// calculate the top-left point of the visible rectangle
point.x = positionBeforeDraw.x - (canvas.getWidth() >> 1);
point.y = positionBeforeDraw.y - (canvas.getHeight() >> 1);
MapViewPosition mapPosition = mapView.getMapPosition();
byte zoomLevel = mapPosition.getZoomLevel();
PointTransformation pointTransformer = new MapsforgePointTransformation(projection, point, zoomLevel);
ShapeWriter shapeWriter = new ShapeWriter(pointTransformer);
shapeWriter.setRemoveDuplicatePoints(true);
FeatureUtilities.drawGeometry(previewGeometry, canvas, shapeWriter, selectedPreviewGeometryPaintFill, selectedPreviewGeometryPaintStroke);
}
}
public boolean onToolTouchEvent(MotionEvent event) {
if (mapView == null || mapView.isClickable()) {
return false;
}
Projection pj = editingViewProjection;
// handle drawing
currentX = event.getX();
currentY = event.getY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startGeoPoint = pj.fromPixels(round(currentX), round(currentY));
pj.toPixels(startGeoPoint, startP);
endP.set(startP.x, startP.y);
drawingPath.reset();
drawingPath.moveTo(startP.x, startP.y);
lastX = currentX;
lastY = currentY;
break;
case MotionEvent.ACTION_MOVE:
float dx = currentX - lastX;
float dy = currentY - lastY;
if (abs(dx) < 1 && abs(dy) < 1) {
lastX = currentX;
lastY = currentY;
return true;
}
GeoPoint currentGeoPoint = pj.fromPixels(round(currentX), round(currentY));
pj.toPixels(currentGeoPoint, tmpP);
drawingPath.lineTo(tmpP.x, tmpP.y);
endP.set(tmpP.x, tmpP.y);
EditManager.INSTANCE.invalidateEditingView();
break;
case MotionEvent.ACTION_UP:
GeoPoint endGeoPoint = pj.fromPixels(round(currentX), round(currentY));
GeometryFactory gf = new GeometryFactory();
Coordinate startCoord = new Coordinate(startGeoPoint.getLongitude(), startGeoPoint.getLatitude());
com.vividsolutions.jts.geom.Point startPoint = gf.createPoint(startCoord);
Coordinate endCoord = new Coordinate(endGeoPoint.getLongitude(), endGeoPoint.getLatitude());
com.vividsolutions.jts.geom.Point endPoint = gf.createPoint(endCoord);
Envelope env = new Envelope(startCoord, endCoord);
select(env.getMaxY(), env.getMinX(), env.getMinY(), env.getMaxX(), startPoint, endPoint);
// EditManager.INSTANCE.invalidateEditingView();
break;
}
return true;
}
public void disable() {
if (mapView != null) {
mapView.setClickable(true);
mapView = null;
}
previewGeometry = null;
startFeature = null;
endFeature = null;
}
private void select(final double n, final double w, final double s, final double e,//
final com.vividsolutions.jts.geom.Point startPoint, final com.vividsolutions.jts.geom.Point endPoint) {
ILayer editLayer = EditManager.INSTANCE.getEditLayer();
SpatialVectorTableLayer layer = (SpatialVectorTableLayer) editLayer;
final SpatialVectorTable spatialVectorTable = layer.getSpatialVectorTable();
final Context context = EditManager.INSTANCE.getEditingView().getContext();
final ProgressDialog infoProgressDialog = new ProgressDialog(context);
infoProgressDialog.setCancelable(true);
infoProgressDialog.setTitle("SELECT");
infoProgressDialog.setMessage("Selecting features...");
infoProgressDialog.setCancelable(false);
infoProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
infoProgressDialog.setIndeterminate(true);
infoProgressDialog.show();
// TODO make this Asynktask right
new AsyncTask<String, Integer, String>() {
protected String doInBackground(String... params) {
try {
double north = n;
double south = s;
if (n - s == 0) {
south = n - 1;
}
double west = w;
double east = e;
if (e - w == 0) {
west = e - 1;
}
String query =
SpatialiteUtilities.getBboxIntersectingFeaturesQuery(LibraryConstants.SRID_WGS84_4326,
spatialVectorTable, north, south, east, west);
List<Feature> features = FeatureUtilities.buildFeatures(query, spatialVectorTable);
Geometry startGeometry = null;
Geometry endGeometry = null;
for (Feature feature : features) {
if (startGeometry != null && endGeometry != null) break;
Geometry geometry = FeatureUtilities.getGeometry(feature);
if (startGeometry == null && geometry != null && geometry.intersects(startPoint)) {
startGeometry = geometry;
startFeature = feature;
} else if (endGeometry == null && geometry != null && geometry.intersects(endPoint)) {
endGeometry = geometry;
endFeature = feature;
}
}
if (startGeometry != null)
if (!doCut) {
previewGeometry = startGeometry.union(endGeometry);
} else {
previewGeometry = startGeometry.difference(endGeometry);
}
return "";
} catch (Exception e) {
GPLog.error(this, null, e); //$NON-NLS-1$
return "ERROR: " + e.getLocalizedMessage();
}
}
protected void onProgressUpdate(Integer... progress) { // on UI thread!
if (infoProgressDialog.isShowing())
infoProgressDialog.incrementProgressBy(progress[0]);
}
protected void onPostExecute(String response) { // on UI thread!
GPDialogs.dismissProgressDialog(infoProgressDialog);
if (response.startsWith("ERROR")) {
GPDialogs.warningDialog(context, response, null);
} else {
GPDialogs.toast(context, context.getString(R.string.preview_mode_save_warning), Toast.LENGTH_SHORT);
EditManager.INSTANCE.invalidateEditingView();
ToolGroup activeToolGroup = EditManager.INSTANCE.getActiveToolGroup();
if (activeToolGroup != null) {
activeToolGroup.onToolFinished(PolygonCutExtendTool.this);
}
}
}
}.execute((String) null);
}
/**
* Get the features as processed by the cut or extend.
* <p/>
* <p>The first feature is assured to have the right id and geometry to be used
* for the db update.</p>
*
* @return the processed feature and the one to remove.
*/
public Feature[] getProcessedFeatures() {
try {
byte[] geomBytes = FeatureUtilities.WKBWRITER.write(previewGeometry);
Feature feature = new Feature(startFeature.getTableName(), startFeature.getDatabasePath(), startFeature.getId(), geomBytes);
return new Feature[]{feature, endFeature}; // new geom feature + feature to remove
} catch (Exception e) {
String msg = "Unable to write geometry" + (previewGeometry == null ? "." : ": " + previewGeometry.toText());
GPLog.error(this, msg, e);
final Context context = EditManager.INSTANCE.getEditingView().getContext();
GPDialogs.warningDialog(context, msg, null);
return null;
}
}
@Override
public void onViewChanged() {
// ignore
}
}