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