package tim.prune.function.sew; import java.util.TreeSet; import tim.prune.App; import tim.prune.GenericFunction; import tim.prune.I18nManager; import tim.prune.UpdateMessageBroker; import tim.prune.data.DataPoint; import tim.prune.data.Track; import tim.prune.function.Cancellable; import tim.prune.gui.GenericProgressDialog; import tim.prune.undo.UndoException; import tim.prune.undo.UndoSewSegments; /** * Function to sew the track segments together if possible, * reversing and moving as required */ public class SewTrackSegmentsFunction extends GenericFunction implements Runnable, Cancellable { /** Set of sorted segment endpoints */ private TreeSet _nodes = null; /** Cancel flag */ private boolean _cancelled = false; /** Constructor */ public SewTrackSegmentsFunction(App inApp) { super(inApp); } /** @return name key */ public String getNameKey() { return "function.sewsegments"; } /** * Execute the function */ public void begin() { // Run in separate thread, with progress bar new Thread(this).start(); } /** * Run the function in a separate thread */ public void run() { // Make a progress bar GenericProgressDialog progressDialog = new GenericProgressDialog(getNameKey(), null, _parentFrame, this); progressDialog.show(); // Make an undo object to store the current points and sequence UndoSewSegments undo = new UndoSewSegments(_app.getTrackInfo().getTrack()); // Make list of all the segment ends _nodes = buildNodeList(_app.getTrackInfo().getTrack()); final int numNodes = (_nodes == null ? 0 : _nodes.size()); if (numNodes < 4) { System.out.println("Can't do anything with this, not enough segments"); progressDialog.close(); } else { progressDialog.showProgress(10, 100); // Say 10% for building the nodes // Disable messaging because we're probably doing a lot of reverses and moves UpdateMessageBroker.enableMessaging(false); // Set now contains all pairs of segment ends, ends at the same location are adjacent // Now we're just interested in pairs of nodes, not three or more at the same location SegmentEnd firstNode = null, secondNode = null; int numJoins = 0, currNode = 0; for (SegmentEnd node : _nodes) { if (!node.isActive()) {continue;} if (firstNode == null) { firstNode = node; } else if (secondNode == null) { if (node.atSamePointAs(firstNode)) { secondNode = node; } else { firstNode = node; } } else if (node.atSamePointAs(secondNode)) { // Found three colocated nodes, not interested firstNode = secondNode = null; } else { // Found a pair joinSegments(firstNode, secondNode); numJoins++; firstNode = node; secondNode = null; } if (_cancelled) {break;} final double fractionDone = 1.0 * currNode / numNodes; progressDialog.showProgress(10 + (int) (fractionDone * 80), 100); currNode++; } if (firstNode != null && secondNode != null) { joinSegments(firstNode, secondNode); numJoins++; } progressDialog.showProgress(90, 100); // Say 90%, only duplicate point deletion left // Delete the duplicate points final int numDeleted = _cancelled ? 0 : deleteSegmentStartPoints(_app.getTrackInfo().getTrack()); progressDialog.close(); // Enable the messaging again UpdateMessageBroker.enableMessaging(true); if (_cancelled) // TODO: Also revert if any of the operations failed { // try to restore using undo object try { undo.performUndo(_app.getTrackInfo()); } catch (UndoException ue) { _app.showErrorMessage("oops", "CANNOT UNDO"); } } else if (numJoins > 0 || numDeleted > 0) { // Give Undo object back to App to confirm final String confirmMessage = (numJoins > 0 ? I18nManager.getTextWithNumber("confirm.sewsegments", numJoins) : "" + numDeleted + " " + I18nManager.getText("confirm.deletepoint.multi")); _app.completeFunction(undo, confirmMessage); UpdateMessageBroker.informSubscribers(); } else { // Nothing done _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getTextWithNumber("error.sewsegments.nothingdone", numNodes/2)); } } } /** * Build a sorted list of all the segment start points and end points * Creates a TreeSet containing two SegmentEnd objects for each segment * @param inTrack track object * @return sorted list of segment ends */ private static TreeSet buildNodeList(Track inTrack) { TreeSet nodes = new TreeSet(); final int numPoints = inTrack.getNumPoints(); DataPoint prevTrackPoint = null; int prevTrackPointIndex = -1; SegmentEnd segmentStart = null; for (int i=0; i 0 && prevTrackPointIndex != segmentStart.getPointIndex()) { // Finish previous segment and store in list SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex); segmentStart.setOtherEnd(segmentEnd); segmentEnd.setOtherEnd(segmentStart); // Don't add closed loops if (!segmentStart.atSamePointAs(segmentEnd)) { nodes.add(segmentStart); nodes.add(segmentEnd); } } // Remember segment start segmentStart = new SegmentEnd(point, i); } prevTrackPoint = point; prevTrackPointIndex = i; } } // Probably need to deal with segmentStart and prevTrackPoint, prevTrackPointIndex if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex()) { // Finish last segment and store in list SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex); segmentStart.setOtherEnd(segmentEnd); segmentEnd.setOtherEnd(segmentStart); // Don't add closed loops if (!segmentStart.atSamePointAs(segmentEnd)) { nodes.add(segmentStart); nodes.add(segmentEnd); } } return nodes; } /** * Join the two segments together represented by the given nodes * @param inFirstNode first node (order doesn't matter) * @param inSecondNode other node */ private void joinSegments(SegmentEnd inFirstNode, SegmentEnd inSecondNode) { final Track track = _app.getTrackInfo().getTrack(); // System.out.println("Join: (" + inFirstNode.getPointIndex() + "-" + inFirstNode.getOtherPointIndex() + ") with (" // + inSecondNode.getPointIndex() + "-" + inSecondNode.getOtherPointIndex() + ")"); // System.out.println(" : " + (inFirstNode.isStart() ? "start" : "end") + " to " + (inSecondNode.isStart() ? "start" : "end")); final boolean moveSecondBeforeFirst = inFirstNode.isStart(); if (inFirstNode.isStart() == inSecondNode.isStart()) { if (track.reverseRange(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex())) { inSecondNode.reverseSegment(); // System.out.println(" : Reverse segment: " + inSecondNode.getEarlierIndex() + " - " + inSecondNode.getLaterIndex()); } else { System.err.println("Oops, reverse range didn't work"); // TODO: Abort? } } if (moveSecondBeforeFirst) { if ((inSecondNode.getLaterIndex()+1) != inFirstNode.getPointIndex()) { // System.out.println(" : Move second segment before first"); cutAndMoveSegment(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex(), inFirstNode.getPointIndex()); } } else if ((inFirstNode.getLaterIndex()+1) != inSecondNode.getPointIndex()) { // System.out.println(" : Move first segment before second (because " + (inFirstNode.getLaterIndex()+1) + " isn't " + inSecondNode.getPointIndex() + ")"); cutAndMoveSegment(inFirstNode.getEarlierIndex(), inFirstNode.getLaterIndex(), inSecondNode.getPointIndex()); } // Now merge the SegmentEnds so that they're not split up again if (inSecondNode.getEarlierIndex() == (inFirstNode.getLaterIndex()+1)) { // System.out.println("second node is now directly after the first node"); } else if (inFirstNode.getEarlierIndex() == (inSecondNode.getLaterIndex()+1)) { //System.out.println("first node is now directly after the second node"); } else { System.err.println("Why aren't the segments directly consecutive after the join?"); } // Find the earliest and latest ends of these two segments SegmentEnd earlierSegmentEnd = (inFirstNode.getEarlierIndex() < inSecondNode.getEarlierIndex() ? inFirstNode : inSecondNode).getEarlierEnd(); SegmentEnd laterSegmentEnd = (inFirstNode.getLaterIndex() > inSecondNode.getLaterIndex() ? inFirstNode : inSecondNode).getLaterEnd(); // Get rid of the inner two segment ends, join the earliest and latest together earlierSegmentEnd.getOtherEnd().deactivate(); laterSegmentEnd.getOtherEnd().deactivate(); earlierSegmentEnd.setOtherEnd(laterSegmentEnd); laterSegmentEnd.setOtherEnd(earlierSegmentEnd); } /** * Cut and move the segment to a different position * @param inSegmentStart start index of segment * @param inSegmentEnd end index of segment * @param inMoveToPos index before which the segment should be moved */ private void cutAndMoveSegment(int inSegmentStart, int inSegmentEnd, int inMoveToPos) { if (!_app.getTrackInfo().getTrack().cutAndMoveSection(inSegmentStart, inSegmentEnd, inMoveToPos)) { System.err.println(" Oops, cut and move didn't work"); // TODO: Throw exception? Return false? } else { // Loop over each node to inform it of the index changes for (SegmentEnd node : _nodes) { node.adjustPointIndex(inSegmentStart, inSegmentEnd, inMoveToPos); } } } /** * The final step of the sewing, removing the duplicate points at the start of each segment * @param inTrack track object * @return number of points deleted */ private static int deleteSegmentStartPoints(Track inTrack) { final int numPoints = inTrack.getNumPoints(); boolean[] deleteFlags = new boolean[numPoints]; // Loop over points in track, setting delete flags int numToDelete = 0; DataPoint prevPoint = null; for (int i=0; i