File: SewTrackSegmentsFunction.java

package info (click to toggle)
gpsprune 17-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 3,984 kB
  • ctags: 5,218
  • sloc: java: 39,403; sh: 25; makefile: 17; python: 15
file content (331 lines) | stat: -rw-r--r-- 11,112 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
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<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;
	}
}