1 package tim.prune.function;
3 import java.util.ArrayList;
6 import tim.prune.I18nManager;
7 import tim.prune.UpdateMessageBroker;
8 import tim.prune.data.DataPoint;
9 import tim.prune.data.Field;
10 import tim.prune.data.FieldList;
11 import tim.prune.data.RangeStats;
12 import tim.prune.data.Track;
13 import tim.prune.data.UnitSetLibrary;
14 import tim.prune.undo.UndoAppendPoints;
17 * Function to create waypoints marking regular distance intervals,
18 * regular time intervals, or halfway points
20 public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
22 /** ArrayList of points to append to the track */
23 private ArrayList<DataPoint> _pointsToAdd = new ArrayList<DataPoint>();
24 /** Counter of previously used multiple */
25 private int _previousMultiple = 0;
28 * Type of halfway point
30 private enum HalfwayType
40 public CreateMarkerWaypointsFunction(App inApp) {
47 public String getNameKey() {
48 return "function.createmarkerwaypoints";
52 * Init the state to start collecting a new set of points
54 private void initMemory()
57 _previousMultiple = 0;
61 * The dialog has been completed and OK pressed, so do the point creation
63 protected void performFunction()
65 // Determine which kind of markers to create
66 final int timeLimitSeconds = getTimeLimitInSeconds();
67 final boolean createByTime = (timeLimitSeconds > 0);
68 final double distLimitKm = getDistanceLimitKilometres();
69 final boolean createByDistance = (distLimitKm > 0.0);
70 final boolean createHalves = isHalvesSelected();
72 // set up the memory from scratch to collect the created points
75 if (createByTime || createByDistance) {
76 createWaypointsAtIntervals(timeLimitSeconds, distLimitKm);
78 else if (createHalves)
80 createHalfwayWaypoints();
87 if (!_pointsToAdd.isEmpty())
90 final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
91 UndoAppendPoints undo = new UndoAppendPoints(numPoints);
93 // Append created points to Track
94 Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE, Field.WAYPT_NAME};
95 final int numPointsToAdd = _pointsToAdd.size();
96 DataPoint[] waypoints = new DataPoint[numPointsToAdd];
97 _pointsToAdd.toArray(waypoints);
98 Track wpTrack = new Track(new FieldList(fields), waypoints);
99 _app.getTrackInfo().getTrack().combine(wpTrack);
101 undo.setNumPointsAppended(numPointsToAdd);
102 final String confirmMessage = I18nManager.getTextWithNumber("confirm.pointsadded", _pointsToAdd.size());
103 _app.completeFunction(undo, confirmMessage);
104 UpdateMessageBroker.informSubscribers();
110 * Create waypoints according to the given intervals
111 * @param inTimeLimitSeconds
112 * @param inDistLimitKm distance limit in kilometres
114 private void createWaypointsAtIntervals(int inTimeLimitSeconds, double inDistLimitKm)
116 final boolean createByTime = (inTimeLimitSeconds > 0);
117 final boolean createByDistance = (inDistLimitKm > 0.0);
119 // Make new waypoints, looping through the points in the track
120 DataPoint currPoint = null, prevPoint = null;
121 double currValue = 0.0, prevValue = 0.0;
122 RangeStats rangeStats = new RangeStats();
123 final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
124 for (int i=0; i<numPoints; i++)
126 currPoint = _app.getTrackInfo().getTrack().getPoint(i);
127 rangeStats.addPoint(currPoint);
129 if (!currPoint.isWaypoint())
131 // Calculate current value
134 currValue = rangeStats.getMovingDurationInSeconds();
135 processValue(prevPoint, prevValue, inTimeLimitSeconds, currPoint, currValue);
137 else if (createByDistance)
139 currValue = rangeStats.getMovingDistanceKilometres();
140 processValue(prevPoint, prevValue, inDistLimitKm, currPoint, currValue);
143 prevPoint = currPoint;
144 prevValue = currValue;
150 * Consider a pair of points in the track to see if a new marker should be inserted between them
151 * @param inPrevPoint previous point
152 * @param inPrevValue value of function at this previous point
153 * @param inLimit user-specified limit for marker values
154 * @param inCurrPoint current point
155 * @param inCurrValue value of function at this current point
157 private void processValue(DataPoint inPrevPoint, double inPrevValue, double inLimit,
158 DataPoint inCurrPoint, double inCurrValue)
160 // Check the current multiple and compare with previously used one
161 final int currMultiple = (int) Math.floor(inCurrValue / inLimit);
162 for (int m=_previousMultiple+1; m<=currMultiple; m++)
164 // Calculate position of limit between the two points
165 final double valueBeforeBreak = (m * inLimit) - inPrevValue;
166 final double valueAfterBreak = inCurrValue - (m * inLimit);
167 final double fractionFromPrev = valueBeforeBreak / (valueBeforeBreak + valueAfterBreak);
168 DataPoint marker = DataPoint.interpolate(inPrevPoint, inCurrPoint, fractionFromPrev);
169 marker.setFieldValue(Field.WAYPT_NAME, createLimitDescription(m), false);
170 _pointsToAdd.add(marker);
172 _previousMultiple = currMultiple;
176 * Create waypoints for the halfway markers
178 private void createHalfwayWaypoints()
180 // Calculate the details of the whole track so we can see what to halve
181 final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
182 RangeStats totalStats = new RangeStats();
183 DataPoint currPoint = null;
184 for (int i=0; i<numPoints; i++)
186 currPoint = _app.getTrackInfo().getTrack().getPoint(i);
187 totalStats.addPoint(currPoint);
189 // Calculate total moving distance of track
190 final double totalDist = totalStats.getMovingDistanceKilometres();
191 // If the track has altitudes, also calculate total climb and total descent
192 final double totalClimb = totalStats.getMovingAltitudeRange().getClimb(UnitSetLibrary.UNITS_METRES);
193 final double totalDescent = totalStats.getMovingAltitudeRange().getDescent(UnitSetLibrary.UNITS_METRES);
195 final double halfDistance = totalDist / 2.0;
196 final double halfClimb = totalClimb / 2.0;
197 final double halfDescent = totalDescent / 2.0;
199 // Now loop through points again, looking for the halfway points
200 RangeStats partialStats = new RangeStats();
201 DataPoint prevPoint = null;
202 boolean createdDistance = false, createdClimb = false, createdDescent = false;
203 double prevDistance = 0.0, prevClimb = 0.0, prevDescent = 0.0;
204 for (int i=0; i<numPoints; i++)
206 currPoint = _app.getTrackInfo().getTrack().getPoint(i);
207 partialStats.addPoint(currPoint);
208 if (!currPoint.isWaypoint())
211 if (!createdDistance && totalDist > 0.0)
213 final double currDist = partialStats.getMovingDistanceKilometres();
214 createdDistance = processHalfValue(prevPoint, prevDistance, halfDistance,
215 currPoint, currDist, HalfwayType.HALF_DISTANCE);
216 prevDistance = currDist;
219 if (!createdClimb && totalClimb > 0.0)
221 final double currClimb = partialStats.getMovingAltitudeRange().getClimb(UnitSetLibrary.UNITS_METRES);
222 createdClimb = processHalfValue(prevPoint, prevClimb, halfClimb,
223 currPoint, currClimb, HalfwayType.HALF_CLIMB);
224 prevClimb = currClimb;
227 if (!createdDescent && totalDescent > 0.0)
229 final double currDescent = partialStats.getMovingAltitudeRange().getDescent(UnitSetLibrary.UNITS_METRES);
230 createdDescent = processHalfValue(prevPoint, prevDescent, halfDescent,
231 currPoint, currDescent, HalfwayType.HALF_DESCENT);
232 prevDescent = currDescent;
235 prevPoint = currPoint;
241 * Consider a pair of points in the track to see if a new halfway marker should be inserted between them
242 * @param inPrevPoint previous point
243 * @param inPrevValue value of function at this previous point
244 * @param inTargetValue target halfway value
245 * @param inCurrPoint current point
246 * @param inCurrValue value of function at this current point
247 * @param inType type of halfway point
249 private boolean processHalfValue(DataPoint inPrevPoint, double inPrevValue, double inTargetValue,
250 DataPoint inCurrPoint, double inCurrValue, HalfwayType inType)
252 if (inPrevValue <= inTargetValue && inCurrValue >= inTargetValue)
254 // Calculate position of limit between the two points
255 final double valueBeforeBreak = inTargetValue - inPrevValue;
256 final double valueAfterBreak = inCurrValue - inTargetValue;
257 final double fractionFromPrev = valueBeforeBreak / (valueBeforeBreak + valueAfterBreak);
258 DataPoint marker = DataPoint.interpolate(inPrevPoint, inCurrPoint, fractionFromPrev);
259 marker.setFieldValue(Field.WAYPT_NAME, createHalfwayName(inType), false);
260 _pointsToAdd.add(marker);
267 * Create the name of the halfway point according to type
268 * @param inType type of point
270 private String createHalfwayName(HalfwayType inType)
272 String typeString = null;
276 typeString = "distance";
279 typeString = "climb";
282 typeString = "descent";
285 if (typeString != null)
287 return I18nManager.getText("dialog.markers.half." + typeString);