/*
* Copyright (C) 2014 Alec Dhuse
*
* 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 co.foldingmap.actions;
import co.foldingmap.map.vector.MultiGeometry;
import co.foldingmap.map.vector.VectorLayer;
import co.foldingmap.map.vector.CoordinateList;
import co.foldingmap.map.vector.CoordinateMath;
import co.foldingmap.map.vector.VectorObject;
import co.foldingmap.map.vector.VectorObjectList;
import co.foldingmap.map.vector.Coordinate;
import co.foldingmap.map.vector.LineString;
import co.foldingmap.map.vector.MapPoint;
import co.foldingmap.map.vector.Polygon;
import co.foldingmap.GUISupport.Updateable;
import co.foldingmap.Logger;
import co.foldingmap.map.DigitalMap;
import co.foldingmap.map.Layer;
import co.foldingmap.map.MapView;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
/**
* Merges a combination of points and LineStrings into a single LineStirng
*
* @author Alec
*/
public class MergeToLineString extends Action {
protected boolean allObjectsArePoints;
protected DigitalMap mapData;
protected VectorObject newObject;
protected VectorObjectList<VectorObject> objectsToMerge, pointsToMerge;
protected VectorObjectList<VectorObject> originalObjects;
protected MapView mapView;
protected Updateable updateable;
public MergeToLineString(DigitalMap mapData, Updateable updateable) {
this.mapData = mapData;
this.commandDescription = "Merge To LineString";
this.updateable = updateable;
}
/**
* Returns if this Action can be undone.
*
* @return
*/
@Override
public boolean canUndo() {
return true;
}
@Override
public void execute() {
boolean verticesCross;
CoordinateList<Coordinate> mergedCoordinates, userSelection;
int currentObjectNumberOfPoints, greatestNumberOfPoints;
VectorObject largestMapObject;
VectorLayer parentLayer;
this.allObjectsArePoints = false;
this.pointsToMerge = new VectorObjectList<VectorObject>();
this.newObject = null;
try {
objectsToMerge = new VectorObjectList<VectorObject>(mapData.getSelectedObjects());
originalObjects = (objectsToMerge.getFullCopy());
allObjectsArePoints = (objectsToMerge.getMapPoints().size() == objectsToMerge.size());
mapView = mapData.getLastMapView();
verticesCross = true;
userSelection = new CoordinateList<Coordinate>();
if (objectsToMerge.size() > 1) {
if (allObjectsArePoints) {
for (int i = 0; i < objectsToMerge.size(); i++)
userSelection.add(objectsToMerge.get(i).getCoordinateList().get(0));
verticesCross = CoordinateMath.testPolygonVertices(constructVertices(userSelection));
} else if (objectsToMerge.getLineStrings().size() == objectsToMerge.size()) {
//All Objects are LineStrings
newObject = mergeFromLineStrings(objectsToMerge);
verticesCross = false;
for (int i = 0; i < objectsToMerge.size(); i++) {
VectorObject tempMapObject = objectsToMerge.get(i);
newObject.setCustomDataFields(tempMapObject.getCustomDataFields());
}
}
//Temp Debug Code
int i3 = 0;
for (VectorObject vObj: objectsToMerge)
i3 += vObj.getCoordinateList().size();
if (newObject.getCoordinateList().size() < i3 - 1)
newObject = mergeFromLineStrings(objectsToMerge);
if (verticesCross) {
greatestNumberOfPoints = 0;
largestMapObject = objectsToMerge.get(0);
//remove old objects and decide which object has the most points, use the info from that object in the new object.
for (int i = 0; i < objectsToMerge.size(); i++) {
VectorObject currentMapObject = objectsToMerge.get(i);
parentLayer = (VectorLayer) currentMapObject.getParentLayer();
currentObjectNumberOfPoints = currentMapObject.getCoordinateList().size();
if (currentObjectNumberOfPoints > greatestNumberOfPoints) {
greatestNumberOfPoints = currentObjectNumberOfPoints;
largestMapObject = currentMapObject;
}
mapData.getSelectedObjects().remove(currentMapObject);
parentLayer.getObjectList().remove(currentMapObject);
}
if (largestMapObject instanceof LineString) {
newObject = (VectorObject) largestMapObject.copy();
mergedCoordinates = MergeFunctions.getCoordinatesForMerge(this.objectsToMerge);
newObject.setCoordinateList(mergedCoordinates);
} else {
mergedCoordinates = MergeFunctions.getCoordinatesForMerge(this.objectsToMerge);
newObject = new LineString(largestMapObject.getName(), "(Unspecified Linestring)", mergedCoordinates);
newObject.setDescription(largestMapObject.getDescription());
newObject.setCustomDataFields(largestMapObject.getCustomDataFields());
}
parentLayer = (VectorLayer) largestMapObject.getParentLayer();
parentLayer.addObject(newObject);
mapData.setSelected(newObject);
} else {
if (newObject == null) {
//use user selecion to merge
newObject = new LineString("New Line", "(Unspecified Linestring)", userSelection);
newObject.setDescription("");
newObject.setCustomDataFields(objectsToMerge.get(0).getCustomDataFields());
}
parentLayer = (VectorLayer) objectsToMerge.get(0).getParentLayer();
for (VectorObject vObj: objectsToMerge) {
VectorLayer l = (VectorLayer) vObj.getParentLayer();
l.removeObject(vObj);
}
parentLayer.addObject(newObject);
}
} else if (objectsToMerge.size() == 1) {
VectorObject currentObject = objectsToMerge.get(0);
if (currentObject instanceof MultiGeometry) {
MultiGeometry currentMulti = (MultiGeometry) currentObject;
MultiGeometry newMulti = new MultiGeometry(currentObject.getName());
VectorObjectList<VectorObject> componetObjects = currentMulti.getComponentObjects();
for (int i = 0; i < componetObjects.size(); i++) {
VectorObject currentSubObject = componetObjects.get(i);
newObject = new LineString(currentObject.getName(), "(Unspecified Linestring)", currentSubObject.getCoordinateList());
newMulti.addObject(newObject);
}
newObject = newMulti;
parentLayer = (VectorLayer) objectsToMerge.get(0).getParentLayer();
parentLayer.addObject(newObject);
mapData.setSelected(newObject);
parentLayer.getObjectList().removeAll(objectsToMerge);
mapData.getSelectedObjects().removeAll(objectsToMerge);
} else {
String newType = objectsToMerge.get(0).getName();
if (objectsToMerge.get(0) instanceof Polygon) {
Polygon p = (Polygon) objectsToMerge.get(0);
String type = p.getObjectClass();
if (type.equals("Lake") || type.equals("Small Island") || type.equals("River") ) {
newType = "Coastline";
}
} else if (currentObject instanceof LineString) {
//For LineStrings, LinearRings and other types with LineString Parent.
newType = currentObject.getObjectClass();
}
newObject = new LineString(currentObject.getName(), newType, currentObject.getCoordinateList());
newObject.setCustomDataFields(objectsToMerge.get(0).getCustomDataFields());
parentLayer = (VectorLayer) objectsToMerge.get(0).getParentLayer();
parentLayer.removeObject(objectsToMerge.get(0));
if (parentLayer == null)
parentLayer = mapData.getVectorLayer();
parentLayer.addObject(newObject);
}
} else {
Logger.log(Logger.WARN, "Error in CommandMergeToLineString.execute() - No objects to merge");
}
//Clear the selected Objects
mapData.deselectObjects();;
mapData.setSelected(newObject);
if (updateable != null)
updateable.update();
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in CommandMergeToLineString.execute() - " + e);
}
}
@Override
public void undo() {
Layer parentLayer;
VectorLayer vectorLayer;
try {
//remove the newly created, merged objects
parentLayer = newObject.getParentLayer();
if (parentLayer instanceof VectorLayer) {
vectorLayer = (VectorLayer) parentLayer;
vectorLayer.getObjectList().remove(newObject);
}
//restore the original objects to their original layers
for (int i = 0; i < originalObjects.size(); i++) {
VectorObject currentMapObject = (VectorObject) originalObjects.get(i);
parentLayer = currentMapObject.getParentLayer();
if (parentLayer instanceof VectorLayer) {
vectorLayer = (VectorLayer) parentLayer;
vectorLayer.addObject(currentMapObject);
}
}
mapData.setSelected(originalObjects);
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in CommandMergeToLineString.undo() - " + e);
}
}
private ArrayList<Line2D> constructVertices(CoordinateList<Coordinate> coordinates) {
ArrayList<Line2D> vertices;
Point2D p1, p2;
vertices = new ArrayList<Line2D>();
for (int i = 0; i < (coordinates.size() - 1); i += 2) {
p1 = new Point2D.Double(mapView.getX(coordinates.get(i), MapView.NO_WRAP), mapView.getY(coordinates.get(i)));
p2 = new Point2D.Double(mapView.getX(coordinates.get(i+1), MapView.NO_WRAP), mapView.getY(coordinates.get(i+1)));
vertices.add(new Line2D.Double(p1, p2));
}
return vertices;
}
public VectorObjectList<VectorObject> mergeConnectedLineStrings(VectorObjectList<VectorObject> objectsToMerge) {
CoordinateList<Coordinate> compareCoordinates, currentCoordinates, newLineStringCoordinates;
Coordinate compareFirstCoordinate, compareLastCoordinate, currentFirstCoordinate, currentLastCoordinate;
LineString compareLineString, currentLineStirng, newLineString;
VectorObjectList<VectorObject> updatedObjects;
updatedObjects = new VectorObjectList<VectorObject>();
updatedObjects.addAll(objectsToMerge);
try {
for (int i = 0; i < objectsToMerge.size(); i++) {
currentLineStirng = (LineString) objectsToMerge.get(i);
currentFirstCoordinate = currentLineStirng.getCoordinateList().get(0);
currentLastCoordinate = currentLineStirng.getCoordinateList().lastCoordinate();
if (updatedObjects.size() > 1) {
for (int j = 0; j < objectsToMerge.size(); j++) {
compareLineString = (LineString) objectsToMerge.get(j);
compareFirstCoordinate = compareLineString.getCoordinateList().get(0);
compareLastCoordinate = compareLineString.getCoordinateList().lastCoordinate();
if (currentLineStirng != compareLineString) {
if (currentFirstCoordinate.equals(compareFirstCoordinate)) {
compareCoordinates = compareLineString.getCoordinateList();
currentCoordinates = currentLineStirng.getCoordinateList();
newLineStringCoordinates = new CoordinateList<Coordinate>();
newLineStringCoordinates.addAll(compareCoordinates.getReverse());
newLineStringCoordinates.addAll(currentCoordinates);
updatedObjects.remove(compareLineString);
updatedObjects.remove(currentLineStirng);
newLineString = new LineString((currentLineStirng.getName() + "-" + compareLineString.getName()), currentLineStirng.getObjectClass(), newLineStringCoordinates);
updatedObjects.add(newLineString);
break;
} else if (currentFirstCoordinate.equals(compareLastCoordinate)) {
compareCoordinates = compareLineString.getCoordinateList();
currentCoordinates = currentLineStirng.getCoordinateList();
newLineStringCoordinates = new CoordinateList<Coordinate>();
newLineStringCoordinates.addAll(compareCoordinates);
newLineStringCoordinates.addAll(currentCoordinates);
updatedObjects.remove(compareLineString);
updatedObjects.remove(currentLineStirng);
newLineString = new LineString((currentLineStirng.getName() + "-" + compareLineString.getName()), currentLineStirng.getObjectClass(), newLineStringCoordinates);
updatedObjects.add(newLineString);
break;
} else if (currentLastCoordinate.equals(compareFirstCoordinate)) {
compareCoordinates = compareLineString.getCoordinateList();
currentCoordinates = currentLineStirng.getCoordinateList();
newLineStringCoordinates = new CoordinateList<Coordinate>();
newLineStringCoordinates.addAll(currentCoordinates);
newLineStringCoordinates.addAll(compareCoordinates);
updatedObjects.remove(compareLineString);
updatedObjects.remove(currentLineStirng);
newLineString = new LineString((currentLineStirng.getName() + "-" + compareLineString.getName()), currentLineStirng.getObjectClass(), newLineStringCoordinates);
updatedObjects.add(newLineString);
break;
} else if (currentLastCoordinate.equals(compareLastCoordinate)) {
compareCoordinates = compareLineString.getCoordinateList();
currentCoordinates = currentLineStirng.getCoordinateList();
newLineStringCoordinates = new CoordinateList<Coordinate>();
newLineStringCoordinates.addAll(currentCoordinates);
newLineStringCoordinates.addAll(compareCoordinates.getReverse());
updatedObjects.remove(compareLineString);
updatedObjects.remove(currentLineStirng);
newLineString = new LineString((currentLineStirng.getName() + "-" + compareLineString.getName()), currentLineStirng.getObjectClass(), newLineStringCoordinates);
updatedObjects.add(newLineString);
break;
}
}
} // end for j loop
}//end size check
} // end for i loop
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in CommanMergeToLineString.mergeConnectedLineStrings(MapObjectList) - " + e);
}
return updatedObjects;
}
public LineString mergeFromLineStrings(VectorObjectList<VectorObject> objectsToMerge) {
LineString currentLineStirng;
VectorObjectList<VectorObject> updatedObjects;
updatedObjects = new VectorObjectList<VectorObject>();
updatedObjects.addAll(objectsToMerge);
try {
while (updatedObjects.size() > 1) {
currentLineStirng = (LineString) updatedObjects.remove(0);
updatedObjects = mergeLineStringToBestMatch(currentLineStirng, updatedObjects);
}
} catch (Exception e) {
Logger.log(Logger.ERR, "Error in CommanMergeToLineString.mergeFromLineStrings(MapObjectList) - " + e);
}
return (LineString) updatedObjects.get(0);
}
//will merge a LineString to the object closest to one of it's endpoints
public static VectorObjectList<VectorObject> mergeLineStringToBestMatch(LineString lineStringToMerge, VectorObjectList<VectorObject> objects) {
CoordinateList<Coordinate> lineStringCoordinates, newlineStringCoordinates;
Coordinate lineStringFirstCoordinate, lineStringLastCoordinate;
double closestDistance, currentDistance;
LineString newLineStirng;
String objectClass;
VectorObject bestMatch, currentObject;
VectorObjectList<VectorObject> updatedObjects;
bestMatch = null;
closestDistance = Float.MAX_VALUE;
lineStringCoordinates = lineStringToMerge.getCoordinateList();
lineStringFirstCoordinate = lineStringToMerge.getCoordinateList().get(0);
lineStringLastCoordinate = lineStringToMerge.getCoordinateList().lastCoordinate();
newLineStirng = null;
updatedObjects = new VectorObjectList<VectorObject>();
updatedObjects.addAll(objects);
for (int i = 0; i < objects.size(); i++) {
currentObject = objects.get(i);
objectClass = (lineStringToMerge.getObjectClass().length() == 0 ? currentObject.getObjectClass() : lineStringToMerge.getObjectClass());
objectClass = (objectClass == null ? objectClass = "(Unspecified Linestring)" : objectClass);
if (currentObject instanceof MapPoint) {
currentDistance = CoordinateMath.getDistance(currentObject.getCoordinateList().get(0), lineStringFirstCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
newlineStringCoordinates.add(currentObject.getCoordinateList().get(0));
newlineStringCoordinates.addAll(lineStringCoordinates);
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
currentDistance = CoordinateMath.getDistance(currentObject.getCoordinateList().get(0), lineStringLastCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
newlineStringCoordinates.addAll(lineStringCoordinates);
newlineStringCoordinates.add(currentObject.getCoordinateList().get(0));
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
} else if (currentObject instanceof LineString) {
CoordinateList<Coordinate> compareCoordinates = currentObject.getCoordinateList();
Coordinate compareFirst = currentObject.getCoordinateList().get(0);
Coordinate compareLast = currentObject.getCoordinateList().lastCoordinate();
currentDistance = CoordinateMath.getDistance(compareFirst, lineStringFirstCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
newlineStringCoordinates.addAll(compareCoordinates.getReverse());
newlineStringCoordinates.addAll(lineStringCoordinates);
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
currentDistance = CoordinateMath.getDistance(compareLast, lineStringFirstCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
newlineStringCoordinates.addAll(compareCoordinates);
newlineStringCoordinates.addAll(lineStringCoordinates);
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
currentDistance = CoordinateMath.getDistance(compareFirst, lineStringLastCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
newlineStringCoordinates.addAll(lineStringCoordinates);
newlineStringCoordinates.addAll(compareCoordinates);
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
currentDistance = CoordinateMath.getDistance(compareLast, lineStringLastCoordinate);
if (currentDistance < closestDistance) {
closestDistance = currentDistance;
bestMatch = currentObject;
newlineStringCoordinates = new CoordinateList<Coordinate>();
if (CoordinateMath.getDistance(lineStringCoordinates.lastCoordinate(), compareCoordinates.get(0)) == currentDistance) {
newlineStringCoordinates.addAll(lineStringCoordinates);
newlineStringCoordinates.addAll(compareCoordinates);
} else {
newlineStringCoordinates.addAll(lineStringCoordinates);
newlineStringCoordinates.addAll(compareCoordinates.getReverse());
}
newLineStirng = new LineString(lineStringToMerge.getName(), objectClass, newlineStringCoordinates);
}
} else {
//Polygon or LineString
}
} // end for loop
if ((bestMatch != null) && (newLineStirng != null)) {
updatedObjects.remove(bestMatch);
updatedObjects.add(newLineStirng);
} else {
updatedObjects.add(lineStringToMerge);
}
return updatedObjects;
}
}