1 package tim.prune.data;
5 import tim.prune.Config;
6 import tim.prune.UpdateMessageBroker;
7 import tim.prune.function.edit.FieldEdit;
8 import tim.prune.function.edit.FieldEditList;
9 import tim.prune.gui.map.MapUtils;
13 * Class to hold all track information,
14 * including track points and waypoints
19 private DataPoint[] _dataPoints = null;
21 private double[] _xValues = null;
22 private double[] _yValues = null;
23 private boolean _scaled = false;
24 private int _numPoints = 0;
25 private boolean _hasTrackpoint = false;
26 private boolean _hasWaypoint = false;
28 private FieldList _masterFieldList = null;
30 private AltitudeRange _altitudeRange = null;
31 private DoubleRange _latRange = null, _longRange = null;
32 private DoubleRange _xRange = null, _yRange = null;
36 * Constructor for empty track
41 _masterFieldList = new FieldList(null);
42 // make empty DataPoint array
43 _dataPoints = new DataPoint[0];
51 * Load method, for initialising and reinitialising data
52 * @param inFieldArray array of Field objects describing fields
53 * @param inPointArray 2d object array containing data
54 * @param inAltFormat altitude format
56 public void load(Field[] inFieldArray, Object[][] inPointArray, Altitude.Format inAltFormat)
58 if (inFieldArray == null || inPointArray == null)
64 _masterFieldList = new FieldList(inFieldArray);
65 // make DataPoint object from each point in inPointList
66 _dataPoints = new DataPoint[inPointArray.length];
67 String[] dataArray = null;
69 for (int p=0; p < inPointArray.length; p++)
71 dataArray = (String[]) inPointArray[p];
72 // Convert to DataPoint objects
73 DataPoint point = new DataPoint(dataArray, _masterFieldList, inAltFormat);
76 _dataPoints[pointIndex] = point;
80 _numPoints = pointIndex;
81 // Set first track point to be start of segment
82 DataPoint firstTrackPoint = getNextTrackPoint(0);
83 if (firstTrackPoint != null) {
84 firstTrackPoint.setSegmentStart(true);
92 * Load the track by transferring the contents from a loaded Track object
93 * @param inOther Track object containing loaded data
95 public void load(Track inOther)
97 _numPoints = inOther._numPoints;
98 _masterFieldList = inOther._masterFieldList;
99 _dataPoints = inOther._dataPoints;
100 // needs to be scaled
104 ////////////////// Modification methods //////////////////////
108 * Combine this Track with new data
109 * @param inOtherTrack other track to combine
111 public void combine(Track inOtherTrack)
114 _masterFieldList = _masterFieldList.merge(inOtherTrack._masterFieldList);
115 // expand data array and add other track's data points
116 int totalPoints = getNumPoints() + inOtherTrack.getNumPoints();
117 DataPoint[] mergedPoints = new DataPoint[totalPoints];
118 System.arraycopy(_dataPoints, 0, mergedPoints, 0, getNumPoints());
119 System.arraycopy(inOtherTrack._dataPoints, 0, mergedPoints, getNumPoints(), inOtherTrack.getNumPoints());
120 _dataPoints = mergedPoints;
121 // combine point count
122 _numPoints = totalPoints;
123 // needs to be scaled again
126 UpdateMessageBroker.informSubscribers();
131 * Crop the track to the given size - subsequent points are not (yet) deleted
132 * @param inNewSize new number of points in track
134 public void cropTo(int inNewSize)
136 if (inNewSize >= 0 && inNewSize < getNumPoints())
138 _numPoints = inNewSize;
139 // needs to be scaled again
141 UpdateMessageBroker.informSubscribers();
147 * Delete the points marked for deletion
148 * @return number of points deleted
150 public int deleteMarkedPoints()
153 // Copy selected points
154 DataPoint[] newPointArray = new DataPoint[_numPoints];
155 for (int i=0; i<_numPoints; i++)
157 DataPoint point = _dataPoints[i];
158 // Don't delete photo points
159 if (point.getPhoto() != null || !point.getDeleteFlag())
161 newPointArray[numCopied] = point;
166 // Copy array references
167 int numDeleted = _numPoints - numCopied;
170 _dataPoints = new DataPoint[numCopied];
171 System.arraycopy(newPointArray, 0, _dataPoints, 0, numCopied);
172 _numPoints = _dataPoints.length;
180 * Delete the specified point
181 * @param inIndex point index
182 * @return true if successful
184 public boolean deletePoint(int inIndex)
186 boolean answer = deleteRange(inIndex, inIndex);
192 * Delete the specified range of points from the Track
193 * @param inStart start of range (inclusive)
194 * @param inEnd end of range (inclusive)
195 * @return true if successful
197 public boolean deleteRange(int inStart, int inEnd)
199 if (inStart < 0 || inEnd < 0 || inEnd < inStart)
201 // no valid range selected so can't delete
204 // check through range to be deleted, and see if any new segment flags present
205 boolean hasSegmentStart = false;
206 DataPoint nextTrackPoint = getNextTrackPoint(inEnd+1);
207 if (nextTrackPoint != null) {
208 for (int i=inStart; i<=inEnd && !hasSegmentStart; i++) {
209 hasSegmentStart |= _dataPoints[i].getSegmentStart();
211 // If segment break found, make sure next trackpoint also has break
212 if (hasSegmentStart) {nextTrackPoint.setSegmentStart(true);}
214 // valid range, let's delete it
215 int numToDelete = inEnd - inStart + 1;
216 DataPoint[] newPointArray = new DataPoint[_numPoints - numToDelete];
217 // Copy points before the selected range
220 System.arraycopy(_dataPoints, 0, newPointArray, 0, inStart);
222 // Copy points after the deleted one(s)
223 if (inEnd < (_numPoints - 1))
225 System.arraycopy(_dataPoints, inEnd + 1, newPointArray, inStart,
226 _numPoints - inEnd - 1);
228 // Copy points over original array
229 _dataPoints = newPointArray;
230 _numPoints -= numToDelete;
231 // needs to be scaled again
238 * Reverse the specified range of points
239 * @param inStart start index
240 * @param inEnd end index
241 * @return true if successful, false otherwise
243 public boolean reverseRange(int inStart, int inEnd)
245 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints)
249 // calculate how many point swaps are required
250 int numPointsToReverse = (inEnd - inStart + 1) / 2;
252 for (int i=0; i<numPointsToReverse; i++)
254 // swap pairs of points
255 p = _dataPoints[inStart + i];
256 _dataPoints[inStart + i] = _dataPoints[inEnd - i];
257 _dataPoints[inEnd - i] = p;
259 // adjust segment starts
260 shiftSegmentStarts(inStart, inEnd);
261 // Find first track point and following track point, and set segment starts to true
262 DataPoint firstTrackPoint = getNextTrackPoint(inStart);
263 if (firstTrackPoint != null) {firstTrackPoint.setSegmentStart(true);}
264 DataPoint nextTrackPoint = getNextTrackPoint(inEnd+1);
265 if (nextTrackPoint != null) {nextTrackPoint.setSegmentStart(true);}
266 // needs to be scaled again
268 UpdateMessageBroker.informSubscribers();
274 * Add the given time offset to the specified range
275 * @param inStart start of range
276 * @param inEnd end of range
277 * @param inOffset offset to add (-ve to subtract)
278 * @return true on success
280 public boolean addTimeOffset(int inStart, int inEnd, long inOffset)
283 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
286 boolean foundTimestamp = false;
287 // Loop over all points within range
288 for (int i=inStart; i<=inEnd; i++)
290 Timestamp timestamp = _dataPoints[i].getTimestamp();
291 if (timestamp != null)
293 // This point has a timestamp so add the offset to it
294 foundTimestamp = true;
295 timestamp.addOffset(inOffset);
298 return foundTimestamp;
302 * Add the given altitude offset to the specified range
303 * @param inStart start of range
304 * @param inEnd end of range
305 * @param inOffset offset to add (-ve to subtract)
306 * @param inFormat altitude format of offset
307 * @param inDecimals number of decimal places in offset
308 * @return true on success
310 public boolean addAltitudeOffset(int inStart, int inEnd, double inOffset,
311 Altitude.Format inFormat, int inDecimals)
314 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints) {
317 boolean foundAlt = false;
318 // Loop over all points within range
319 for (int i=inStart; i<=inEnd; i++)
321 Altitude alt = _dataPoints[i].getAltitude();
322 if (alt != null && alt.isValid())
324 // This point has an altitude so add the offset to it
326 alt.addOffset(inOffset, inFormat, inDecimals);
329 // needs to be scaled again
334 // TODO: Function to collect and sort photo points by time or photo filename
335 // TODO: Function to convert waypoint names into timestamps
338 * Collect all waypoints to the start or end of the track
339 * @param inAtStart true to collect at start, false for end
340 * @return true if successful, false if no change
342 public boolean collectWaypoints(boolean inAtStart)
344 // Check for mixed data, numbers of waypoints & nons
345 int numWaypoints = 0, numNonWaypoints = 0;
346 boolean wayAfterNon = false, nonAfterWay = false;
347 DataPoint[] waypoints = new DataPoint[_numPoints];
348 DataPoint[] nonWaypoints = new DataPoint[_numPoints];
349 DataPoint point = null;
350 for (int i=0; i<_numPoints; i++)
352 point = _dataPoints[i];
353 if (point.isWaypoint())
355 waypoints[numWaypoints] = point;
357 wayAfterNon |= (numNonWaypoints > 0);
361 nonWaypoints[numNonWaypoints] = point;
363 nonAfterWay |= (numWaypoints > 0);
366 // Exit if the data is already in the specified order
367 if (numWaypoints == 0 || numNonWaypoints == 0
368 || (inAtStart && !wayAfterNon && nonAfterWay)
369 || (!inAtStart && wayAfterNon && !nonAfterWay))
374 // Copy the arrays back into _dataPoints in the specified order
377 System.arraycopy(waypoints, 0, _dataPoints, 0, numWaypoints);
378 System.arraycopy(nonWaypoints, 0, _dataPoints, numWaypoints, numNonWaypoints);
382 System.arraycopy(nonWaypoints, 0, _dataPoints, 0, numNonWaypoints);
383 System.arraycopy(waypoints, 0, _dataPoints, numNonWaypoints, numWaypoints);
385 // needs to be scaled again
387 UpdateMessageBroker.informSubscribers();
393 * Interleave all waypoints by each nearest track point
394 * @return true if successful, false if no change
396 public boolean interleaveWaypoints()
398 // Separate waypoints and find nearest track point
399 int numWaypoints = 0;
400 DataPoint[] waypoints = new DataPoint[_numPoints];
401 int[] pointIndices = new int[_numPoints];
402 DataPoint point = null;
404 for (i=0; i<_numPoints; i++)
406 point = _dataPoints[i];
407 if (point.isWaypoint())
409 waypoints[numWaypoints] = point;
410 pointIndices[numWaypoints] = getNearestPointIndex(
411 _xValues[i], _yValues[i], -1.0, true);
415 // Exit if data not mixed
416 if (numWaypoints == 0 || numWaypoints == _numPoints)
419 // Loop round points copying to correct order
420 DataPoint[] dataCopy = new DataPoint[_numPoints];
422 for (i=0; i<_numPoints; i++)
424 point = _dataPoints[i];
425 // if it's a track point, copy it
426 if (!point.isWaypoint())
428 dataCopy[copyIndex] = point;
431 // check for waypoints with this index
432 for (int j=0; j<numWaypoints; j++)
434 if (pointIndices[j] == i)
436 dataCopy[copyIndex] = waypoints[j];
441 // Copy data back to track
442 _dataPoints = dataCopy;
443 // needs to be scaled again to recalc x, y
445 UpdateMessageBroker.informSubscribers();
451 * Cut and move the specified section
452 * @param inSectionStart start index of section
453 * @param inSectionEnd end index of section
454 * @param inMoveTo index of move to point
455 * @return true if move successful
457 public boolean cutAndMoveSection(int inSectionStart, int inSectionEnd, int inMoveTo)
459 // TODO: Move cut/move into separate function?
460 // Check that indices make sense
461 if (inSectionStart > 0 && inSectionEnd > inSectionStart && inMoveTo >= 0
462 && (inMoveTo < inSectionStart || inMoveTo > (inSectionEnd+1)))
464 // do the cut and move
465 DataPoint[] newPointArray = new DataPoint[_numPoints];
466 // System.out.println("Cut/move section (" + inSectionStart + " - " + inSectionEnd + ") to before point " + inMoveTo);
467 // Is it a forward copy or a backward copy?
468 if (inSectionStart > inMoveTo)
470 int sectionLength = inSectionEnd - inSectionStart + 1;
471 // move section to earlier point
473 System.arraycopy(_dataPoints, 0, newPointArray, 0, inMoveTo); // unchanged points before
475 System.arraycopy(_dataPoints, inSectionStart, newPointArray, inMoveTo, sectionLength); // moved bit
476 // after insertion point, before moved bit
477 System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo + sectionLength, inSectionStart - inMoveTo);
479 if (inSectionEnd < (_numPoints - 1)) {
480 System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionEnd+1, _numPoints - inSectionEnd - 1);
485 // Move section to later point
486 if (inSectionStart > 0) {
487 System.arraycopy(_dataPoints, 0, newPointArray, 0, inSectionStart); // unchanged points before
489 // from end of section to move to point
490 if (inMoveTo > (inSectionEnd + 1)) {
491 System.arraycopy(_dataPoints, inSectionEnd+1, newPointArray, inSectionStart, inMoveTo - inSectionEnd - 1);
494 System.arraycopy(_dataPoints, inSectionStart, newPointArray, inSectionStart + inMoveTo - inSectionEnd - 1,
495 inSectionEnd - inSectionStart + 1);
496 // unchanged bit after
497 if (inSectionEnd < (_numPoints - 1)) {
498 System.arraycopy(_dataPoints, inMoveTo, newPointArray, inMoveTo, _numPoints - inMoveTo);
501 // Copy array references
502 _dataPoints = newPointArray;
511 * Interpolate extra points between two selected ones
512 * @param inStartIndex start index of interpolation
513 * @param inNumPoints num points to insert
514 * @return true if successful
516 public boolean interpolate(int inStartIndex, int inNumPoints)
519 if (inStartIndex < 0 || inStartIndex >= _numPoints || inNumPoints <= 0)
522 // get start and end points
523 DataPoint startPoint = getPoint(inStartIndex);
524 DataPoint endPoint = getPoint(inStartIndex + 1);
526 // Make array of points to insert
527 DataPoint[] insertedPoints = startPoint.interpolate(endPoint, inNumPoints);
529 // Insert points into track
530 return insertRange(insertedPoints, inStartIndex + 1);
535 * Average selected points
536 * @param inStartIndex start index of selection
537 * @param inEndIndex end index of selection
538 * @return true if successful
540 public boolean average(int inStartIndex, int inEndIndex)
543 if (inStartIndex < 0 || inStartIndex >= _numPoints || inEndIndex <= inStartIndex)
546 DataPoint startPoint = getPoint(inStartIndex);
547 double firstLatitude = startPoint.getLatitude().getDouble();
548 double firstLongitude = startPoint.getLongitude().getDouble();
549 double latitudeDiff = 0.0, longitudeDiff = 0.0;
550 double totalAltitude = 0;
551 int numAltitudes = 0;
552 Altitude.Format altFormat = Config.getConfigBoolean(Config.KEY_METRIC_UNITS)?Altitude.Format.METRES:Altitude.Format.FEET;
553 // loop between start and end points
554 for (int i=inStartIndex; i<= inEndIndex; i++)
556 DataPoint currPoint = getPoint(i);
557 latitudeDiff += (currPoint.getLatitude().getDouble() - firstLatitude);
558 longitudeDiff += (currPoint.getLongitude().getDouble() - firstLongitude);
559 if (currPoint.hasAltitude()) {
560 totalAltitude += currPoint.getAltitude().getValue(altFormat);
564 int numPoints = inEndIndex - inStartIndex + 1;
565 double meanLatitude = firstLatitude + (latitudeDiff / numPoints);
566 double meanLongitude = firstLongitude + (longitudeDiff / numPoints);
567 Altitude meanAltitude = null;
568 if (numAltitudes > 0) {meanAltitude = new Altitude((int) (totalAltitude / numAltitudes), altFormat);}
570 DataPoint insertedPoint = new DataPoint(new Latitude(meanLatitude, Coordinate.FORMAT_NONE),
571 new Longitude(meanLongitude, Coordinate.FORMAT_NONE), meanAltitude);
572 // Make into singleton
573 insertedPoint.setSegmentStart(true);
574 DataPoint nextPoint = getNextTrackPoint(inEndIndex+1);
575 if (nextPoint != null) {nextPoint.setSegmentStart(true);}
576 // Insert points into track
577 return insertRange(new DataPoint[] {insertedPoint}, inEndIndex + 1);
582 * Append the specified points to the end of the track
583 * @param inPoints DataPoint objects to add
585 public void appendPoints(DataPoint[] inPoints)
587 // Insert points into track
588 if (inPoints != null && inPoints.length > 0)
590 insertRange(inPoints, _numPoints);
592 // needs to be scaled again to recalc x, y
594 UpdateMessageBroker.informSubscribers();
598 //////// information methods /////////////
602 * Get the point at the given index
603 * @param inPointNum index number, starting at 0
604 * @return DataPoint object, or null if out of range
606 public DataPoint getPoint(int inPointNum)
608 if (inPointNum > -1 && inPointNum < getNumPoints())
610 return _dataPoints[inPointNum];
617 * @return altitude range of points as AltitudeRange object
619 public AltitudeRange getAltitudeRange()
621 if (!_scaled) scalePoints();
622 return _altitudeRange;
626 * @return the number of (valid) points in the track
628 public int getNumPoints()
634 * @return The range of x values as a DoubleRange object
636 public DoubleRange getXRange()
638 if (!_scaled) scalePoints();
643 * @return The range of y values as a DoubleRange object
645 public DoubleRange getYRange()
647 if (!_scaled) scalePoints();
652 * @return The range of lat values as a DoubleRange object
654 public DoubleRange getLatRange()
656 if (!_scaled) scalePoints();
660 * @return The range of lon values as a DoubleRange object
662 public DoubleRange getLonRange()
664 if (!_scaled) scalePoints();
669 * @param inPointNum point index, starting at 0
670 * @return scaled x value of specified point
672 public double getX(int inPointNum)
674 if (!_scaled) scalePoints();
675 return _xValues[inPointNum];
679 * @param inPointNum point index, starting at 0
680 * @return scaled y value of specified point
682 public double getY(int inPointNum)
684 if (!_scaled) scalePoints();
685 return _yValues[inPointNum];
689 * @return the master field list
691 public FieldList getFieldList()
693 return _masterFieldList;
698 * Checks if any data exists for the specified field
699 * @param inField Field to examine
700 * @return true if data exists for this field
702 public boolean hasData(Field inField)
704 // Don't use this method for altitudes
705 if (inField.equals(Field.ALTITUDE)) {return hasAltitudeData();}
706 return hasData(inField, 0, _numPoints-1);
711 * Checks if any data exists for the specified field in the specified range
712 * @param inField Field to examine
713 * @param inStart start of range to check
714 * @param inEnd end of range to check (inclusive)
715 * @return true if data exists for this field
717 public boolean hasData(Field inField, int inStart, int inEnd)
719 // Loop over selected point range
720 for (int i=inStart; i<=inEnd; i++)
722 if (_dataPoints[i].getFieldValue(inField) != null)
724 // Check altitudes and timestamps
725 if ((inField != Field.ALTITUDE || _dataPoints[i].getAltitude().isValid())
726 && (inField != Field.TIMESTAMP || _dataPoints[i].getTimestamp().isValid()))
736 * @return true if track has altitude data (which are not all zero)
738 public boolean hasAltitudeData()
740 return getAltitudeRange().getMaximum() > 0;
744 * @return true if track contains at least one trackpoint
746 public boolean hasTrackPoints()
748 if (!_scaled) scalePoints();
749 return _hasTrackpoint;
753 * @return true if track contains waypoints
755 public boolean hasWaypoints()
757 if (!_scaled) scalePoints();
762 * @return true if track contains any points marked for deletion
764 public boolean hasMarkedPoints()
766 if (_numPoints < 1) {
769 // Loop over points looking for any marked for deletion
770 for (int i=0; i<=_numPoints-1; i++)
772 if (_dataPoints[i] != null && _dataPoints[i].getDeleteFlag()) {
781 * Clear all the deletion markers
783 public void clearDeletionMarkers()
785 for (int i=0; i<_numPoints; i++)
787 _dataPoints[i].setMarkedForDeletion(false);
792 * Collect all the waypoints into the given List
793 * @param inList List to fill with waypoints
795 public void getWaypoints(List<DataPoint> inList)
799 // loop over points and copy all waypoints into list
800 for (int i=0; i<=_numPoints-1; i++)
802 if (_dataPoints[i] != null && _dataPoints[i].isWaypoint())
804 inList.add(_dataPoints[i]);
811 * Search for the given Point in the track and return the index
812 * @param inPoint Point to look for
813 * @return index of Point, if any or -1 if not found
815 public int getPointIndex(DataPoint inPoint)
819 // Loop over points in track
820 for (int i=0; i<=_numPoints-1; i++)
822 if (_dataPoints[i] == inPoint)
833 ///////// Internal processing methods ////////////////
837 * Scale all the points in the track to gain x and y values
840 private void scalePoints()
842 // Loop through all points in track, to see limits of lat, long and altitude
843 _longRange = new DoubleRange();
844 _latRange = new DoubleRange();
845 _altitudeRange = new AltitudeRange();
847 _hasWaypoint = false; _hasTrackpoint = false;
848 for (p=0; p < getNumPoints(); p++)
850 DataPoint point = getPoint(p);
851 if (point != null && point.isValid())
853 _longRange.addValue(point.getLongitude().getDouble());
854 _latRange.addValue(point.getLatitude().getDouble());
855 if (point.getAltitude().isValid())
857 _altitudeRange.addValue(point.getAltitude());
859 if (point.isWaypoint())
862 _hasTrackpoint = true;
866 // Loop over points and calculate scales
867 _xValues = new double[getNumPoints()];
868 _yValues = new double[getNumPoints()];
869 _xRange = new DoubleRange();
870 _yRange = new DoubleRange();
871 for (p=0; p < getNumPoints(); p++)
873 DataPoint point = getPoint(p);
876 _xValues[p] = MapUtils.getXFromLongitude(point.getLongitude().getDouble());
877 _xRange.addValue(_xValues[p]);
878 _yValues[p] = MapUtils.getYFromLatitude(point.getLatitude().getDouble());
879 _yRange.addValue(_yValues[p]);
887 * Find the nearest point to the specified x and y coordinates
888 * or -1 if no point is within the specified max distance
889 * @param inX x coordinate
890 * @param inY y coordinate
891 * @param inMaxDist maximum distance from selected coordinates
892 * @param inJustTrackPoints true if waypoints should be ignored
893 * @return index of nearest point or -1 if not found
895 public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
897 int nearestPoint = 0;
898 double nearestDist = -1.0;
900 for (int i=0; i < getNumPoints(); i++)
902 if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
904 currDist = Math.abs(_xValues[i] - inX) + Math.abs(_yValues[i] - inY);
905 if (currDist < nearestDist || nearestDist < 0.0)
908 nearestDist = currDist;
912 // Check whether it's within required distance
913 if (nearestDist > inMaxDist && inMaxDist > 0.0)
921 * Get the next track point starting from the given index
922 * @param inStartIndex index to start looking from
923 * @return next track point, or null if end of data reached
925 public DataPoint getNextTrackPoint(int inStartIndex)
927 return getNextTrackPoint(inStartIndex, _numPoints, true);
931 * Get the next track point in the given range
932 * @param inStartIndex index to start looking from
933 * @param inEndIndex index to stop looking
934 * @return next track point, or null if end of data reached
936 public DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex)
938 return getNextTrackPoint(inStartIndex, inEndIndex, true);
942 * Get the previous track point starting from the given index
943 * @param inStartIndex index to start looking from
944 * @return next track point, or null if end of data reached
946 public DataPoint getPreviousTrackPoint(int inStartIndex)
948 return getNextTrackPoint(inStartIndex, _numPoints, false);
952 * Get the next track point starting from the given index
953 * @param inStartIndex index to start looking from
954 * @param inEndIndex index to stop looking (inclusive)
955 * @param inCountUp true for next, false for previous
956 * @return next track point, or null if end of data reached
958 private DataPoint getNextTrackPoint(int inStartIndex, int inEndIndex, boolean inCountUp)
960 // Loop forever over points
961 int increment = inCountUp?1:-1;
962 for (int i=inStartIndex; i<=inEndIndex; i+=increment)
964 DataPoint point = getPoint(i);
965 // Exit if end of data reached - there wasn't a track point
966 if (point == null) {return null;}
967 if (point.isValid() && !point.isWaypoint()) {
968 // next track point found
976 * Shift all the segment start flags in the given range by 1
977 * Method used by reverse range and its undo
978 * @param inStartIndex start of range, inclusive
979 * @param inEndIndex end of range, inclusive
981 public void shiftSegmentStarts(int inStartIndex, int inEndIndex)
983 boolean prevFlag = true;
984 boolean currFlag = true;
985 for (int i=inStartIndex; i<= inEndIndex; i++)
987 DataPoint point = getPoint(i);
988 if (point != null && !point.isWaypoint())
991 currFlag = point.getSegmentStart();
993 point.setSegmentStart(prevFlag);
999 ////////////////// Cloning and replacing ///////////////////
1002 * Clone the array of DataPoints
1003 * @return shallow copy of DataPoint objects
1005 public DataPoint[] cloneContents()
1007 DataPoint[] clone = new DataPoint[getNumPoints()];
1008 System.arraycopy(_dataPoints, 0, clone, 0, getNumPoints());
1014 * Clone the specified range of data points
1015 * @param inStart start index (inclusive)
1016 * @param inEnd end index (inclusive)
1017 * @return shallow copy of DataPoint objects
1019 public DataPoint[] cloneRange(int inStart, int inEnd)
1021 int numSelected = 0;
1022 if (inEnd >= 0 && inEnd >= inStart)
1024 numSelected = inEnd - inStart + 1;
1026 DataPoint[] result = new DataPoint[numSelected>0?numSelected:0];
1027 if (numSelected > 0)
1029 System.arraycopy(_dataPoints, inStart, result, 0, numSelected);
1036 * Re-insert the specified point at the given index
1037 * @param inPoint point to insert
1038 * @param inIndex index at which to insert the point
1039 * @return true if it worked, false otherwise
1041 public boolean insertPoint(DataPoint inPoint, int inIndex)
1043 if (inIndex > _numPoints || inPoint == null)
1047 // Make new array to copy points over to
1048 DataPoint[] newPointArray = new DataPoint[_numPoints + 1];
1051 System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
1053 newPointArray[inIndex] = inPoint;
1054 if (inIndex < _numPoints)
1056 System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+1, _numPoints - inIndex);
1058 // Change over to new array
1059 _dataPoints = newPointArray;
1061 // needs to be scaled again
1063 UpdateMessageBroker.informSubscribers();
1069 * Re-insert the specified point range at the given index
1070 * @param inPoints point array to insert
1071 * @param inIndex index at which to insert the points
1072 * @return true if it worked, false otherwise
1074 public boolean insertRange(DataPoint[] inPoints, int inIndex)
1076 if (inIndex > _numPoints || inPoints == null)
1080 // Make new array to copy points over to
1081 DataPoint[] newPointArray = new DataPoint[_numPoints + inPoints.length];
1084 System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
1086 System.arraycopy(inPoints, 0, newPointArray, inIndex, inPoints.length);
1087 if (inIndex < _numPoints)
1089 System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+inPoints.length, _numPoints - inIndex);
1091 // Change over to new array
1092 _dataPoints = newPointArray;
1093 _numPoints += inPoints.length;
1094 // needs to be scaled again
1096 UpdateMessageBroker.informSubscribers();
1102 * Replace the track contents with the given point array
1103 * @param inContents array of DataPoint objects
1104 * @return true on success
1106 public boolean replaceContents(DataPoint[] inContents)
1108 // master field array stays the same
1109 // (would need to store field array too if we wanted to redo a load)
1110 // replace data array
1111 _dataPoints = inContents;
1112 _numPoints = _dataPoints.length;
1114 UpdateMessageBroker.informSubscribers();
1120 * Edit the specified point
1121 * @param inPoint point to edit
1122 * @param inEditList list of edits to make
1123 * @return true if successful
1125 public boolean editPoint(DataPoint inPoint, FieldEditList inEditList)
1127 if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
1129 // remember if coordinates have changed
1130 boolean coordsChanged = false;
1131 // go through edits one by one
1132 int numEdits = inEditList.getNumEdits();
1133 for (int i=0; i<numEdits; i++)
1135 FieldEdit edit = inEditList.getEdit(i);
1136 Field editField = edit.getField();
1137 inPoint.setFieldValue(editField, edit.getValue());
1138 // Check that master field list has this field already (maybe point name has been added)
1139 if (!_masterFieldList.contains(editField)) {
1140 _masterFieldList.extendList(editField);
1142 // check coordinates
1143 coordsChanged |= (editField.equals(Field.LATITUDE)
1144 || editField.equals(Field.LONGITUDE) || editField.equals(Field.ALTITUDE));
1146 // set photo status if coordinates have changed
1147 if (inPoint.getPhoto() != null && coordsChanged)
1149 inPoint.getPhoto().setCurrentStatus(Photo.Status.CONNECTED);
1151 // point possibly needs to be scaled again
1153 // trigger listeners
1154 UpdateMessageBroker.informSubscribers();