]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/Track.java
Version 4, January 2008
[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          * @param inPointNum point index, starting at 0
542          * @return scaled x value of specified point
543          */
544         public double getX(int inPointNum)
545         {
546                 if (!_scaled) scalePoints();
547                 return _xValues[inPointNum];
548         }
549
550         /**
551          * @param inPointNum point index, starting at 0
552          * @return scaled y value of specified point
553          */
554         public double getY(int inPointNum)
555         {
556                 if (!_scaled) scalePoints();
557                 return _yValues[inPointNum];
558         }
559
560         /**
561          * @return the master field list
562          */
563         public FieldList getFieldList()
564         {
565                 return _masterFieldList;
566         }
567
568
569         /**
570          * Checks if any data exists for the specified field
571          * @param inField Field to examine
572          * @return true if data exists for this field
573          */
574         public boolean hasData(Field inField)
575         {
576                 return hasData(inField, 0, _numPoints-1);
577         }
578
579
580         /**
581          * Checks if any data exists for the specified field in the specified range
582          * @param inField Field to examine
583          * @param inStart start of range to check
584          * @param inEnd end of range to check (inclusive)
585          * @return true if data exists for this field
586          */
587         public boolean hasData(Field inField, int inStart, int inEnd)
588         {
589                 for (int i=inStart; i<=inEnd; i++)
590                 {
591                         if (_dataPoints[i].getFieldValue(inField) != null)
592                         {
593                                 return true;
594                         }
595                 }
596                 return false;
597         }
598
599
600         /**
601          * @return true if track contains waypoints and trackpoints
602          */
603         public boolean hasMixedData()
604         {
605                 if (!_scaled) scalePoints();
606                 return _mixedData;
607         }
608
609
610         /**
611          * Collect all the waypoints into the given List
612          * @param inList List to fill with waypoints
613          */
614         public void getWaypoints(List inList)
615         {
616                 // clear list
617                 inList.clear();
618                 // loop over points and copy all waypoints into list
619                 for (int i=0; i<=_numPoints-1; i++)
620                 {
621                         if (_dataPoints[i] != null && _dataPoints[i].isWaypoint())
622                         {
623                                 inList.add(_dataPoints[i]);
624                         }
625                 }
626         }
627
628
629         /**
630          * Search for the given Point in the track and return the index
631          * @param inPoint Point to look for
632          * @return index of Point, if any or -1 if not found
633          */
634         public int getPointIndex(DataPoint inPoint)
635         {
636                 if (inPoint != null)
637                 {
638                         // Loop over points in track
639                         for (int i=0; i<=_numPoints-1; i++)
640                         {
641                                 if (_dataPoints[i] == inPoint)
642                                 {
643                                         return i;
644                                 }
645                         }
646                 }
647                 // not found
648                 return -1;
649         }
650
651
652         ///////// Internal processing methods ////////////////
653
654
655         /**
656          * Scale all the points in the track to gain x and y values
657          * ready for plotting
658          */
659         private void scalePoints()
660         {
661                 // Loop through all points in track, to see limits of lat, long and altitude
662                 _longRange = new DoubleRange();
663                 _latRange = new DoubleRange();
664                 _altitudeRange = new AltitudeRange();
665                 int p;
666                 boolean hasWaypoint = false, hasTrackpoint = false;
667                 for (p=0; p < getNumPoints(); p++)
668                 {
669                         DataPoint point = getPoint(p);
670                         if (point != null && point.isValid())
671                         {
672                                 _longRange.addValue(point.getLongitude().getDouble());
673                                 _latRange.addValue(point.getLatitude().getDouble());
674                                 if (point.getAltitude().isValid())
675                                 {
676                                         _altitudeRange.addValue(point.getAltitude());
677                                 }
678                                 if (point.isWaypoint())
679                                         hasWaypoint = true;
680                                 else
681                                         hasTrackpoint = true;
682                         }
683                 }
684                 _mixedData = hasWaypoint && hasTrackpoint;
685
686                 // Use medians to centre at 0
687                 double longMedian = (_longRange.getMaximum() + _longRange.getMinimum()) / 2.0;
688                 double latMedian = (_latRange.getMaximum() + _latRange.getMinimum()) / 2.0;
689                 double longFactor = Math.cos(latMedian / 180.0 * Math.PI); // Function of median latitude
690
691                 // Loop over points and calculate scales
692                 _xValues = new double[getNumPoints()];
693                 _yValues = new double[getNumPoints()];
694                 _xRange = new DoubleRange();
695                 _yRange = new DoubleRange();
696                 for (p=0; p < getNumPoints(); p++)
697                 {
698                         DataPoint point = getPoint(p);
699                         if (point != null)
700                         {
701                                 _xValues[p] = (point.getLongitude().getDouble() - longMedian) * longFactor;
702                                 _xRange.addValue(_xValues[p]);
703                                 _yValues[p] = (point.getLatitude().getDouble() - latMedian);
704                                 _yRange.addValue(_yValues[p]);
705                         }
706                 }
707                 _scaled = true;
708         }
709
710
711         /**
712          * Find the nearest point to the specified x and y coordinates
713          * or -1 if no point is within the specified max distance
714          * @param inX x coordinate
715          * @param inY y coordinate
716          * @param inMaxDist maximum distance from selected coordinates
717          * @param inJustTrackPoints true if waypoints should be ignored
718          * @return index of nearest point or -1 if not found
719          */
720         public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
721         {
722                 int nearestPoint = 0;
723                 double nearestDist = -1.0;
724                 double currDist;
725                 for (int i=0; i < getNumPoints(); i++)
726                 {
727                         if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
728                         {
729                                 currDist = Math.abs(_xValues[i] - inX) + Math.abs(_yValues[i] - inY);
730                                 if (currDist < nearestDist || nearestDist < 0.0)
731                                 {
732                                         nearestPoint = i;
733                                         nearestDist = currDist;
734                                 }
735                         }
736                 }
737                 // Check whether it's within required distance
738                 if (nearestDist > inMaxDist && inMaxDist > 0.0)
739                 {
740                         return -1;
741                 }
742                 return nearestPoint;
743         }
744
745
746         ////////////////// Cloning and replacing ///////////////////
747
748         /**
749          * Clone the array of DataPoints
750          * @return shallow copy of DataPoint objects
751          */
752         public DataPoint[] cloneContents()
753         {
754                 DataPoint[] clone = new DataPoint[getNumPoints()];
755                 System.arraycopy(_dataPoints, 0, clone, 0, getNumPoints());
756                 return clone;
757         }
758
759
760         /**
761          * Clone the specified range of data points
762          * @param inStart start index (inclusive)
763          * @param inEnd end index (inclusive)
764          * @return shallow copy of DataPoint objects
765          */
766         public DataPoint[] cloneRange(int inStart, int inEnd)
767         {
768                 int numSelected = 0;
769                 if (inEnd >= 0 && inEnd >= inStart)
770                 {
771                         numSelected = inEnd - inStart + 1;
772                 }
773                 DataPoint[] result = new DataPoint[numSelected>0?numSelected:0];
774                 if (numSelected > 0)
775                 {
776                         System.arraycopy(_dataPoints, inStart, result, 0, numSelected);
777                 }
778                 return result;
779         }
780
781
782         /**
783          * Re-insert the specified point at the given index
784          * @param inPoint point to insert
785          * @param inIndex index at which to insert the point
786          * @return true if it worked, false otherwise
787          */
788         public boolean insertPoint(DataPoint inPoint, int inIndex)
789         {
790                 if (inIndex > _numPoints || inPoint == null)
791                 {
792                         return false;
793                 }
794                 // Make new array to copy points over to
795                 DataPoint[] newPointArray = new DataPoint[_numPoints + 1];
796                 if (inIndex > 0)
797                 {
798                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
799                 }
800                 newPointArray[inIndex] = inPoint;
801                 if (inIndex < _numPoints)
802                 {
803                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+1, _numPoints - inIndex);
804                 }
805                 // Change over to new array
806                 _dataPoints = newPointArray;
807                 _numPoints++;
808                 // needs to be scaled again
809                 _scaled = false;
810                 _broker.informSubscribers();
811                 return true;
812         }
813
814
815         /**
816          * Re-insert the specified point range at the given index
817          * @param inPoints point array to insert
818          * @param inIndex index at which to insert the points
819          * @return true if it worked, false otherwise
820          */
821         public boolean insertRange(DataPoint[] inPoints, int inIndex)
822         {
823                 if (inIndex > _numPoints || inPoints == null)
824                 {
825                         return false;
826                 }
827                 // Make new array to copy points over to
828                 DataPoint[] newPointArray = new DataPoint[_numPoints + inPoints.length];
829                 if (inIndex > 0)
830                 {
831                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
832                 }
833                 System.arraycopy(inPoints, 0, newPointArray, inIndex, inPoints.length);
834                 if (inIndex < _numPoints)
835                 {
836                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+inPoints.length, _numPoints - inIndex);
837                 }
838                 // Change over to new array
839                 _dataPoints = newPointArray;
840                 _numPoints += inPoints.length;
841                 // needs to be scaled again
842                 _scaled = false;
843                 _broker.informSubscribers();
844                 return true;
845         }
846
847
848         /**
849          * Replace the track contents with the given point array
850          * @param inContents array of DataPoint objects
851          * @return true on success
852          */
853         public boolean replaceContents(DataPoint[] inContents)
854         {
855                 // master field array stays the same
856                 // (would need to store field array too if we wanted to redo a load)
857                 // replace data array
858                 _dataPoints = inContents;
859                 _numPoints = _dataPoints.length;
860                 _scaled = false;
861                 _broker.informSubscribers();
862                 return true;
863         }
864
865
866         /**
867          * Edit the specified point
868          * @param inPoint point to edit
869          * @param inEditList list of edits to make
870          * @return true if successful
871          */
872         public boolean editPoint(DataPoint inPoint, FieldEditList inEditList)
873         {
874                 if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
875                 {
876                         // remember if coordinates have changed
877                         boolean coordsChanged = false;
878                         // go through edits one by one
879                         int numEdits = inEditList.getNumEdits();
880                         for (int i=0; i<numEdits; i++)
881                         {
882                                 FieldEdit edit = inEditList.getEdit(i);
883                                 inPoint.setFieldValue(edit.getField(), edit.getValue());
884                                 // check coordinates
885                                 coordsChanged |= (edit.getField().equals(Field.LATITUDE)
886                                         || edit.getField().equals(Field.LONGITUDE) || edit.getField().equals(Field.ALTITUDE));
887                         }
888                         // set photo status if coordinates have changed
889                         if (inPoint.getPhoto() != null && coordsChanged)
890                         {
891                                 inPoint.getPhoto().setCurrentStatus(PhotoStatus.CONNECTED);
892                         }
893                         // point possibly needs to be scaled again
894                         _scaled = false;
895                         // trigger listeners
896                         _broker.informSubscribers();
897                         return true;
898                 }
899                 return false;
900         }
901 }