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