1 package tim.prune.data;
5 import tim.prune.UpdateMessageBroker;
6 import tim.prune.edit.FieldEdit;
7 import tim.prune.edit.FieldEditList;
11 * Class to hold all track information,
12 * including track points and waypoints
17 UpdateMessageBroker _broker = null;
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 _mixedData = false;
27 private FieldList _masterFieldList = null;
29 private AltitudeRange _altitudeRange = null;
30 private DoubleRange _latRange = null, _longRange = null;
31 private DoubleRange _xRange = null, _yRange = null;
35 * Constructor giving arrays of Fields and Objects
36 * @param inFieldArray field array
37 * @param inPointArray 2d array of field values
39 public Track(UpdateMessageBroker inBroker)
43 _masterFieldList = new FieldList(null);
44 // make empty DataPoint array
45 _dataPoints = new DataPoint[0];
53 * Load method, for initialising and reinitialising data
54 * @param inFieldArray array of Field objects describing fields
55 * @param inPointArray 2d object array containing data
56 * @param inAltFormat altitude format
58 public void load(Field[] inFieldArray, Object[][] inPointArray, int inAltFormat)
61 _masterFieldList = new FieldList(inFieldArray);
62 // make DataPoint object from each point in inPointList
63 _dataPoints = new DataPoint[inPointArray.length];
64 String[] dataArray = null;
66 for (int p=0; p < inPointArray.length; p++)
68 dataArray = (String[]) inPointArray[p];
69 // Convert to DataPoint objects
70 DataPoint point = new DataPoint(dataArray, _masterFieldList, inAltFormat);
73 _dataPoints[pointIndex] = point;
77 _numPoints = pointIndex;
83 ////////////////// Modification methods //////////////////////
87 * Combine this Track with new data
90 public void combine(Track inOtherTrack)
93 _masterFieldList = _masterFieldList.merge(inOtherTrack._masterFieldList);
94 // expand data array and add other track's data points
95 int totalPoints = getNumPoints() + inOtherTrack.getNumPoints();
96 DataPoint[] mergedPoints = new DataPoint[totalPoints];
97 System.arraycopy(_dataPoints, 0, mergedPoints, 0, getNumPoints());
98 System.arraycopy(inOtherTrack._dataPoints, 0, mergedPoints, getNumPoints(), inOtherTrack.getNumPoints());
99 _dataPoints = mergedPoints;
100 // combine point count
101 _numPoints = totalPoints;
102 // needs to be scaled again
105 _broker.informSubscribers();
110 * Crop the track to the given size - subsequent points are not (yet) deleted
111 * @param inNewSize new number of points in track
113 public void cropTo(int inNewSize)
115 if (inNewSize >= 0 && inNewSize < getNumPoints())
117 _numPoints = inNewSize;
118 // needs to be scaled again
120 _broker.informSubscribers();
126 * Compress the track to the given resolution
127 * @param inResolution resolution
128 * @return number of points deleted
130 public int compress(int inResolution)
132 // (maybe should be separate thread?)
133 // (maybe should be in separate class?)
134 // (maybe should be based on subtended angles instead of distances?)
136 if (inResolution <= 0) return 0;
138 // Establish range of track and minimum range between points
140 double wholeScale = _xRange.getMaximum() - _xRange.getMinimum();
141 double yscale = _yRange.getMaximum() - _yRange.getMinimum();
142 if (yscale > wholeScale) wholeScale = yscale;
143 double minDist = wholeScale / inResolution;
145 // Copy selected points
146 DataPoint[] newPointArray = new DataPoint[_numPoints];
147 int[] pointIndices = new int[_numPoints];
148 for (int i=0; i<_numPoints; i++)
150 boolean keepPoint = true;
151 if (!_dataPoints[i].isWaypoint())
153 // go through newPointArray to check for range
154 for (int j=0; j<numCopied && keepPoint; j++)
156 // calculate distance between point j and current point
157 double pointDist = Math.abs(_xValues[i] - _xValues[pointIndices[j]])
158 + Math.abs(_yValues[i] - _yValues[pointIndices[j]]);
159 if (pointDist < minDist)
165 newPointArray[numCopied] = _dataPoints[i];
166 pointIndices[numCopied] = i;
171 // Copy array references
172 int numDeleted = _numPoints - numCopied;
175 _dataPoints = new DataPoint[numCopied];
176 System.arraycopy(newPointArray, 0, _dataPoints, 0, numCopied);
177 _numPoints = _dataPoints.length;
179 _broker.informSubscribers();
186 * Halve the track by deleting alternate points
187 * @return number of points deleted
191 if (_numPoints < 100) return 0;
192 int newSize = _numPoints / 2;
193 int numDeleted = _numPoints - newSize;
194 DataPoint[] newPointArray = new DataPoint[newSize];
195 // Delete alternate points
196 for (int i=0; i<newSize; i++)
197 newPointArray[i] = _dataPoints[i*2];
198 // Copy array references
199 _dataPoints = newPointArray;
200 _numPoints = _dataPoints.length;
202 _broker.informSubscribers();
208 * Delete the specified point
209 * @return true if successful
211 public boolean deletePoint(int inIndex)
213 boolean answer = deleteRange(inIndex, inIndex);
219 * Delete the specified range of points from the Track
220 * @param inStart start of range (inclusive)
221 * @param inEnd end of range (inclusive)
222 * @return true if successful
224 public boolean deleteRange(int inStart, int inEnd)
226 // TODO: Check for deleting photos?
227 if (inStart < 0 || inEnd < 0 || inEnd < inStart)
229 // no valid range selected so can't delete
232 // valid range, let's delete it
233 int numToDelete = inEnd - inStart + 1;
234 DataPoint[] newPointArray = new DataPoint[_numPoints - numToDelete];
235 // Copy points before the selected range
238 System.arraycopy(_dataPoints, 0, newPointArray, 0, inStart);
240 // Copy points after the deleted one(s)
241 if (inEnd < (_numPoints - 1))
243 System.arraycopy(_dataPoints, inEnd + 1, newPointArray, inStart,
244 _numPoints - inEnd - 1);
246 // Copy points over original array (careful!)
247 _dataPoints = newPointArray;
248 _numPoints -= numToDelete;
249 // needs to be scaled again
256 * Delete all the duplicate points in the track
257 * @return number of points deleted
259 public int deleteDuplicates()
261 // loop through Track counting duplicates first
262 boolean[] dupes = new boolean[_numPoints];
265 for (i=1; i<_numPoints; i++)
267 DataPoint p1 = _dataPoints[i];
268 // Loop through all points before this one
269 for (j=0; j<i && !dupes[i]; j++)
271 DataPoint p2 = _dataPoints[j];
272 if (p1.isDuplicate(p2))
281 // Make new resized array and copy DataPoints over
282 DataPoint[] newPointArray = new DataPoint[_numPoints - numDupes];
284 for (i=0; i<_numPoints; i++)
288 newPointArray[j] = _dataPoints[i];
292 // Copy array references
293 _dataPoints = newPointArray;
294 _numPoints = _dataPoints.length;
296 _broker.informSubscribers();
303 * Reverse the specified range of points
304 * @return true if successful, false otherwise
306 public boolean reverseRange(int inStart, int inEnd)
308 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints)
312 // calculate how many point swaps are required
313 int numPointsToReverse = (inEnd - inStart + 1) / 2;
315 for (int i=0; i<numPointsToReverse; i++)
317 // swap pairs of points
318 p = _dataPoints[inStart + i];
319 _dataPoints[inStart + i] = _dataPoints[inEnd - i];
320 _dataPoints[inEnd - i] = p;
322 // needs to be scaled again
324 _broker.informSubscribers();
330 * Collect all waypoints to the start or end of the track
331 * @param inAtStart true to collect at start, false for end
332 * @return true if successful, false if no change
334 public boolean collectWaypoints(boolean inAtStart)
336 // Check for mixed data, numbers of waypoints & nons
337 int numWaypoints = 0, numNonWaypoints = 0;
338 boolean wayAfterNon = false, nonAfterWay = false;
339 DataPoint[] waypoints = new DataPoint[_numPoints];
340 DataPoint[] nonWaypoints = new DataPoint[_numPoints];
341 DataPoint point = null;
342 for (int i=0; i<_numPoints; i++)
344 point = _dataPoints[i];
345 if (point.isWaypoint())
347 waypoints[numWaypoints] = point;
349 wayAfterNon |= (numNonWaypoints > 0);
353 nonWaypoints[numNonWaypoints] = point;
355 nonAfterWay |= (numWaypoints > 0);
358 // Exit if the data is already in the specified order
359 if (numWaypoints == 0 || numNonWaypoints == 0
360 || (inAtStart && !wayAfterNon && nonAfterWay)
361 || (!inAtStart && wayAfterNon && !nonAfterWay))
366 // Copy the arrays back into _dataPoints in the specified order
369 System.arraycopy(waypoints, 0, _dataPoints, 0, numWaypoints);
370 System.arraycopy(nonWaypoints, 0, _dataPoints, numWaypoints, numNonWaypoints);
374 System.arraycopy(nonWaypoints, 0, _dataPoints, 0, numNonWaypoints);
375 System.arraycopy(waypoints, 0, _dataPoints, numNonWaypoints, numWaypoints);
377 // needs to be scaled again
379 _broker.informSubscribers();
385 * Interleave all waypoints by each nearest track point
386 * @return true if successful, false if no change
388 public boolean interleaveWaypoints()
390 // Separate waypoints and find nearest track point
391 int numWaypoints = 0;
392 DataPoint[] waypoints = new DataPoint[_numPoints];
393 int[] pointIndices = new int[_numPoints];
394 DataPoint point = null;
396 for (i=0; i<_numPoints; i++)
398 point = _dataPoints[i];
399 if (point.isWaypoint())
401 waypoints[numWaypoints] = point;
402 pointIndices[numWaypoints] = getNearestPointIndex(
403 _xValues[i], _yValues[i], -1.0, true);
407 // Exit if data not mixed
408 if (numWaypoints == 0 || numWaypoints == _numPoints)
411 // Loop round points copying to correct order
412 DataPoint[] dataCopy = new DataPoint[_numPoints];
414 for (i=0; i<_numPoints; i++)
416 point = _dataPoints[i];
417 // if it's a track point, copy it
418 if (!point.isWaypoint())
420 dataCopy[copyIndex] = point;
423 // check for waypoints with this index
424 for (int j=0; j<numWaypoints; j++)
426 if (pointIndices[j] == i)
428 dataCopy[copyIndex] = waypoints[j];
433 // Copy data back to track
434 _dataPoints = dataCopy;
435 // needs to be scaled again to recalc x, y
437 _broker.informSubscribers();
441 // TODO: Need to rearrange photo points too?
444 * Interpolate extra points between two selected ones
445 * @param inStartIndex start index of interpolation
446 * @param inNumPoints num points to insert
447 * @return true if successful
449 public boolean interpolate(int inStartIndex, int inNumPoints)
452 if (inStartIndex < 0 || inStartIndex >= _numPoints || inNumPoints <= 0)
455 // get start and end points
456 DataPoint startPoint = getPoint(inStartIndex);
457 DataPoint endPoint = getPoint(inStartIndex + 1);
459 // Make array of points to insert
460 DataPoint[] insertedPoints = startPoint.interpolate(endPoint, inNumPoints);
462 // Insert points into track
463 return insertRange(insertedPoints, inStartIndex + 1);
468 * Append the specified points to the end of the track
469 * @param inPoints DataPoint objects to add
471 public void appendPoints(DataPoint[] inPoints)
473 // Insert points into track
474 if (inPoints != null && inPoints.length > 0)
476 insertRange(inPoints, _numPoints);
478 // needs to be scaled again to recalc x, y
480 _broker.informSubscribers();
484 //////// information methods /////////////
488 * Get the point at the given index
489 * @param inPointNum index number, starting at 0
490 * @return DataPoint object, or null if out of range
492 public DataPoint getPoint(int inPointNum)
494 if (inPointNum > -1 && inPointNum < getNumPoints())
496 return _dataPoints[inPointNum];
503 * @return altitude range of points as AltitudeRange object
505 public AltitudeRange getAltitudeRange()
507 if (!_scaled) scalePoints();
508 return _altitudeRange;
511 * @return the number of (valid) points in the track
513 public int getNumPoints()
519 * @return The range of x values as a DoubleRange object
521 public DoubleRange getXRange()
523 if (!_scaled) scalePoints();
528 * @return The range of y values as a DoubleRange object
530 public DoubleRange getYRange()
532 if (!_scaled) scalePoints();
537 * @param inPointNum point index, starting at 0
538 * @return scaled x value of specified point
540 public double getX(int inPointNum)
542 if (!_scaled) scalePoints();
543 return _xValues[inPointNum];
547 * @param inPointNum point index, starting at 0
548 * @return scaled y value of specified point
550 public double getY(int inPointNum)
552 if (!_scaled) scalePoints();
553 return _yValues[inPointNum];
557 * @return the master field list
559 public FieldList getFieldList()
561 return _masterFieldList;
566 * Checks if any data exists for the specified field
567 * @param inField Field to examine
568 * @return true if data exists for this field
570 public boolean hasData(Field inField)
572 return hasData(inField, 0, _numPoints-1);
577 * Checks if any data exists for the specified field in the specified range
578 * @param inField Field to examine
579 * @param inStart start of range to check
580 * @param inEnd end of range to check (inclusive)
581 * @return true if data exists for this field
583 public boolean hasData(Field inField, int inStart, int inEnd)
585 for (int i=inStart; i<=inEnd; i++)
587 if (_dataPoints[i].getFieldValue(inField) != null)
597 * @return true if track contains waypoints and trackpoints
599 public boolean hasMixedData()
601 if (!_scaled) scalePoints();
607 * Collect all the waypoints into the given List
608 * @param inList List to fill with waypoints
610 public void getWaypoints(List inList)
614 // loop over points and copy all waypoints into list
615 for (int i=0; i<=_numPoints-1; i++)
617 if (_dataPoints[i].isWaypoint())
619 inList.add(_dataPoints[i]);
623 // TODO: Make similar method to get list of photos
627 * Search for the given Point in the track and return the index
628 * @param inPoint Point to look for
629 * @return index of Point, if any or -1 if not found
631 public int getPointIndex(DataPoint inPoint)
635 // Loop over points in track
636 for (int i=0; i<=_numPoints-1; i++)
638 if (_dataPoints[i] == inPoint)
649 ///////// Internal processing methods ////////////////
653 * Scale all the points in the track to gain x and y values
656 private void scalePoints()
658 // Loop through all points in track, to see limits of lat, long and altitude
659 _longRange = new DoubleRange();
660 _latRange = new DoubleRange();
661 _altitudeRange = new AltitudeRange();
663 boolean hasWaypoint = false, hasTrackpoint = false;
664 for (p=0; p < getNumPoints(); p++)
666 DataPoint point = getPoint(p);
667 if (point != null && point.isValid())
669 _longRange.addValue(point.getLongitude().getDouble());
670 _latRange.addValue(point.getLatitude().getDouble());
671 if (point.getAltitude().isValid())
673 _altitudeRange.addValue(point.getAltitude());
675 if (point.isWaypoint())
678 hasTrackpoint = true;
681 _mixedData = hasWaypoint && hasTrackpoint;
683 // Use medians to centre at 0
684 double longMedian = (_longRange.getMaximum() + _longRange.getMinimum()) / 2.0;
685 double latMedian = (_latRange.getMaximum() + _latRange.getMinimum()) / 2.0;
686 double longFactor = Math.cos(latMedian / 180.0 * Math.PI); // Function of median latitude
688 // Loop over points and calculate scales
689 _xValues = new double[getNumPoints()];
690 _yValues = new double[getNumPoints()];
691 _xRange = new DoubleRange();
692 _yRange = new DoubleRange();
693 for (p=0; p < getNumPoints(); p++)
695 DataPoint point = getPoint(p);
698 _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor;
699 _xRange.addValue(_xValues[p]);
700 _yValues[p] = (point.getLatitude().getDouble() - latMedian);
701 _yRange.addValue(_yValues[p]);
709 * Find the nearest point to the specified x and y coordinates
710 * or -1 if no point is within the specified max distance
711 * @param inX x coordinate
712 * @param inY y coordinate
713 * @param inMaxDist maximum distance from selected coordinates
714 * @param inJustTrackPoints true if waypoints should be ignored
715 * @return index of nearest point or -1 if not found
717 public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
719 int nearestPoint = 0;
720 double nearestDist = -1.0;
722 for (int i=0; i < getNumPoints(); i++)
724 if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
726 currDist = Math.abs(_xValues[i] - inX) + Math.abs(_yValues[i] - inY);
727 if (currDist < nearestDist || nearestDist < 0.0)
730 nearestDist = currDist;
734 // Check whether it's within required distance
735 if (nearestDist > inMaxDist && inMaxDist > 0.0)
743 ////////////////// Cloning and replacing ///////////////////
746 * Clone the array of DataPoints
747 * @return shallow copy of DataPoint objects
749 public DataPoint[] cloneContents()
751 DataPoint[] clone = new DataPoint[getNumPoints()];
752 System.arraycopy(_dataPoints, 0, clone, 0, getNumPoints());
758 * Clone the specified range of data points
759 * @param inStart start index (inclusive)
760 * @param inEnd end index (inclusive)
761 * @return shallow copy of DataPoint objects
763 public DataPoint[] cloneRange(int inStart, int inEnd)
766 if (inEnd >= 0 && inEnd >= inStart)
768 numSelected = inEnd - inStart + 1;
770 DataPoint[] result = new DataPoint[numSelected>0?numSelected:0];
773 System.arraycopy(_dataPoints, inStart, result, 0, numSelected);
780 * Re-insert the specified point at the given index
781 * @param inPoint point to insert
782 * @param inIndex index at which to insert the point
783 * @return true if it worked, false otherwise
785 public boolean insertPoint(DataPoint inPoint, int inIndex)
787 if (inIndex > _numPoints || inPoint == null)
791 // Make new array to copy points over to
792 DataPoint[] newPointArray = new DataPoint[_numPoints + 1];
795 System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
797 newPointArray[inIndex] = inPoint;
798 if (inIndex < _numPoints)
800 System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+1, _numPoints - inIndex);
802 // Change over to new array
803 _dataPoints = newPointArray;
805 // needs to be scaled again
807 _broker.informSubscribers();
813 * Re-insert the specified point range at the given index
814 * @param inPoints point array to insert
815 * @param inIndex index at which to insert the points
816 * @return true if it worked, false otherwise
818 public boolean insertRange(DataPoint[] inPoints, int inIndex)
820 if (inIndex > _numPoints || inPoints == null)
824 // Make new array to copy points over to
825 DataPoint[] newPointArray = new DataPoint[_numPoints + inPoints.length];
828 System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
830 System.arraycopy(inPoints, 0, newPointArray, inIndex, inPoints.length);
831 if (inIndex < _numPoints)
833 System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+inPoints.length, _numPoints - inIndex);
835 // Change over to new array
836 _dataPoints = newPointArray;
837 _numPoints += inPoints.length;
838 // needs to be scaled again
840 _broker.informSubscribers();
846 * Replace the track contents with the given point array
847 * @param inContents array of DataPoint objects
849 public boolean replaceContents(DataPoint[] inContents)
851 // master field array stays the same
852 // (would need to store field array too if we wanted to redo a load)
853 // replace data array
854 _dataPoints = inContents;
855 _numPoints = _dataPoints.length;
857 _broker.informSubscribers();
863 * Edit the specified point
864 * @param inPoint point to edit
865 * @param inEditList list of edits to make
866 * @return true if successful
868 public boolean editPoint(DataPoint inPoint, FieldEditList inEditList)
870 if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
872 // go through edits one by one
873 int numEdits = inEditList.getNumEdits();
874 for (int i=0; i<numEdits; i++)
876 FieldEdit edit = inEditList.getEdit(i);
877 inPoint.setFieldValue(edit.getField(), edit.getValue());
879 // possibly needs to be scaled again
882 _broker.informSubscribers();