]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/DataPoint.java
Version 11, August 2010
[GpsPrune.git] / tim / prune / data / DataPoint.java
1 package tim.prune.data;
2
3 import tim.prune.config.Config;
4
5 /**
6  * Class to represent a single data point in the series
7  * including all its fields
8  * Can be either a track point or a waypoint
9  */
10 public class DataPoint
11 {
12         /** Array of Strings holding raw values */
13         private String[] _fieldValues = null;
14         /** list of field definitions */
15         private FieldList _fieldList = null;
16         /** Special fields for coordinates */
17         private Coordinate _latitude = null, _longitude = null;
18         private Altitude _altitude;
19         private Timestamp _timestamp = null;
20         private Photo _photo = null;
21         private String _waypointName = null;
22         private boolean _startOfSegment = false;
23         private boolean _markedForDeletion = false;
24         private int _modifyCount = 0;
25
26         /**
27          * Constructor
28          * @param inValueArray array of String values
29          * @param inFieldList list of fields
30          * @param inAltFormat altitude format
31          */
32         public DataPoint(String[] inValueArray, FieldList inFieldList, Altitude.Format inAltFormat)
33         {
34                 // save data
35                 _fieldValues = inValueArray;
36                 // save list of fields
37                 _fieldList = inFieldList;
38                 // parse fields into objects
39                 parseFields(null, inAltFormat);
40         }
41
42
43         /**
44          * Parse the string values into objects eg Coordinates
45          * @param inField field which has changed, or null for all
46          * @param inAltFormat altitude format
47          */
48         private void parseFields(Field inField, Altitude.Format inAltFormat)
49         {
50                 if (inField == null || inField == Field.LATITUDE) {
51                         _latitude = new Latitude(getFieldValue(Field.LATITUDE));
52                 }
53                 if (inField == null || inField == Field.LONGITUDE) {
54                         _longitude = new Longitude(getFieldValue(Field.LONGITUDE));
55                 }
56                 if (inField == null || inField == Field.ALTITUDE) {
57                         _altitude = new Altitude(getFieldValue(Field.ALTITUDE), inAltFormat);
58                 }
59                 if (inField == null || inField == Field.TIMESTAMP) {
60                         _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
61                 }
62                 if (inField == null || inField == Field.WAYPT_NAME) {
63                         _waypointName = getFieldValue(Field.WAYPT_NAME);
64                 }
65                 if (inField == null || inField == Field.NEW_SEGMENT)
66                 {
67                         String segmentStr = getFieldValue(Field.NEW_SEGMENT);
68                         if (segmentStr != null) {segmentStr = segmentStr.trim();}
69                         _startOfSegment = (segmentStr != null && (segmentStr.equals("1") || segmentStr.toUpperCase().equals("Y")));
70                 }
71         }
72
73
74         /**
75          * Constructor for additional points (eg interpolated, photos)
76          * @param inLatitude latitude
77          * @param inLongitude longitude
78          * @param inAltitude altitude
79          */
80         public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude)
81         {
82                 // Only these three fields are available
83                 _fieldValues = new String[3];
84                 Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
85                 _fieldList = new FieldList(fields);
86                 _latitude = inLatitude;
87                 _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_NONE);
88                 _longitude = inLongitude;
89                 _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_NONE);
90                 if (inAltitude == null) {
91                         _altitude = Altitude.NONE;
92                 }
93                 else {
94                         _altitude = inAltitude;
95                         _fieldValues[2] = "" + inAltitude.getValue();
96                 }
97                 _timestamp = new Timestamp(null);
98         }
99
100
101         /**
102          * Get the value for the given field
103          * @param inField field to interrogate
104          * @return value of field
105          */
106         public String getFieldValue(Field inField)
107         {
108                 return getFieldValue(_fieldList.getFieldIndex(inField));
109         }
110
111
112         /**
113          * Get the value at the given index
114          * @param inIndex index number starting at zero
115          * @return field value, or null if not found
116          */
117         public String getFieldValue(int inIndex)
118         {
119                 if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length)
120                         return null;
121                 return _fieldValues[inIndex];
122         }
123
124
125         /**
126          * Set (or edit) the specified field value
127          * @param inField Field to set
128          * @param inValue value to set
129          * @param inUndo true if undo operation, false otherwise
130          */
131         public void setFieldValue(Field inField, String inValue, boolean inUndo)
132         {
133                 // See if this data point already has this field
134                 int fieldIndex = _fieldList.getFieldIndex(inField);
135                 // Add to field list if necessary
136                 if (fieldIndex < 0)
137                 {
138                         // If value is empty & field doesn't exist then do nothing
139                         if (inValue == null || inValue.equals(""))
140                         {
141                                 return;
142                         }
143                         // value isn't empty so extend field list
144                         fieldIndex = _fieldList.extendList(inField);
145                 }
146                 // Extend array of field values if necessary
147                 if (fieldIndex >= _fieldValues.length)
148                 {
149                         resizeValueArray(fieldIndex);
150                 }
151                 // Set field value in array
152                 _fieldValues[fieldIndex] = inValue;
153                 // Increment edit count on all field edits except segment
154                 if (inField != Field.NEW_SEGMENT) {
155                         setModified(inUndo);
156                 }
157                 // Change Coordinate, Altitude, Name or Timestamp fields after edit
158                 if (_altitude != null && _altitude.getFormat() != Altitude.Format.NO_FORMAT) {
159                         // Altitude already present so reuse format
160                         parseFields(inField, _altitude.getFormat());
161                 }
162                 else {
163                         // use default altitude format from config
164                         parseFields(inField, Config.getConfigBoolean(Config.KEY_METRIC_UNITS)?Altitude.Format.METRES:Altitude.Format.FEET);
165                 }
166         }
167
168         /**
169          * Either increment or decrement the modify count, depending on whether it's an undo or not
170          * @param inUndo true for undo, false otherwise
171          */
172         public void setModified(boolean inUndo)
173         {
174                 if (!inUndo) {
175                         _modifyCount++;
176                 }
177                 else {
178                         _modifyCount--;
179                 }
180         }
181
182         /**
183          * @return field list for this point
184          */
185         public FieldList getFieldList()
186         {
187                 return _fieldList;
188         }
189
190         /** @param inFlag true for start of track segment */
191         public void setSegmentStart(boolean inFlag)
192         {
193                 setFieldValue(Field.NEW_SEGMENT, inFlag?"1":null, false);
194         }
195
196         /**
197          * Mark the point for deletion
198          * @param inFlag true to delete, false to keep
199          */
200         public void setMarkedForDeletion(boolean inFlag) {
201                 _markedForDeletion = inFlag;
202         }
203
204         /** @return latitude */
205         public Coordinate getLatitude()
206         {
207                 return _latitude;
208         }
209         /** @return longitude */
210         public Coordinate getLongitude()
211         {
212                 return _longitude;
213         }
214         /** @return true if point has altitude */
215         public boolean hasAltitude()
216         {
217                 return _altitude.isValid();
218         }
219         /** @return altitude */
220         public Altitude getAltitude()
221         {
222                 return _altitude;
223         }
224         /** @return true if point has timestamp */
225         public boolean hasTimestamp()
226         {
227                 return _timestamp.isValid();
228         }
229         /** @return timestamp */
230         public Timestamp getTimestamp()
231         {
232                 return _timestamp;
233         }
234         /** @return waypoint name, if any */
235         public String getWaypointName()
236         {
237                 return _waypointName;
238         }
239
240         /** @return true if start of new track segment */
241         public boolean getSegmentStart()
242         {
243                 return _startOfSegment;
244         }
245
246         /** @return true if point marked for deletion */
247         public boolean getDeleteFlag()
248         {
249                 return _markedForDeletion;
250         }
251
252         /**
253          * @return true if point has a waypoint name
254          */
255         public boolean isWaypoint()
256         {
257                 return (_waypointName != null && !_waypointName.equals(""));
258         }
259
260         /**
261          * @return true if point has been modified since loading
262          */
263         public boolean isModified()
264         {
265                 return _modifyCount > 0;
266         }
267
268         /**
269          * Compare two DataPoint objects to see if they are duplicates
270          * @param inOther other object to compare
271          * @return true if the points are equivalent
272          */
273         public boolean isDuplicate(DataPoint inOther)
274         {
275                 if (inOther == null) return false;
276                 if (_longitude == null || _latitude == null
277                         || inOther._longitude == null || inOther._latitude == null)
278                 {
279                         return false;
280                 }
281                 // Make sure photo points aren't specified as duplicates
282                 if (_photo != null) return false;
283                 // Compare latitude and longitude
284                 if (!_longitude.equals(inOther._longitude) || !_latitude.equals(inOther._latitude))
285                 {
286                         return false;
287                 }
288                 // Note that conversion from decimal to dms can make non-identical points into duplicates
289                 // Compare waypoint name (if any)
290                 if (!isWaypoint())
291                 {
292                         return !inOther.isWaypoint();
293                 }
294                 return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
295         }
296
297
298         /**
299          * Set the photo for this data point
300          * @param inPhoto Photo object
301          */
302         public void setPhoto(Photo inPhoto)
303         {
304                 _photo = inPhoto;
305         }
306
307
308         /**
309          * @return associated Photo object
310          */
311         public Photo getPhoto()
312         {
313                 return _photo;
314         }
315
316
317         /**
318          * @return true if the point is valid
319          */
320         public boolean isValid()
321         {
322                 return _latitude.isValid() && _longitude.isValid();
323         }
324
325         /**
326          * Interpolate a set of points between this one and the given one
327          * @param inEndPoint end point of interpolation
328          * @param inNumPoints number of points to generate
329          * @return the DataPoint array
330          */
331         public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints)
332         {
333                 DataPoint[] range = new DataPoint[inNumPoints];
334                 // Loop over points
335                 for (int i=0; i<inNumPoints; i++)
336                 {
337                         Coordinate latitude = Coordinate.interpolate(_latitude, inEndPoint.getLatitude(), i, inNumPoints);
338                         Coordinate longitude = Coordinate.interpolate(_longitude, inEndPoint.getLongitude(), i, inNumPoints);
339                         Altitude altitude = Altitude.interpolate(_altitude, inEndPoint.getAltitude(), i, inNumPoints);
340                         range[i] = new DataPoint(latitude, longitude, altitude);
341                 }
342                 return range;
343         }
344
345         /**
346          * Interpolate between the two given points
347          * @param inStartPoint start point
348          * @param inEndPoint end point
349          * @param inFrac fractional distance from first point (0.0 to 1.0)
350          * @return new DataPoint object between two given ones
351          */
352         public static DataPoint interpolate(DataPoint inStartPoint, DataPoint inEndPoint, double inFrac)
353         {
354                 if (inStartPoint == null || inEndPoint == null) {return null;}
355                 return new DataPoint(
356                         Coordinate.interpolate(inStartPoint.getLatitude(), inEndPoint.getLatitude(), inFrac),
357                         Coordinate.interpolate(inStartPoint.getLongitude(), inEndPoint.getLongitude(), inFrac),
358                         Altitude.interpolate(inStartPoint.getAltitude(), inEndPoint.getAltitude(), inFrac)
359                 );
360         }
361
362         /**
363          * Calculate the number of radians between two points (for distance calculation)
364          * @param inPoint1 first point
365          * @param inPoint2 second point
366          * @return angular distance between points in radians
367          */
368         public static double calculateRadiansBetween(DataPoint inPoint1, DataPoint inPoint2)
369         {
370                 if (inPoint1 == null || inPoint2 == null)
371                         return 0.0;
372                 final double TO_RADIANS = Math.PI / 180.0;
373                 // Get lat and long from points
374                 double lat1 = inPoint1.getLatitude().getDouble() * TO_RADIANS;
375                 double lat2 = inPoint2.getLatitude().getDouble() * TO_RADIANS;
376                 double lon1 = inPoint1.getLongitude().getDouble() * TO_RADIANS;
377                 double lon2 = inPoint2.getLongitude().getDouble() * TO_RADIANS;
378                 // Formula given by Wikipedia:Great-circle_distance as follows:
379                 // angle = 2 arcsin( sqrt( (sin ((lat2-lat1)/2))^^2 + cos(lat1)cos(lat2)(sin((lon2-lon1)/2))^^2))
380                 double firstSine = Math.sin((lat2-lat1) / 2.0);
381                 double secondSine = Math.sin((lon2-lon1) / 2.0);
382                 double term2 = Math.cos(lat1) * Math.cos(lat2) * secondSine * secondSine;
383                 double answer = 2 * Math.asin(Math.sqrt(firstSine*firstSine + term2));
384                 // phew
385                 return answer;
386         }
387
388
389         /**
390          * Resize the value array
391          * @param inNewIndex new index to allow
392          */
393         private void resizeValueArray(int inNewIndex)
394         {
395                 int newSize = inNewIndex + 1;
396                 if (newSize > _fieldValues.length)
397                 {
398                         String[] newArray = new String[newSize];
399                         System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length);
400                         _fieldValues = newArray;
401                 }
402         }
403
404
405         /**
406          * @return a clone object with copied data
407          */
408         public DataPoint clonePoint()
409         {
410                 // Copy all values (note that photo not copied)
411                 String[] valuesCopy = new String[_fieldValues.length];
412                 System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
413                 // Make new object to hold cloned data
414                 DataPoint point = new DataPoint(valuesCopy, _fieldList, _altitude.getFormat());
415                 return point;
416         }
417
418
419         /**
420          * Get string for debug
421          * @see java.lang.Object#toString()
422          */
423         public String toString()
424         {
425                 return "[Lat=" + getLatitude().toString() + ", Lon=" + getLongitude().toString() + "]";
426         }
427 }