]> gitweb.fperrin.net Git - GpsPrune.git/blob - src/tim/prune/function/CreateMarkerWaypointsFunction.java
Version 20.4, May 2021
[GpsPrune.git] / src / tim / prune / function / CreateMarkerWaypointsFunction.java
1 package tim.prune.function;
2
3 import java.util.ArrayList;
4
5 import tim.prune.App;
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;
15
16 /**
17  * Function to create waypoints marking regular distance intervals,
18  * regular time intervals, or halfway points
19  */
20 public class CreateMarkerWaypointsFunction extends DistanceTimeLimitFunction
21 {
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;
26
27         /*
28          * Type of halfway point
29          */
30         private enum HalfwayType
31         {
32                 HALF_DISTANCE,
33                 HALF_CLIMB,
34                 HALF_DESCENT
35         }
36
37         /**
38          * Constructor
39          */
40         public CreateMarkerWaypointsFunction(App inApp) {
41                 super(inApp, true);
42         }
43
44         /**
45          * @return name key
46          */
47         public String getNameKey() {
48                 return "function.createmarkerwaypoints";
49         }
50
51         /**
52          * Init the state to start collecting a new set of points
53          */
54         private void initMemory()
55         {
56                 _pointsToAdd.clear();
57                 _previousMultiple = 0;
58         }
59
60         /**
61          * The dialog has been completed and OK pressed, so do the point creation
62          */
63         protected void performFunction()
64         {
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();
71
72                 // set up the memory from scratch to collect the created points
73                 initMemory();
74
75                 if (createByTime || createByDistance) {
76                         createWaypointsAtIntervals(timeLimitSeconds, distLimitKm);
77                 }
78                 else if (createHalves)
79                 {
80                         createHalfwayWaypoints();
81                 }
82                 else
83                 {
84                         return;
85                 }
86
87                 if (!_pointsToAdd.isEmpty())
88                 {
89                         // Make undo object
90                         final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
91                         UndoAppendPoints undo = new UndoAppendPoints(numPoints);
92
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);
100
101                         undo.setNumPointsAppended(numPointsToAdd);
102                         final String confirmMessage = I18nManager.getTextWithNumber("confirm.pointsadded", _pointsToAdd.size());
103                         _app.completeFunction(undo, confirmMessage);
104                         UpdateMessageBroker.informSubscribers();
105                 }
106                 _dialog.dispose();
107         }
108
109         /**
110          * Create waypoints according to the given intervals
111          * @param inTimeLimitSeconds
112          * @param inDistLimitKm distance limit in kilometres
113          */
114         private void createWaypointsAtIntervals(int inTimeLimitSeconds, double inDistLimitKm)
115         {
116                 final boolean createByTime = (inTimeLimitSeconds > 0);
117                 final boolean createByDistance = (inDistLimitKm > 0.0);
118
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++)
125                 {
126                         currPoint = _app.getTrackInfo().getTrack().getPoint(i);
127                         rangeStats.addPoint(currPoint);
128
129                         if (!currPoint.isWaypoint())
130                         {
131                                 // Calculate current value
132                                 if (createByTime)
133                                 {
134                                         currValue = rangeStats.getMovingDurationInSeconds();
135                                         processValue(prevPoint, prevValue, inTimeLimitSeconds, currPoint, currValue);
136                                 }
137                                 else if (createByDistance)
138                                 {
139                                         currValue = rangeStats.getMovingDistanceKilometres();
140                                         processValue(prevPoint, prevValue, inDistLimitKm, currPoint, currValue);
141                                 }
142
143                                 prevPoint = currPoint;
144                                 prevValue = currValue;
145                         }
146                 }
147         }
148
149         /**
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
156          */
157         private void processValue(DataPoint inPrevPoint, double inPrevValue, double inLimit,
158                 DataPoint inCurrPoint, double inCurrValue)
159         {
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++)
163                 {
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);
171                 }
172                 _previousMultiple = currMultiple;
173         }
174
175         /**
176          * Create waypoints for the halfway markers
177          */
178         private void createHalfwayWaypoints()
179         {
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++)
185                 {
186                         currPoint = _app.getTrackInfo().getTrack().getPoint(i);
187                         totalStats.addPoint(currPoint);
188                 }
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);
194
195                 final double halfDistance = totalDist / 2.0;
196                 final double halfClimb = totalClimb / 2.0;
197                 final double halfDescent = totalDescent / 2.0;
198
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++)
205                 {
206                         currPoint = _app.getTrackInfo().getTrack().getPoint(i);
207                         partialStats.addPoint(currPoint);
208                         if (!currPoint.isWaypoint())
209                         {
210                                 // distance
211                                 if (!createdDistance && totalDist > 0.0)
212                                 {
213                                         final double currDist = partialStats.getMovingDistanceKilometres();
214                                         createdDistance = processHalfValue(prevPoint, prevDistance, halfDistance,
215                                                 currPoint, currDist, HalfwayType.HALF_DISTANCE);
216                                         prevDistance = currDist;
217                                 }
218                                 // climb
219                                 if (!createdClimb && totalClimb > 0.0)
220                                 {
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;
225                                 }
226                                 // descent
227                                 if (!createdDescent && totalDescent > 0.0)
228                                 {
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;
233                                 }
234
235                                 prevPoint = currPoint;
236                         }
237                 }
238         }
239
240         /**
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
248          */
249         private boolean processHalfValue(DataPoint inPrevPoint, double inPrevValue, double inTargetValue,
250                 DataPoint inCurrPoint, double inCurrValue, HalfwayType inType)
251         {
252                 if (inPrevValue <= inTargetValue && inCurrValue >= inTargetValue)
253                 {
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);
261                         return true;
262                 }
263                 return false;
264         }
265
266         /**
267          * Create the name of the halfway point according to type
268          * @param inType type of point
269          */
270         private String createHalfwayName(HalfwayType inType)
271         {
272                 String typeString = null;
273                 switch (inType)
274                 {
275                         case HALF_DISTANCE:
276                                 typeString = "distance";
277                                 break;
278                         case HALF_CLIMB:
279                                 typeString = "climb";
280                                 break;
281                         case HALF_DESCENT:
282                                 typeString = "descent";
283                                 break;
284                 }
285                 if (typeString != null)
286                 {
287                         return I18nManager.getText("dialog.markers.half." + typeString);
288                 }
289                 return "half";
290         }
291 }