]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/Track.java
f6ad709b05e3bde338749607e3526426e85b3a9a
[GpsPrune.git] / tim / prune / data / Track.java
1 package tim.prune.data;
2
3 import java.util.List;
4
5 import tim.prune.UpdateMessageBroker;
6 import tim.prune.edit.FieldEdit;
7 import tim.prune.edit.FieldEditList;
8
9
10 /**
11  * Class to hold all track information,
12  * including track points and waypoints
13  */
14 public class Track
15 {
16         // Broker object
17         UpdateMessageBroker _broker = null;
18         // Data points
19         private DataPoint[] _dataPoints = null;
20         // Scaled x, y values
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;
26         // Master field list
27         private FieldList _masterFieldList = null;
28         // variable ranges
29         private AltitudeRange _altitudeRange = null;
30         private DoubleRange _latRange = null, _longRange = null;
31         private DoubleRange _xRange = null, _yRange = null;
32
33
34         /**
35          * Constructor for empty track
36          * @param inBroker message broker object
37          */
38         public Track(UpdateMessageBroker inBroker)
39         {
40                 _broker = inBroker;
41                 // create field list
42                 _masterFieldList = new FieldList(null);
43                 // make empty DataPoint array
44                 _dataPoints = new DataPoint[0];
45                 _numPoints = 0;
46                 // needs to be scaled
47                 _scaled = false;
48         }
49
50
51         /**
52          * Load method, for initialising and reinitialising data
53          * @param inFieldArray array of Field objects describing fields
54          * @param inPointArray 2d object array containing data
55          * @param inAltFormat altitude format
56          */
57         public void load(Field[] inFieldArray, Object[][] inPointArray, int inAltFormat)
58         {
59                 if (inFieldArray == null || inPointArray == null)
60                 {
61                         _numPoints = 0;
62                         return;
63                 }
64                 // copy field list
65                 _masterFieldList = new FieldList(inFieldArray);
66                 // make DataPoint object from each point in inPointList
67                 _dataPoints = new DataPoint[inPointArray.length];
68                 String[] dataArray = null;
69                 int pointIndex = 0;
70                 for (int p=0; p < inPointArray.length; p++)
71                 {
72                         dataArray = (String[]) inPointArray[p];
73                         // Convert to DataPoint objects
74                         DataPoint point = new DataPoint(dataArray, _masterFieldList, inAltFormat);
75                         if (point.isValid())
76                         {
77                                 _dataPoints[pointIndex] = point;
78                                 pointIndex++;
79                         }
80                 }
81                 _numPoints = pointIndex;
82                 // needs to be scaled
83                 _scaled = false;
84         }
85
86
87         ////////////////// Modification methods //////////////////////
88
89
90         /**
91          * Combine this Track with new data
92          * @param inOtherTrack
93          */
94         public void combine(Track inOtherTrack)
95         {
96                 // merge field list
97                 _masterFieldList = _masterFieldList.merge(inOtherTrack._masterFieldList);
98                 // expand data array and add other track's data points
99                 int totalPoints = getNumPoints() + inOtherTrack.getNumPoints();
100                 DataPoint[] mergedPoints = new DataPoint[totalPoints];
101                 System.arraycopy(_dataPoints, 0, mergedPoints, 0, getNumPoints());
102                 System.arraycopy(inOtherTrack._dataPoints, 0, mergedPoints, getNumPoints(), inOtherTrack.getNumPoints());
103                 _dataPoints = mergedPoints;
104                 // combine point count
105                 _numPoints = totalPoints;
106                 // needs to be scaled again
107                 _scaled = false;
108                 // inform listeners
109                 _broker.informSubscribers();
110         }
111
112
113         /**
114          * Crop the track to the given size - subsequent points are not (yet) deleted
115          * @param inNewSize new number of points in track
116          */
117         public void cropTo(int inNewSize)
118         {
119                 if (inNewSize >= 0 && inNewSize < getNumPoints())
120                 {
121                         _numPoints = inNewSize;
122                         // needs to be scaled again
123                         _scaled = false;
124                         _broker.informSubscribers();
125                 }
126         }
127
128
129         /**
130          * Compress the track to the given resolution
131          * @param inResolution resolution
132          * @return number of points deleted
133          */
134         public int compress(int inResolution)
135         {
136                 // (maybe should be separate thread?)
137                 // (maybe should be in separate class?)
138                 // (maybe should be based on subtended angles instead of distances?)
139
140                 if (inResolution <= 0) return 0;
141                 int numCopied = 0;
142                 // Establish range of track and minimum range between points
143                 scalePoints();
144                 double wholeScale = _xRange.getMaximum() - _xRange.getMinimum();
145                 double yscale = _yRange.getMaximum() - _yRange.getMinimum();
146                 if (yscale > wholeScale) wholeScale = yscale;
147                 double minDist = wholeScale / inResolution;
148
149                 // Copy selected points
150                 DataPoint[] newPointArray = new DataPoint[_numPoints];
151                 int[] pointIndices = new int[_numPoints];
152                 for (int i=0; i<_numPoints; i++)
153                 {
154                         boolean keepPoint = true;
155                         // Don't delete waypoints or photo points
156                         if (!_dataPoints[i].isWaypoint() && _dataPoints[i].getPhoto() == null)
157                         {
158                                 // go through newPointArray to check for range
159                                 for (int j=0; j<numCopied && keepPoint; j++)
160                                 {
161                                         // calculate distance between point j and current point
162                                         double pointDist = Math.abs(_xValues[i] - _xValues[pointIndices[j]])
163                                          + Math.abs(_yValues[i] - _yValues[pointIndices[j]]);
164                                         if (pointDist < minDist)
165                                                 keepPoint = false;
166                                 }
167                         }
168                         if (keepPoint)
169                         {
170                                 newPointArray[numCopied] = _dataPoints[i];
171                                 pointIndices[numCopied] = i;
172                                 numCopied++;
173                         }
174                 }
175
176                 // Copy array references
177                 int numDeleted = _numPoints - numCopied;
178                 if (numDeleted > 0)
179                 {
180                         _dataPoints = new DataPoint[numCopied];
181                         System.arraycopy(newPointArray, 0, _dataPoints, 0, numCopied);
182                         _numPoints = _dataPoints.length;
183                         _scaled = false;
184                 }
185                 return numDeleted;
186         }
187
188
189         /**
190          * Halve the track by deleting alternate points
191          * @return number of points deleted
192          */
193         public int halve()
194         {
195                 if (_numPoints < 100) return 0;
196                 int newSize = _numPoints / 2;
197                 int numDeleted = _numPoints - newSize;
198                 DataPoint[] newPointArray = new DataPoint[newSize];
199                 // Delete alternate points
200                 for (int i=0; i<newSize; i++)
201                         newPointArray[i] = _dataPoints[i*2];
202                 // Copy array references
203                 _dataPoints = newPointArray;
204                 _numPoints = _dataPoints.length;
205                 _scaled = false;
206                 _broker.informSubscribers();
207                 return numDeleted;
208         }
209
210
211         /**
212          * Delete the specified point
213          * @param inIndex point index
214          * @return true if successful
215          */
216         public boolean deletePoint(int inIndex)
217         {
218                 boolean answer = deleteRange(inIndex, inIndex);
219                 return answer;
220         }
221
222
223         /**
224          * Delete the specified range of points from the Track
225          * @param inStart start of range (inclusive)
226          * @param inEnd end of range (inclusive)
227          * @return true if successful
228          */
229         public boolean deleteRange(int inStart, int inEnd)
230         {
231                 if (inStart < 0 || inEnd < 0 || inEnd < inStart)
232                 {
233                         // no valid range selected so can't delete
234                         return false;
235                 }
236                 // valid range, let's delete it
237                 int numToDelete = inEnd - inStart + 1;
238                 DataPoint[] newPointArray = new DataPoint[_numPoints - numToDelete];
239                 // Copy points before the selected range
240                 if (inStart > 0)
241                 {
242                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inStart);
243                 }
244                 // Copy points after the deleted one(s)
245                 if (inEnd < (_numPoints - 1))
246                 {
247                         System.arraycopy(_dataPoints, inEnd + 1, newPointArray, inStart,
248                                 _numPoints - inEnd - 1);
249                 }
250                 // Copy points over original array
251                 _dataPoints = newPointArray;
252                 _numPoints -= numToDelete;
253                 // needs to be scaled again
254                 _scaled = false;
255                 return true;
256         }
257
258
259         /**
260          * Delete all the duplicate points in the track
261          * @return number of points deleted
262          */
263         public int deleteDuplicates()
264         {
265                 // loop through Track counting duplicates first
266                 boolean[] dupes = new boolean[_numPoints];
267                 int numDupes = 0;
268                 int i, j;
269                 for (i=1; i<_numPoints; i++)
270                 {
271                         DataPoint p1 = _dataPoints[i];
272                         // Loop through all points before this one
273                         for (j=0; j<i && !dupes[i]; j++)
274                         {
275                                 DataPoint p2 = _dataPoints[j];
276                                 if (p1.isDuplicate(p2))
277                                 {
278                                         dupes[i] = true;
279                                         numDupes++;
280                                 }
281                         }
282                 }
283                 if (numDupes > 0)
284                 {
285                         // Make new resized array and copy DataPoints over
286                         DataPoint[] newPointArray = new DataPoint[_numPoints - numDupes];
287                         j = 0;
288                         for (i=0; i<_numPoints; i++)
289                         {
290                                 if (!dupes[i])
291                                 {
292                                         newPointArray[j] = _dataPoints[i];
293                                         j++;
294                                 }
295                         }
296                         // Copy array references
297                         _dataPoints = newPointArray;
298                         _numPoints = _dataPoints.length;
299                         _scaled = false;
300                 }
301                 return numDupes;
302         }
303
304
305         /**
306          * Reverse the specified range of points
307          * @param inStart start index
308          * @param inEnd end index
309          * @return true if successful, false otherwise
310          */
311         public boolean reverseRange(int inStart, int inEnd)
312         {
313                 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints)
314                 {
315                         return false;
316                 }
317                 // calculate how many point swaps are required
318                 int numPointsToReverse = (inEnd - inStart + 1) / 2;
319                 DataPoint p = null;
320                 for (int i=0; i<numPointsToReverse; i++)
321                 {
322                         // swap pairs of points
323                         p = _dataPoints[inStart + i];
324                         _dataPoints[inStart + i] = _dataPoints[inEnd - i];
325                         _dataPoints[inEnd - i] = p;
326                 }
327                 // needs to be scaled again
328                 _scaled = false;
329                 _broker.informSubscribers();
330                 return true;
331         }
332
333
334         /**
335          * Collect all waypoints to the start or end of the track
336          * @param inAtStart true to collect at start, false for end
337          * @return true if successful, false if no change
338          */
339         public boolean collectWaypoints(boolean inAtStart)
340         {
341                 // Check for mixed data, numbers of waypoints & nons
342                 int numWaypoints = 0, numNonWaypoints = 0;
343                 boolean wayAfterNon = false, nonAfterWay = false;
344                 DataPoint[] waypoints = new DataPoint[_numPoints];
345                 DataPoint[] nonWaypoints = new DataPoint[_numPoints];
346                 DataPoint point = null;
347                 for (int i=0; i<_numPoints; i++)
348                 {
349                         point = _dataPoints[i];
350                         if (point.isWaypoint())
351                         {
352                                 waypoints[numWaypoints] = point;
353                                 numWaypoints++;
354                                 wayAfterNon |= (numNonWaypoints > 0);
355                         }
356                         else
357                         {
358                                 nonWaypoints[numNonWaypoints] = point;
359                                 numNonWaypoints++;
360                                 nonAfterWay |= (numWaypoints > 0);
361                         }
362                 }
363                 // Exit if the data is already in the specified order
364                 if (numWaypoints == 0 || numNonWaypoints == 0
365                         || (inAtStart && !wayAfterNon && nonAfterWay)
366                         || (!inAtStart && wayAfterNon && !nonAfterWay))
367                 {
368                         return false;
369                 }
370
371                 // Copy the arrays back into _dataPoints in the specified order
372                 if (inAtStart)
373                 {
374                         System.arraycopy(waypoints, 0, _dataPoints, 0, numWaypoints);
375                         System.arraycopy(nonWaypoints, 0, _dataPoints, numWaypoints, numNonWaypoints);
376                 }
377                 else
378                 {
379                         System.arraycopy(nonWaypoints, 0, _dataPoints, 0, numNonWaypoints);
380                         System.arraycopy(waypoints, 0, _dataPoints, numNonWaypoints, numWaypoints);
381                 }
382                 // needs to be scaled again
383                 _scaled = false;
384                 _broker.informSubscribers();
385                 return true;
386         }
387
388
389         /**
390          * Interleave all waypoints by each nearest track point
391          * @return true if successful, false if no change
392          */
393         public boolean interleaveWaypoints()
394         {
395                 // Separate waypoints and find nearest track point
396                 int numWaypoints = 0;
397                 DataPoint[] waypoints = new DataPoint[_numPoints];
398                 int[] pointIndices = new int[_numPoints];
399                 DataPoint point = null;
400                 int i = 0;
401                 for (i=0; i<_numPoints; i++)
402                 {
403                         point = _dataPoints[i];
404                         if (point.isWaypoint())
405                         {
406                                 waypoints[numWaypoints] = point;
407                                 pointIndices[numWaypoints] = getNearestPointIndex(
408                                         _xValues[i], _yValues[i], -1.0, true);
409                                 numWaypoints++;
410                         }
411                 }
412                 // Exit if data not mixed
413                 if (numWaypoints == 0 || numWaypoints == _numPoints)
414                         return false;
415
416                 // Loop round points copying to correct order
417                 DataPoint[] dataCopy = new DataPoint[_numPoints];
418                 int copyIndex = 0;
419                 for (i=0; i<_numPoints; i++)
420                 {
421                         point = _dataPoints[i];
422                         // if it's a track point, copy it
423                         if (!point.isWaypoint())
424                         {
425                                 dataCopy[copyIndex] = point;
426                                 copyIndex++;
427                         }
428                         // check for waypoints with this index
429                         for (int j=0; j<numWaypoints; j++)
430                         {
431                                 if (pointIndices[j] == i)
432                                 {
433                                         dataCopy[copyIndex] = waypoints[j];
434                                         copyIndex++;
435                                 }
436                         }
437                 }
438                 // Copy data back to track
439                 _dataPoints = dataCopy;
440                 // needs to be scaled again to recalc x, y
441                 _scaled = false;
442                 _broker.informSubscribers();
443                 return true;
444         }
445
446
447         /**
448          * Interpolate extra points between two selected ones
449          * @param inStartIndex start index of interpolation
450          * @param inNumPoints num points to insert
451          * @return true if successful
452          */
453         public boolean interpolate(int inStartIndex, int inNumPoints)
454         {
455                 // check parameters
456                 if (inStartIndex < 0 || inStartIndex >= _numPoints || inNumPoints <= 0)
457                         return false;
458
459                 // get start and end points
460                 DataPoint startPoint = getPoint(inStartIndex);
461                 DataPoint endPoint = getPoint(inStartIndex + 1);
462
463                 // Make array of points to insert
464                 DataPoint[] insertedPoints = startPoint.interpolate(endPoint, inNumPoints);
465
466                 // Insert points into track
467                 return insertRange(insertedPoints, inStartIndex + 1);
468         }
469
470
471         /**
472          * Append the specified points to the end of the track
473          * @param inPoints DataPoint objects to add
474          */
475         public void appendPoints(DataPoint[] inPoints)
476         {
477                 // Insert points into track
478                 if (inPoints != null && inPoints.length > 0)
479                 {
480                         insertRange(inPoints, _numPoints);
481                 }
482                 // needs to be scaled again to recalc x, y
483                 _scaled = false;
484                 _broker.informSubscribers();
485         }
486
487
488         //////// information methods /////////////
489
490
491         /**
492          * Get the point at the given index
493          * @param inPointNum index number, starting at 0
494          * @return DataPoint object, or null if out of range
495          */
496         public DataPoint getPoint(int inPointNum)
497         {
498                 if (inPointNum > -1 && inPointNum < getNumPoints())
499                 {
500                         return _dataPoints[inPointNum];
501                 }
502                 return null;
503         }
504
505
506         /**
507          * @return altitude range of points as AltitudeRange object
508          */
509         public AltitudeRange getAltitudeRange()
510         {
511                 if (!_scaled) scalePoints();
512                 return _altitudeRange;
513         }
514         /**
515          * @return the number of (valid) points in the track
516          */
517         public int getNumPoints()
518         {
519                 return _numPoints;
520         }
521
522         /**
523          * @return The range of x values as a DoubleRange object
524          */
525         public DoubleRange getXRange()
526         {
527                 if (!_scaled) scalePoints();
528                 return _xRange;
529         }
530
531         /**
532          * @return The range of y values as a DoubleRange object
533          */
534         public DoubleRange getYRange()
535         {
536                 if (!_scaled) scalePoints();
537                 return _yRange;
538         }
539
540         /**
541          * @return The range of lat values as a DoubleRange object
542          */
543         public DoubleRange getLatRange()
544         {
545                 if (!_scaled) scalePoints();
546                 return _latRange;
547         }
548         /**
549          * @return The range of lon values as a DoubleRange object
550          */
551         public DoubleRange getLonRange()
552         {
553                 if (!_scaled) scalePoints();
554                 return _longRange;
555         }
556
557         /**
558          * @param inPointNum point index, starting at 0
559          * @return scaled x value of specified point
560          */
561         public double getX(int inPointNum)
562         {
563                 if (!_scaled) scalePoints();
564                 return _xValues[inPointNum];
565         }
566
567         /**
568          * @param inPointNum point index, starting at 0
569          * @return scaled y value of specified point
570          */
571         public double getY(int inPointNum)
572         {
573                 if (!_scaled) scalePoints();
574                 return _yValues[inPointNum];
575         }
576
577         /**
578          * @return the master field list
579          */
580         public FieldList getFieldList()
581         {
582                 return _masterFieldList;
583         }
584
585
586         /**
587          * Checks if any data exists for the specified field
588          * @param inField Field to examine
589          * @return true if data exists for this field
590          */
591         public boolean hasData(Field inField)
592         {
593                 return hasData(inField, 0, _numPoints-1);
594         }
595
596
597         /**
598          * Checks if any data exists for the specified field in the specified range
599          * @param inField Field to examine
600          * @param inStart start of range to check
601          * @param inEnd end of range to check (inclusive)
602          * @return true if data exists for this field
603          */
604         public boolean hasData(Field inField, int inStart, int inEnd)
605         {
606                 for (int i=inStart; i<=inEnd; i++)
607                 {
608                         if (_dataPoints[i].getFieldValue(inField) != null)
609                         {
610                                 return true;
611                         }
612                 }
613                 return false;
614         }
615
616
617         /**
618          * @return true if track contains waypoints and trackpoints
619          */
620         public boolean hasMixedData()
621         {
622                 if (!_scaled) scalePoints();
623                 return _mixedData;
624         }
625
626
627         /**
628          * Collect all the waypoints into the given List
629          * @param inList List to fill with waypoints
630          */
631         public void getWaypoints(List inList)
632         {
633                 // clear list
634                 inList.clear();
635                 // loop over points and copy all waypoints into list
636                 for (int i=0; i<=_numPoints-1; i++)
637                 {
638                         if (_dataPoints[i] != null && _dataPoints[i].isWaypoint())
639                         {
640                                 inList.add(_dataPoints[i]);
641                         }
642                 }
643         }
644
645
646         /**
647          * Search for the given Point in the track and return the index
648          * @param inPoint Point to look for
649          * @return index of Point, if any or -1 if not found
650          */
651         public int getPointIndex(DataPoint inPoint)
652         {
653                 if (inPoint != null)
654                 {
655                         // Loop over points in track
656                         for (int i=0; i<=_numPoints-1; i++)
657                         {
658                                 if (_dataPoints[i] == inPoint)
659                                 {
660                                         return i;
661                                 }
662                         }
663                 }
664                 // not found
665                 return -1;
666         }
667
668
669         ///////// Internal processing methods ////////////////
670
671
672         /**
673          * Scale all the points in the track to gain x and y values
674          * ready for plotting
675          */
676         private void scalePoints()
677         {
678                 // Loop through all points in track, to see limits of lat, long and altitude
679                 _longRange = new DoubleRange();
680                 _latRange = new DoubleRange();
681                 _altitudeRange = new AltitudeRange();
682                 int p;
683                 boolean hasWaypoint = false, hasTrackpoint = false;
684                 for (p=0; p < getNumPoints(); p++)
685                 {
686                         DataPoint point = getPoint(p);
687                         if (point != null && point.isValid())
688                         {
689                                 _longRange.addValue(point.getLongitude().getDouble());
690                                 _latRange.addValue(point.getLatitude().getDouble());
691                                 if (point.getAltitude().isValid())
692                                 {
693                                         _altitudeRange.addValue(point.getAltitude());
694                                 }
695                                 if (point.isWaypoint())
696                                         hasWaypoint = true;
697                                 else
698                                         hasTrackpoint = true;
699                         }
700                 }
701                 _mixedData = hasWaypoint && hasTrackpoint;
702
703                 // Use medians to centre at 0
704                 double longMedian = (_longRange.getMaximum() + _longRange.getMinimum()) / 2.0;
705                 double latMedian = (_latRange.getMaximum() + _latRange.getMinimum()) / 2.0;
706                 double longFactor = Math.cos(latMedian / 180.0 * Math.PI); // Function of median latitude
707
708                 // Loop over points and calculate scales
709                 _xValues = new double[getNumPoints()];
710                 _yValues = new double[getNumPoints()];
711                 _xRange = new DoubleRange();
712                 _yRange = new DoubleRange();
713                 for (p=0; p < getNumPoints(); p++)
714                 {
715                         DataPoint point = getPoint(p);
716                         if (point != null)
717                         {
718                                 _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor;
719                                 _xRange.addValue(_xValues[p]);
720                                 _yValues[p] = (point.getLatitude().getDouble() - latMedian);
721                                 _yRange.addValue(_yValues[p]);
722                         }
723                 }
724                 _scaled = true;
725         }
726
727
728         /**
729          * Find the nearest point to the specified x and y coordinates
730          * or -1 if no point is within the specified max distance
731          * @param inX x coordinate
732          * @param inY y coordinate
733          * @param inMaxDist maximum distance from selected coordinates
734          * @param inJustTrackPoints true if waypoints should be ignored
735          * @return index of nearest point or -1 if not found
736          */
737         public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
738         {
739                 int nearestPoint = 0;
740                 double nearestDist = -1.0;
741                 double currDist;
742                 for (int i=0; i < getNumPoints(); i++)
743                 {
744                         if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
745                         {
746                                 currDist = Math.abs(_xValues[i] - inX) + Math.abs(_yValues[i] - inY);
747                                 if (currDist < nearestDist || nearestDist < 0.0)
748                                 {
749                                         nearestPoint = i;
750                                         nearestDist = currDist;
751                                 }
752                         }
753                 }
754                 // Check whether it's within required distance
755                 if (nearestDist > inMaxDist && inMaxDist > 0.0)
756                 {
757                         return -1;
758                 }
759                 return nearestPoint;
760         }
761
762
763         ////////////////// Cloning and replacing ///////////////////
764
765         /**
766          * Clone the array of DataPoints
767          * @return shallow copy of DataPoint objects
768          */
769         public DataPoint[] cloneContents()
770         {
771                 DataPoint[] clone = new DataPoint[getNumPoints()];
772                 System.arraycopy(_dataPoints, 0, clone, 0, getNumPoints());
773                 return clone;
774         }
775
776
777         /**
778          * Clone the specified range of data points
779          * @param inStart start index (inclusive)
780          * @param inEnd end index (inclusive)
781          * @return shallow copy of DataPoint objects
782          */
783         public DataPoint[] cloneRange(int inStart, int inEnd)
784         {
785                 int numSelected = 0;
786                 if (inEnd >= 0 && inEnd >= inStart)
787                 {
788                         numSelected = inEnd - inStart + 1;
789                 }
790                 DataPoint[] result = new DataPoint[numSelected>0?numSelected:0];
791                 if (numSelected > 0)
792                 {
793                         System.arraycopy(_dataPoints, inStart, result, 0, numSelected);
794                 }
795                 return result;
796         }
797
798
799         /**
800          * Re-insert the specified point at the given index
801          * @param inPoint point to insert
802          * @param inIndex index at which to insert the point
803          * @return true if it worked, false otherwise
804          */
805         public boolean insertPoint(DataPoint inPoint, int inIndex)
806         {
807                 if (inIndex > _numPoints || inPoint == null)
808                 {
809                         return false;
810                 }
811                 // Make new array to copy points over to
812                 DataPoint[] newPointArray = new DataPoint[_numPoints + 1];
813                 if (inIndex > 0)
814                 {
815                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
816                 }
817                 newPointArray[inIndex] = inPoint;
818                 if (inIndex < _numPoints)
819                 {
820                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+1, _numPoints - inIndex);
821                 }
822                 // Change over to new array
823                 _dataPoints = newPointArray;
824                 _numPoints++;
825                 // needs to be scaled again
826                 _scaled = false;
827                 _broker.informSubscribers();
828                 return true;
829         }
830
831
832         /**
833          * Re-insert the specified point range at the given index
834          * @param inPoints point array to insert
835          * @param inIndex index at which to insert the points
836          * @return true if it worked, false otherwise
837          */
838         public boolean insertRange(DataPoint[] inPoints, int inIndex)
839         {
840                 if (inIndex > _numPoints || inPoints == null)
841                 {
842                         return false;
843                 }
844                 // Make new array to copy points over to
845                 DataPoint[] newPointArray = new DataPoint[_numPoints + inPoints.length];
846                 if (inIndex > 0)
847                 {
848                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
849                 }
850                 System.arraycopy(inPoints, 0, newPointArray, inIndex, inPoints.length);
851                 if (inIndex < _numPoints)
852                 {
853                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+inPoints.length, _numPoints - inIndex);
854                 }
855                 // Change over to new array
856                 _dataPoints = newPointArray;
857                 _numPoints += inPoints.length;
858                 // needs to be scaled again
859                 _scaled = false;
860                 _broker.informSubscribers();
861                 return true;
862         }
863
864
865         /**
866          * Replace the track contents with the given point array
867          * @param inContents array of DataPoint objects
868          * @return true on success
869          */
870         public boolean replaceContents(DataPoint[] inContents)
871         {
872                 // master field array stays the same
873                 // (would need to store field array too if we wanted to redo a load)
874                 // replace data array
875                 _dataPoints = inContents;
876                 _numPoints = _dataPoints.length;
877                 _scaled = false;
878                 _broker.informSubscribers();
879                 return true;
880         }
881
882
883         /**
884          * Edit the specified point
885          * @param inPoint point to edit
886          * @param inEditList list of edits to make
887          * @return true if successful
888          */
889         public boolean editPoint(DataPoint inPoint, FieldEditList inEditList)
890         {
891                 if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
892                 {
893                         // remember if coordinates have changed
894                         boolean coordsChanged = false;
895                         // go through edits one by one
896                         int numEdits = inEditList.getNumEdits();
897                         for (int i=0; i<numEdits; i++)
898                         {
899                                 FieldEdit edit = inEditList.getEdit(i);
900                                 inPoint.setFieldValue(edit.getField(), edit.getValue());
901                                 // check coordinates
902                                 coordsChanged |= (edit.getField().equals(Field.LATITUDE)
903                                         || edit.getField().equals(Field.LONGITUDE) || edit.getField().equals(Field.ALTITUDE));
904                         }
905                         // set photo status if coordinates have changed
906                         if (inPoint.getPhoto() != null && coordsChanged)
907                         {
908                                 inPoint.getPhoto().setCurrentStatus(PhotoStatus.CONNECTED);
909                         }
910                         // point possibly needs to be scaled again
911                         _scaled = false;
912                         // trigger listeners
913                         _broker.informSubscribers();
914                         return true;
915                 }
916                 return false;
917         }
918 }