]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/Track.java
e73fa736eb6731d1d7c86ee5d48dffc6bf35b0eb
[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 giving arrays of Fields and Objects
36          * @param inFieldArray field array
37          * @param inPointArray 2d array of field values
38          */
39         public Track(UpdateMessageBroker inBroker)
40         {
41                 _broker = inBroker;
42                 // create field list
43                 _masterFieldList = new FieldList(null);
44                 // make empty DataPoint array
45                 _dataPoints = new DataPoint[0];
46                 _numPoints = 0;
47                 // needs to be scaled
48                 _scaled = false;
49         }
50
51
52         /**
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
57          */
58         public void load(Field[] inFieldArray, Object[][] inPointArray, int inAltFormat)
59         {
60                 // copy field list
61                 _masterFieldList = new FieldList(inFieldArray);
62                 // make DataPoint object from each point in inPointList
63                 _dataPoints = new DataPoint[inPointArray.length];
64                 String[] dataArray = null;
65                 int pointIndex = 0;
66                 for (int p=0; p < inPointArray.length; p++)
67                 {
68                         dataArray = (String[]) inPointArray[p];
69                         // Convert to DataPoint objects
70                         DataPoint point = new DataPoint(dataArray, _masterFieldList, inAltFormat);
71                         if (point.isValid())
72                         {
73                                 _dataPoints[pointIndex] = point;
74                                 pointIndex++;
75                         }
76                 }
77                 _numPoints = pointIndex;
78                 // needs to be scaled
79                 _scaled = false;
80         }
81
82
83         ////////////////// Modification methods //////////////////////
84
85
86         /**
87          * Combine this Track with new data
88          * @param inOtherTrack
89          */
90         public void combine(Track inOtherTrack)
91         {
92                 // merge field list
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
103                 _scaled = false;
104                 // inform listeners
105                 _broker.informSubscribers();
106         }
107
108
109         /**
110          * Crop the track to the given size - subsequent points are not (yet) deleted
111          * @param inNewSize new number of points in track
112          */
113         public void cropTo(int inNewSize)
114         {
115                 if (inNewSize >= 0 && inNewSize < getNumPoints())
116                 {
117                         _numPoints = inNewSize;
118                         // needs to be scaled again
119                         _scaled = false;
120                         _broker.informSubscribers();
121                 }
122         }
123
124
125         /**
126          * Compress the track to the given resolution
127          * @param inResolution resolution
128          * @return number of points deleted
129          */
130         public int compress(int inResolution)
131         {
132                 // (maybe should be separate thread?)
133                 // (maybe should be in separate class?)
134                 // (maybe should be based on subtended angles instead of distances?)
135
136                 if (inResolution <= 0) return 0;
137                 int numCopied = 0;
138                 // Establish range of track and minimum range between points
139                 scalePoints();
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;
144
145                 // Copy selected points
146                 DataPoint[] newPointArray = new DataPoint[_numPoints];
147                 int[] pointIndices = new int[_numPoints];
148                 for (int i=0; i<_numPoints; i++)
149                 {
150                         boolean keepPoint = true;
151                         if (!_dataPoints[i].isWaypoint())
152                         {
153                                 // go through newPointArray to check for range
154                                 for (int j=0; j<numCopied && keepPoint; j++)
155                                 {
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)
160                                                 keepPoint = false;
161                                 }
162                         }
163                         if (keepPoint)
164                         {
165                                 newPointArray[numCopied] = _dataPoints[i];
166                                 pointIndices[numCopied] = i;
167                                 numCopied++;
168                         }
169                 }
170
171                 // Copy array references
172                 int numDeleted = _numPoints - numCopied;
173                 if (numDeleted > 0)
174                 {
175                         _dataPoints = new DataPoint[numCopied];
176                         System.arraycopy(newPointArray, 0, _dataPoints, 0, numCopied);
177                         _numPoints = _dataPoints.length;
178                         _scaled = false;
179                         _broker.informSubscribers();
180                 }
181                 return numDeleted;
182         }
183
184
185         /**
186          * Halve the track by deleting alternate points
187          * @return number of points deleted
188          */
189         public int halve()
190         {
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;
201                 _scaled = false;
202                 _broker.informSubscribers();
203                 return numDeleted;
204         }
205
206
207         /**
208          * Delete the specified point
209          * @return true if successful
210          */
211         public boolean deletePoint(int inIndex)
212         {
213                 boolean answer = deleteRange(inIndex, inIndex);
214                 return answer;
215         }
216
217
218         /**
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
223          */
224         public boolean deleteRange(int inStart, int inEnd)
225         {
226                 // TODO: Check for deleting photos?
227                 if (inStart < 0 || inEnd < 0 || inEnd < inStart)
228                 {
229                         // no valid range selected so can't delete
230                         return false;
231                 }
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
236                 if (inStart > 0)
237                 {
238                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inStart);
239                 }
240                 // Copy points after the deleted one(s)
241                 if (inEnd < (_numPoints - 1))
242                 {
243                         System.arraycopy(_dataPoints, inEnd + 1, newPointArray, inStart,
244                                 _numPoints - inEnd - 1);
245                 }
246                 // Copy points over original array (careful!)
247                 _dataPoints = newPointArray;
248                 _numPoints -= numToDelete;
249                 // needs to be scaled again
250                 _scaled = false;
251                 return true;
252         }
253
254
255         /**
256          * Delete all the duplicate points in the track
257          * @return number of points deleted
258          */
259         public int deleteDuplicates()
260         {
261                 // loop through Track counting duplicates first
262                 boolean[] dupes = new boolean[_numPoints];
263                 int numDupes = 0;
264                 int i, j;
265                 for (i=1; i<_numPoints; i++)
266                 {
267                         DataPoint p1 = _dataPoints[i];
268                         // Loop through all points before this one
269                         for (j=0; j<i && !dupes[i]; j++)
270                         {
271                                 DataPoint p2 = _dataPoints[j];
272                                 if (p1.isDuplicate(p2))
273                                 {
274                                         dupes[i] = true;
275                                         numDupes++;
276                                 }
277                         }
278                 }
279                 if (numDupes > 0)
280                 {
281                         // Make new resized array and copy DataPoints over
282                         DataPoint[] newPointArray = new DataPoint[_numPoints - numDupes];
283                         j = 0;
284                         for (i=0; i<_numPoints; i++)
285                         {
286                                 if (!dupes[i])
287                                 {
288                                         newPointArray[j] = _dataPoints[i];
289                                         j++;
290                                 }
291                         }
292                         // Copy array references
293                         _dataPoints = newPointArray;
294                         _numPoints = _dataPoints.length;
295                         _scaled = false;
296                         _broker.informSubscribers();
297                 }
298                 return numDupes;
299         }
300
301
302         /**
303          * Reverse the specified range of points
304          * @return true if successful, false otherwise
305          */
306         public boolean reverseRange(int inStart, int inEnd)
307         {
308                 if (inStart < 0 || inEnd < 0 || inStart >= inEnd || inEnd >= _numPoints)
309                 {
310                         return false;
311                 }
312                 // calculate how many point swaps are required
313                 int numPointsToReverse = (inEnd - inStart + 1) / 2;
314                 DataPoint p = null;
315                 for (int i=0; i<numPointsToReverse; i++)
316                 {
317                         // swap pairs of points
318                         p = _dataPoints[inStart + i];
319                         _dataPoints[inStart + i] = _dataPoints[inEnd - i];
320                         _dataPoints[inEnd - i] = p;
321                 }
322                 // needs to be scaled again
323                 _scaled = false;
324                 _broker.informSubscribers();
325                 return true;
326         }
327
328
329         /**
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
333          */
334         public boolean collectWaypoints(boolean inAtStart)
335         {
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++)
343                 {
344                         point = _dataPoints[i];
345                         if (point.isWaypoint())
346                         {
347                                 waypoints[numWaypoints] = point;
348                                 numWaypoints++;
349                                 wayAfterNon |= (numNonWaypoints > 0);
350                         }
351                         else
352                         {
353                                 nonWaypoints[numNonWaypoints] = point;
354                                 numNonWaypoints++;
355                                 nonAfterWay |= (numWaypoints > 0);
356                         }
357                 }
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))
362                 {
363                         return false;
364                 }
365
366                 // Copy the arrays back into _dataPoints in the specified order
367                 if (inAtStart)
368                 {
369                         System.arraycopy(waypoints, 0, _dataPoints, 0, numWaypoints);
370                         System.arraycopy(nonWaypoints, 0, _dataPoints, numWaypoints, numNonWaypoints);
371                 }
372                 else
373                 {
374                         System.arraycopy(nonWaypoints, 0, _dataPoints, 0, numNonWaypoints);
375                         System.arraycopy(waypoints, 0, _dataPoints, numNonWaypoints, numWaypoints);
376                 }
377                 // needs to be scaled again
378                 _scaled = false;
379                 _broker.informSubscribers();
380                 return true;
381         }
382
383
384         /**
385          * Interleave all waypoints by each nearest track point
386          * @return true if successful, false if no change
387          */
388         public boolean interleaveWaypoints()
389         {
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;
395                 int i = 0;
396                 for (i=0; i<_numPoints; i++)
397                 {
398                         point = _dataPoints[i];
399                         if (point.isWaypoint())
400                         {
401                                 waypoints[numWaypoints] = point;
402                                 pointIndices[numWaypoints] = getNearestPointIndex(
403                                         _xValues[i], _yValues[i], -1.0, true);
404                                 numWaypoints++;
405                         }
406                 }
407                 // Exit if data not mixed
408                 if (numWaypoints == 0 || numWaypoints == _numPoints)
409                         return false;
410
411                 // Loop round points copying to correct order
412                 DataPoint[] dataCopy = new DataPoint[_numPoints];
413                 int copyIndex = 0;
414                 for (i=0; i<_numPoints; i++)
415                 {
416                         point = _dataPoints[i];
417                         // if it's a track point, copy it
418                         if (!point.isWaypoint())
419                         {
420                                 dataCopy[copyIndex] = point;
421                                 copyIndex++;
422                         }
423                         // check for waypoints with this index
424                         for (int j=0; j<numWaypoints; j++)
425                         {
426                                 if (pointIndices[j] == i)
427                                 {
428                                         dataCopy[copyIndex] = waypoints[j];
429                                         copyIndex++;
430                                 }
431                         }
432                 }
433                 // Copy data back to track
434                 _dataPoints = dataCopy;
435                 // needs to be scaled again to recalc x, y
436                 _scaled = false;
437                 _broker.informSubscribers();
438                 return true;
439         }
440
441         // TODO: Need to rearrange photo points too?
442
443         /**
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
448          */
449         public boolean interpolate(int inStartIndex, int inNumPoints)
450         {
451                 // check parameters
452                 if (inStartIndex < 0 || inStartIndex >= _numPoints || inNumPoints <= 0)
453                         return false;
454
455                 // get start and end points
456                 DataPoint startPoint = getPoint(inStartIndex);
457                 DataPoint endPoint = getPoint(inStartIndex + 1);
458
459                 // Make array of points to insert
460                 DataPoint[] insertedPoints = startPoint.interpolate(endPoint, inNumPoints);
461
462                 // Insert points into track
463                 return insertRange(insertedPoints, inStartIndex + 1);
464         }
465
466
467         /**
468          * Append the specified points to the end of the track
469          * @param inPoints DataPoint objects to add
470          */
471         public void appendPoints(DataPoint[] inPoints)
472         {
473                 // Insert points into track
474                 if (inPoints != null && inPoints.length > 0)
475                 {
476                         insertRange(inPoints, _numPoints);
477                 }
478                 // needs to be scaled again to recalc x, y
479                 _scaled = false;
480                 _broker.informSubscribers();
481         }
482
483
484         //////// information methods /////////////
485
486
487         /**
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
491          */
492         public DataPoint getPoint(int inPointNum)
493         {
494                 if (inPointNum > -1 && inPointNum < getNumPoints())
495                 {
496                         return _dataPoints[inPointNum];
497                 }
498                 return null;
499         }
500
501
502         /**
503          * @return altitude range of points as AltitudeRange object
504          */
505         public AltitudeRange getAltitudeRange()
506         {
507                 if (!_scaled) scalePoints();
508                 return _altitudeRange;
509         }
510         /**
511          * @return the number of (valid) points in the track
512          */
513         public int getNumPoints()
514         {
515                 return _numPoints;
516         }
517
518         /**
519          * @return The range of x values as a DoubleRange object
520          */
521         public DoubleRange getXRange()
522         {
523                 if (!_scaled) scalePoints();
524                 return _xRange;
525         }
526
527         /**
528          * @return The range of y values as a DoubleRange object
529          */
530         public DoubleRange getYRange()
531         {
532                 if (!_scaled) scalePoints();
533                 return _yRange;
534         }
535
536         /**
537          * @param inPointNum point index, starting at 0
538          * @return scaled x value of specified point
539          */
540         public double getX(int inPointNum)
541         {
542                 if (!_scaled) scalePoints();
543                 return _xValues[inPointNum];
544         }
545
546         /**
547          * @param inPointNum point index, starting at 0
548          * @return scaled y value of specified point
549          */
550         public double getY(int inPointNum)
551         {
552                 if (!_scaled) scalePoints();
553                 return _yValues[inPointNum];
554         }
555
556         /**
557          * @return the master field list
558          */
559         public FieldList getFieldList()
560         {
561                 return _masterFieldList;
562         }
563
564
565         /**
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
569          */
570         public boolean hasData(Field inField)
571         {
572                 return hasData(inField, 0, _numPoints-1);
573         }
574
575
576         /**
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
582          */
583         public boolean hasData(Field inField, int inStart, int inEnd)
584         {
585                 for (int i=inStart; i<=inEnd; i++)
586                 {
587                         if (_dataPoints[i].getFieldValue(inField) != null)
588                         {
589                                 return true;
590                         }
591                 }
592                 return false;
593         }
594
595
596         /**
597          * @return true if track contains waypoints and trackpoints
598          */
599         public boolean hasMixedData()
600         {
601                 if (!_scaled) scalePoints();
602                 return _mixedData;
603         }
604
605
606         /**
607          * Collect all the waypoints into the given List
608          * @param inList List to fill with waypoints
609          */
610         public void getWaypoints(List inList)
611         {
612                 // clear list
613                 inList.clear();
614                 // loop over points and copy all waypoints into list
615                 for (int i=0; i<=_numPoints-1; i++)
616                 {
617                         if (_dataPoints[i].isWaypoint())
618                         {
619                                 inList.add(_dataPoints[i]);
620                         }
621                 }
622         }
623         // TODO: Make similar method to get list of photos
624
625
626         /**
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
630          */
631         public int getPointIndex(DataPoint inPoint)
632         {
633                 if (inPoint != null)
634                 {
635                         // Loop over points in track
636                         for (int i=0; i<=_numPoints-1; i++)
637                         {
638                                 if (_dataPoints[i] == inPoint)
639                                 {
640                                         return i;
641                                 }
642                         }
643                 }
644                 // not found
645                 return -1;
646         }
647
648
649         ///////// Internal processing methods ////////////////
650
651
652         /**
653          * Scale all the points in the track to gain x and y values
654          * ready for plotting
655          */
656         private void scalePoints()
657         {
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();
662                 int p;
663                 boolean hasWaypoint = false, hasTrackpoint = false;
664                 for (p=0; p < getNumPoints(); p++)
665                 {
666                         DataPoint point = getPoint(p);
667                         if (point != null && point.isValid())
668                         {
669                                 _longRange.addValue(point.getLongitude().getDouble());
670                                 _latRange.addValue(point.getLatitude().getDouble());
671                                 if (point.getAltitude().isValid())
672                                 {
673                                         _altitudeRange.addValue(point.getAltitude());
674                                 }
675                                 if (point.isWaypoint())
676                                         hasWaypoint = true;
677                                 else
678                                         hasTrackpoint = true;
679                         }
680                 }
681                 _mixedData = hasWaypoint && hasTrackpoint;
682
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
687
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++)
694                 {
695                         DataPoint point = getPoint(p);
696                         if (point != null)
697                         {
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]);
702                         }
703                 }
704                 _scaled = true;
705         }
706
707
708         /**
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
716          */
717         public int getNearestPointIndex(double inX, double inY, double inMaxDist, boolean inJustTrackPoints)
718         {
719                 int nearestPoint = 0;
720                 double nearestDist = -1.0;
721                 double currDist;
722                 for (int i=0; i < getNumPoints(); i++)
723                 {
724                         if (!inJustTrackPoints || !_dataPoints[i].isWaypoint())
725                         {
726                                 currDist = Math.abs(_xValues[i] - inX) + Math.abs(_yValues[i] - inY);
727                                 if (currDist < nearestDist || nearestDist < 0.0)
728                                 {
729                                         nearestPoint = i;
730                                         nearestDist = currDist;
731                                 }
732                         }
733                 }
734                 // Check whether it's within required distance
735                 if (nearestDist > inMaxDist && inMaxDist > 0.0)
736                 {
737                         return -1;
738                 }
739                 return nearestPoint;
740         }
741
742
743         ////////////////// Cloning and replacing ///////////////////
744
745         /**
746          * Clone the array of DataPoints
747          * @return shallow copy of DataPoint objects
748          */
749         public DataPoint[] cloneContents()
750         {
751                 DataPoint[] clone = new DataPoint[getNumPoints()];
752                 System.arraycopy(_dataPoints, 0, clone, 0, getNumPoints());
753                 return clone;
754         }
755
756
757         /**
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
762          */
763         public DataPoint[] cloneRange(int inStart, int inEnd)
764         {
765                 int numSelected = 0;
766                 if (inEnd >= 0 && inEnd >= inStart)
767                 {
768                         numSelected = inEnd - inStart + 1;
769                 }
770                 DataPoint[] result = new DataPoint[numSelected>0?numSelected:0];
771                 if (numSelected > 0)
772                 {
773                         System.arraycopy(_dataPoints, inStart, result, 0, numSelected);
774                 }
775                 return result;
776         }
777
778
779         /**
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
784          */
785         public boolean insertPoint(DataPoint inPoint, int inIndex)
786         {
787                 if (inIndex > _numPoints || inPoint == null)
788                 {
789                         return false;
790                 }
791                 // Make new array to copy points over to
792                 DataPoint[] newPointArray = new DataPoint[_numPoints + 1];
793                 if (inIndex > 0)
794                 {
795                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
796                 }
797                 newPointArray[inIndex] = inPoint;
798                 if (inIndex < _numPoints)
799                 {
800                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+1, _numPoints - inIndex);
801                 }
802                 // Change over to new array
803                 _dataPoints = newPointArray;
804                 _numPoints++;
805                 // needs to be scaled again
806                 _scaled = false;
807                 _broker.informSubscribers();
808                 return true;
809         }
810
811
812         /**
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
817          */
818         public boolean insertRange(DataPoint[] inPoints, int inIndex)
819         {
820                 if (inIndex > _numPoints || inPoints == null)
821                 {
822                         return false;
823                 }
824                 // Make new array to copy points over to
825                 DataPoint[] newPointArray = new DataPoint[_numPoints + inPoints.length];
826                 if (inIndex > 0)
827                 {
828                         System.arraycopy(_dataPoints, 0, newPointArray, 0, inIndex);
829                 }
830                 System.arraycopy(inPoints, 0, newPointArray, inIndex, inPoints.length);
831                 if (inIndex < _numPoints)
832                 {
833                         System.arraycopy(_dataPoints, inIndex, newPointArray, inIndex+inPoints.length, _numPoints - inIndex);
834                 }
835                 // Change over to new array
836                 _dataPoints = newPointArray;
837                 _numPoints += inPoints.length;
838                 // needs to be scaled again
839                 _scaled = false;
840                 _broker.informSubscribers();
841                 return true;
842         }
843
844
845         /**
846          * Replace the track contents with the given point array
847          * @param inContents array of DataPoint objects
848          */
849         public boolean replaceContents(DataPoint[] inContents)
850         {
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;
856                 _scaled = false;
857                 _broker.informSubscribers();
858                 return true;
859         }
860
861
862         /**
863          * Edit the specified point
864          * @param inPoint point to edit
865          * @param inEditList list of edits to make
866          * @return true if successful
867          */
868         public boolean editPoint(DataPoint inPoint, FieldEditList inEditList)
869         {
870                 if (inPoint != null && inEditList != null && inEditList.getNumEdits() > 0)
871                 {
872                         // go through edits one by one
873                         int numEdits = inEditList.getNumEdits();
874                         for (int i=0; i<numEdits; i++)
875                         {
876                                 FieldEdit edit = inEditList.getEdit(i);
877                                 inPoint.setFieldValue(edit.getField(), edit.getValue());
878                         }
879                         // possibly needs to be scaled again
880                         _scaled = false;
881                         // trigger listeners
882                         _broker.informSubscribers();
883                         return true;
884                 }
885                 return false;
886         }
887 }