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