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