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