+++ /dev/null
-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<SegmentEnd> _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<SegmentEnd> buildNodeList(Track inTrack)
- {
- TreeSet<SegmentEnd> nodes = new TreeSet<SegmentEnd>();
- final int numPoints = inTrack.getNumPoints();
- DataPoint prevTrackPoint = null;
- int prevTrackPointIndex = -1;
- SegmentEnd segmentStart = null;
- for (int i=0; i<numPoints; i++)
- {
- DataPoint point = inTrack.getPoint(i);
- if (!point.isWaypoint() && !point.hasMedia())
- {
- if (point.getSegmentStart())
- {
- // Start of new segment - does previous one need to be saved?
- if (segmentStart != null && prevTrackPointIndex > 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<numPoints; i++)
- {
- DataPoint point = inTrack.getPoint(i);
- if (!point.isWaypoint())
- {
- if (prevPoint != null && point.getSegmentStart() && point.isDuplicate(prevPoint))
- {
- deleteFlags[i] = true;
- numToDelete++;
- }
- prevPoint = point;
- }
- }
- // Make new datapoint array of the right size
- DataPoint[] pointCopies = new DataPoint[numPoints - numToDelete];
- // Loop over points again, keeping the ones we want
- int copyIndex = 0;
- for (int i=0; i<numPoints; i++)
- {
- if (!deleteFlags[i]) {
- pointCopies[copyIndex] = inTrack.getPoint(i);
- copyIndex++;
- }
- }
- // Finally, replace the copied points in the track
- inTrack.replaceContents(pointCopies);
- return numToDelete;
- }
-
- /** Function cancelled by progress dialog */
- public void cancel() {
- _cancelled = true;
- }
-}