X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Ffunction%2Fsew%2FSewTrackSegmentsFunction.java;fp=src%2Ftim%2Fprune%2Ffunction%2Fsew%2FSewTrackSegmentsFunction.java;h=44ad9dba6f2c018b1605eaef463f7016798df6b2;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/function/sew/SewTrackSegmentsFunction.java b/src/tim/prune/function/sew/SewTrackSegmentsFunction.java new file mode 100644 index 0000000..44ad9db --- /dev/null +++ b/src/tim/prune/function/sew/SewTrackSegmentsFunction.java @@ -0,0 +1,331 @@ +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