1 package tim.prune.function.sew;
3 import java.util.TreeSet;
6 import tim.prune.GenericFunction;
7 import tim.prune.I18nManager;
8 import tim.prune.UpdateMessageBroker;
9 import tim.prune.data.DataPoint;
10 import tim.prune.data.Track;
11 import tim.prune.function.Cancellable;
12 import tim.prune.gui.GenericProgressDialog;
13 import tim.prune.undo.UndoException;
14 import tim.prune.undo.UndoSewSegments;
17 * Function to sew the track segments together if possible,
18 * reversing and moving as required
20 public class SewTrackSegmentsFunction extends GenericFunction implements Runnable, Cancellable
22 /** Set of sorted segment endpoints */
23 private TreeSet<SegmentEnd> _nodes = null;
25 private boolean _cancelled = false;
29 public SewTrackSegmentsFunction(App inApp) {
33 /** @return name key */
34 public String getNameKey() {
35 return "function.sewsegments";
39 * Execute the function
43 // Run in separate thread, with progress bar
44 new Thread(this).start();
48 * Run the function in a separate thread
52 // Make a progress bar
53 GenericProgressDialog progressDialog = new GenericProgressDialog(getNameKey(), null, _parentFrame, this);
54 progressDialog.show();
55 // Make an undo object to store the current points and sequence
56 UndoSewSegments undo = new UndoSewSegments(_app.getTrackInfo().getTrack());
58 // Make list of all the segment ends
59 _nodes = buildNodeList(_app.getTrackInfo().getTrack());
60 final int numNodes = (_nodes == null ? 0 : _nodes.size());
63 System.out.println("Can't do anything with this, not enough segments");
64 progressDialog.close();
68 progressDialog.showProgress(10, 100); // Say 10% for building the nodes
70 // Disable messaging because we're probably doing a lot of reverses and moves
71 UpdateMessageBroker.enableMessaging(false);
72 // Set now contains all pairs of segment ends, ends at the same location are adjacent
73 // Now we're just interested in pairs of nodes, not three or more at the same location
74 SegmentEnd firstNode = null, secondNode = null;
75 int numJoins = 0, currNode = 0;
76 for (SegmentEnd node : _nodes)
78 if (!node.isActive()) {continue;}
79 if (firstNode == null)
83 else if (secondNode == null)
85 if (node.atSamePointAs(firstNode)) {
92 else if (node.atSamePointAs(secondNode))
94 // Found three colocated nodes, not interested
95 firstNode = secondNode = null;
100 joinSegments(firstNode, secondNode);
102 firstNode = node; secondNode = null;
104 if (_cancelled) {break;}
105 final double fractionDone = 1.0 * currNode / numNodes;
106 progressDialog.showProgress(10 + (int) (fractionDone * 80), 100);
109 if (firstNode != null && secondNode != null)
111 joinSegments(firstNode, secondNode);
115 progressDialog.showProgress(90, 100); // Say 90%, only duplicate point deletion left
117 // Delete the duplicate points
118 final int numDeleted = _cancelled ? 0 : deleteSegmentStartPoints(_app.getTrackInfo().getTrack());
120 progressDialog.close();
121 // Enable the messaging again
122 UpdateMessageBroker.enableMessaging(true);
123 if (_cancelled) // TODO: Also revert if any of the operations failed
125 // try to restore using undo object
127 undo.performUndo(_app.getTrackInfo());
129 catch (UndoException ue) {
130 _app.showErrorMessage("oops", "CANNOT UNDO");
133 else if (numJoins > 0 || numDeleted > 0)
135 // Give Undo object back to App to confirm
136 final String confirmMessage = (numJoins > 0 ? I18nManager.getTextWithNumber("confirm.sewsegments", numJoins)
137 : "" + numDeleted + " " + I18nManager.getText("confirm.deletepoint.multi"));
138 _app.completeFunction(undo, confirmMessage);
139 UpdateMessageBroker.informSubscribers();
144 _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getTextWithNumber("error.sewsegments.nothingdone", numNodes/2));
150 * Build a sorted list of all the segment start points and end points
151 * Creates a TreeSet containing two SegmentEnd objects for each segment
152 * @param inTrack track object
153 * @return sorted list of segment ends
155 private static TreeSet<SegmentEnd> buildNodeList(Track inTrack)
157 TreeSet<SegmentEnd> nodes = new TreeSet<SegmentEnd>();
158 final int numPoints = inTrack.getNumPoints();
159 DataPoint prevTrackPoint = null;
160 int prevTrackPointIndex = -1;
161 SegmentEnd segmentStart = null;
162 for (int i=0; i<numPoints; i++)
164 DataPoint point = inTrack.getPoint(i);
165 if (!point.isWaypoint() && !point.hasMedia())
167 if (point.getSegmentStart())
169 // Start of new segment - does previous one need to be saved?
170 if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
172 // Finish previous segment and store in list
173 SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex);
174 segmentStart.setOtherEnd(segmentEnd);
175 segmentEnd.setOtherEnd(segmentStart);
176 // Don't add closed loops
177 if (!segmentStart.atSamePointAs(segmentEnd))
179 nodes.add(segmentStart);
180 nodes.add(segmentEnd);
183 // Remember segment start
184 segmentStart = new SegmentEnd(point, i);
186 prevTrackPoint = point;
187 prevTrackPointIndex = i;
190 // Probably need to deal with segmentStart and prevTrackPoint, prevTrackPointIndex
191 if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
193 // Finish last segment and store in list
194 SegmentEnd segmentEnd = new SegmentEnd(prevTrackPoint, prevTrackPointIndex);
195 segmentStart.setOtherEnd(segmentEnd);
196 segmentEnd.setOtherEnd(segmentStart);
197 // Don't add closed loops
198 if (!segmentStart.atSamePointAs(segmentEnd))
200 nodes.add(segmentStart);
201 nodes.add(segmentEnd);
208 * Join the two segments together represented by the given nodes
209 * @param inFirstNode first node (order doesn't matter)
210 * @param inSecondNode other node
212 private void joinSegments(SegmentEnd inFirstNode, SegmentEnd inSecondNode)
214 final Track track = _app.getTrackInfo().getTrack();
215 // System.out.println("Join: (" + inFirstNode.getPointIndex() + "-" + inFirstNode.getOtherPointIndex() + ") with ("
216 // + inSecondNode.getPointIndex() + "-" + inSecondNode.getOtherPointIndex() + ")");
217 // System.out.println(" : " + (inFirstNode.isStart() ? "start" : "end") + " to " + (inSecondNode.isStart() ? "start" : "end"));
218 final boolean moveSecondBeforeFirst = inFirstNode.isStart();
219 if (inFirstNode.isStart() == inSecondNode.isStart())
221 if (track.reverseRange(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex()))
223 inSecondNode.reverseSegment();
224 // System.out.println(" : Reverse segment: " + inSecondNode.getEarlierIndex() + " - " + inSecondNode.getLaterIndex());
227 System.err.println("Oops, reverse range didn't work");
231 if (moveSecondBeforeFirst)
233 if ((inSecondNode.getLaterIndex()+1) != inFirstNode.getPointIndex())
235 // System.out.println(" : Move second segment before first");
236 cutAndMoveSegment(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex(), inFirstNode.getPointIndex());
239 else if ((inFirstNode.getLaterIndex()+1) != inSecondNode.getPointIndex())
241 // System.out.println(" : Move first segment before second (because " + (inFirstNode.getLaterIndex()+1) + " isn't " + inSecondNode.getPointIndex() + ")");
242 cutAndMoveSegment(inFirstNode.getEarlierIndex(), inFirstNode.getLaterIndex(), inSecondNode.getPointIndex());
244 // Now merge the SegmentEnds so that they're not split up again
245 if (inSecondNode.getEarlierIndex() == (inFirstNode.getLaterIndex()+1)) {
246 // System.out.println("second node is now directly after the first node");
248 else if (inFirstNode.getEarlierIndex() == (inSecondNode.getLaterIndex()+1)) {
249 //System.out.println("first node is now directly after the second node");
252 System.err.println("Why aren't the segments directly consecutive after the join?");
254 // Find the earliest and latest ends of these two segments
255 SegmentEnd earlierSegmentEnd = (inFirstNode.getEarlierIndex() < inSecondNode.getEarlierIndex() ? inFirstNode : inSecondNode).getEarlierEnd();
256 SegmentEnd laterSegmentEnd = (inFirstNode.getLaterIndex() > inSecondNode.getLaterIndex() ? inFirstNode : inSecondNode).getLaterEnd();
257 // Get rid of the inner two segment ends, join the earliest and latest together
258 earlierSegmentEnd.getOtherEnd().deactivate();
259 laterSegmentEnd.getOtherEnd().deactivate();
260 earlierSegmentEnd.setOtherEnd(laterSegmentEnd);
261 laterSegmentEnd.setOtherEnd(earlierSegmentEnd);
265 * Cut and move the segment to a different position
266 * @param inSegmentStart start index of segment
267 * @param inSegmentEnd end index of segment
268 * @param inMoveToPos index before which the segment should be moved
270 private void cutAndMoveSegment(int inSegmentStart, int inSegmentEnd, int inMoveToPos)
272 if (!_app.getTrackInfo().getTrack().cutAndMoveSection(inSegmentStart, inSegmentEnd, inMoveToPos))
274 System.err.println(" Oops, cut and move didn't work");
275 // TODO: Throw exception? Return false?
279 // Loop over each node to inform it of the index changes
280 for (SegmentEnd node : _nodes) {
281 node.adjustPointIndex(inSegmentStart, inSegmentEnd, inMoveToPos);
287 * The final step of the sewing, removing the duplicate points at the start of each segment
288 * @param inTrack track object
289 * @return number of points deleted
291 private static int deleteSegmentStartPoints(Track inTrack)
293 final int numPoints = inTrack.getNumPoints();
294 boolean[] deleteFlags = new boolean[numPoints];
295 // Loop over points in track, setting delete flags
297 DataPoint prevPoint = null;
298 for (int i=0; i<numPoints; i++)
300 DataPoint point = inTrack.getPoint(i);
301 if (!point.isWaypoint())
303 if (prevPoint != null && point.getSegmentStart() && point.isDuplicate(prevPoint))
305 deleteFlags[i] = true;
311 // Make new datapoint array of the right size
312 DataPoint[] pointCopies = new DataPoint[numPoints - numToDelete];
313 // Loop over points again, keeping the ones we want
315 for (int i=0; i<numPoints; i++)
317 if (!deleteFlags[i]) {
318 pointCopies[copyIndex] = inTrack.getPoint(i);
322 // Finally, replace the copied points in the track
323 inTrack.replaceContents(pointCopies);
327 /** Function cancelled by progress dialog */
328 public void cancel() {