]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/sew/SewTrackSegmentsFunction.java
Version 16, February 2014
[GpsPrune.git] / tim / prune / function / sew / SewTrackSegmentsFunction.java
1 package tim.prune.function.sew;
2
3 import java.util.TreeSet;
4
5 import tim.prune.App;
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;
15
16 /**
17  * Function to sew the track segments together if possible,
18  * reversing and moving as required
19  */
20 public class SewTrackSegmentsFunction extends GenericFunction implements Runnable, Cancellable
21 {
22         /** Set of sorted segment endpoints */
23         private TreeSet<SegmentEnd> _nodes = null;
24         /** Cancel flag */
25         private boolean _cancelled = false;
26
27
28         /** Constructor */
29         public SewTrackSegmentsFunction(App inApp) {
30                 super(inApp);
31         }
32
33         /** @return name key */
34         public String getNameKey() {
35                 return "function.sewsegments";
36         }
37
38         /**
39          * Execute the function
40          */
41         public void begin()
42         {
43                 // Run in separate thread, with progress bar
44                 new Thread(this).start();
45         }
46
47         /**
48          * Run the function in a separate thread
49          */
50         public void run()
51         {
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());
57
58                 // Make list of all the segment ends
59                 _nodes = buildNodeList(_app.getTrackInfo().getTrack());
60                 final int numNodes = (_nodes == null ? 0 : _nodes.size());
61                 if (numNodes < 4)
62                 {
63                         System.out.println("Can't do anything with this, not enough segments");
64                         progressDialog.close();
65                 }
66                 else
67                 {
68                         progressDialog.showProgress(10, 100); // Say 10% for building the nodes
69
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)
77                         {
78                                 if (!node.isActive()) {continue;}
79                                 if (firstNode == null)
80                                 {
81                                         firstNode = node;
82                                 }
83                                 else if (secondNode == null)
84                                 {
85                                         if (node.atSamePointAs(firstNode)) {
86                                                 secondNode = node;
87                                         }
88                                         else {
89                                                 firstNode = node;
90                                         }
91                                 }
92                                 else if (node.atSamePointAs(secondNode))
93                                 {
94                                         // Found three colocated nodes, not interested
95                                         firstNode = secondNode = null;
96                                 }
97                                 else
98                                 {
99                                         // Found a pair
100                                         joinSegments(firstNode, secondNode);
101                                         numJoins++;
102                                         firstNode = node; secondNode = null;
103                                 }
104                                 if (_cancelled) {break;}
105                                 final double fractionDone = 1.0 * currNode / numNodes;
106                                 progressDialog.showProgress(10 + (int) (fractionDone * 80), 100);
107                                 currNode++;
108                         }
109                         if (firstNode != null && secondNode != null)
110                         {
111                                 joinSegments(firstNode, secondNode);
112                                 numJoins++;
113                         }
114
115                         progressDialog.showProgress(90, 100); // Say 90%, only duplicate point deletion left
116
117                         // Delete the duplicate points
118                         final int numDeleted = _cancelled ? 0 : deleteSegmentStartPoints(_app.getTrackInfo().getTrack());
119
120                         progressDialog.close();
121                         // Enable the messaging again
122                         UpdateMessageBroker.enableMessaging(true);
123                         if (_cancelled) // TODO: Also revert if any of the operations failed
124                         {
125                                 // try to restore using undo object
126                                 try {
127                                         undo.performUndo(_app.getTrackInfo());
128                                 }
129                                 catch (UndoException ue) {
130                                         _app.showErrorMessage("oops", "CANNOT UNDO");
131                                 }
132                         }
133                         else if (numJoins > 0 || numDeleted > 0)
134                         {
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();
140                         }
141                         else
142                         {
143                                 // Nothing done
144                                 _app.showErrorMessageNoLookup(getNameKey(), I18nManager.getTextWithNumber("error.sewsegments.nothingdone", numNodes/2));
145                         }
146                 }
147         }
148
149         /**
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
154          */
155         private static TreeSet<SegmentEnd> buildNodeList(Track inTrack)
156         {
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++)
163                 {
164                         DataPoint point = inTrack.getPoint(i);
165                         if (!point.isWaypoint() && !point.hasMedia())
166                         {
167                                 if (point.getSegmentStart())
168                                 {
169                                         // Start of new segment - does previous one need to be saved?
170                                         if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
171                                         {
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))
178                                                 {
179                                                         nodes.add(segmentStart);
180                                                         nodes.add(segmentEnd);
181                                                 }
182                                         }
183                                         // Remember segment start
184                                         segmentStart = new SegmentEnd(point, i);
185                                 }
186                                 prevTrackPoint = point;
187                                 prevTrackPointIndex = i;
188                         }
189                 }
190                 // Probably need to deal with segmentStart and prevTrackPoint, prevTrackPointIndex
191                 if (segmentStart != null && prevTrackPointIndex > 0 && prevTrackPointIndex != segmentStart.getPointIndex())
192                 {
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))
199                         {
200                                 nodes.add(segmentStart);
201                                 nodes.add(segmentEnd);
202                         }
203                 }
204                 return nodes;
205         }
206
207         /**
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
211          */
212         private void joinSegments(SegmentEnd inFirstNode, SegmentEnd inSecondNode)
213         {
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())
220                 {
221                         if (track.reverseRange(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex()))
222                         {
223                                 inSecondNode.reverseSegment();
224                                 // System.out.println("    : Reverse segment: " + inSecondNode.getEarlierIndex() + " - " + inSecondNode.getLaterIndex());
225                         }
226                         else {
227                                 System.err.println("Oops, reverse range didn't work");
228                                 // TODO: Abort?
229                         }
230                 }
231                 if (moveSecondBeforeFirst)
232                 {
233                         if ((inSecondNode.getLaterIndex()+1) != inFirstNode.getPointIndex())
234                         {
235                                 // System.out.println("    : Move second segment before first");
236                                 cutAndMoveSegment(inSecondNode.getEarlierIndex(), inSecondNode.getLaterIndex(), inFirstNode.getPointIndex());
237                         }
238                 }
239                 else if ((inFirstNode.getLaterIndex()+1) != inSecondNode.getPointIndex())
240                 {
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());
243                 }
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");
247                 }
248                 else if (inFirstNode.getEarlierIndex() == (inSecondNode.getLaterIndex()+1)) {
249                         //System.out.println("first node is now directly after the second node");
250                 }
251                 else {
252                         System.err.println("Why aren't the segments directly consecutive after the join?");
253                 }
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);
262         }
263
264         /**
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
269          */
270         private void cutAndMoveSegment(int inSegmentStart, int inSegmentEnd, int inMoveToPos)
271         {
272                 if (!_app.getTrackInfo().getTrack().cutAndMoveSection(inSegmentStart, inSegmentEnd, inMoveToPos))
273                 {
274                         System.err.println("   Oops, cut and move didn't work");
275                         // TODO: Throw exception? Return false?
276                 }
277                 else
278                 {
279                         // Loop over each node to inform it of the index changes
280                         for (SegmentEnd node : _nodes) {
281                                 node.adjustPointIndex(inSegmentStart, inSegmentEnd, inMoveToPos);
282                         }
283                 }
284         }
285
286         /**
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
290          */
291         private static int deleteSegmentStartPoints(Track inTrack)
292         {
293                 final int numPoints = inTrack.getNumPoints();
294                 boolean[] deleteFlags = new boolean[numPoints];
295                 // Loop over points in track, setting delete flags
296                 int numToDelete = 0;
297                 DataPoint prevPoint = null;
298                 for (int i=0; i<numPoints; i++)
299                 {
300                         DataPoint point = inTrack.getPoint(i);
301                         if (!point.isWaypoint())
302                         {
303                                 if (prevPoint != null && point.getSegmentStart() && point.isDuplicate(prevPoint))
304                                 {
305                                         deleteFlags[i] = true;
306                                         numToDelete++;
307                                 }
308                                 prevPoint = point;
309                         }
310                 }
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
314                 int copyIndex = 0;
315                 for (int i=0; i<numPoints; i++)
316                 {
317                         if (!deleteFlags[i]) {
318                                 pointCopies[copyIndex] = inTrack.getPoint(i);
319                                 copyIndex++;
320                         }
321                 }
322                 // Finally, replace the copied points in the track
323                 inTrack.replaceContents(pointCopies);
324                 return numToDelete;
325         }
326
327         /** Function cancelled by progress dialog */
328         public void cancel() {
329                 _cancelled = true;
330         }
331 }