]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/DataPoint.java
46c4acd9d1ad562883ed9d8a8830802d1f123c19
[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         public Coordinate getLatitude()
142         {
143                 return _latitude;
144         }
145         public Coordinate getLongitude()
146         {
147                 return _longitude;
148         }
149         public boolean hasAltitude()
150         {
151                 return _altitude.isValid();
152         }
153         public Altitude getAltitude()
154         {
155                 return _altitude;
156         }
157         public boolean hasTimestamp()
158         {
159                 return _timestamp.isValid();
160         }
161         public Timestamp getTimestamp()
162         {
163                 return _timestamp;
164         }
165         public String getWaypointName()
166         {
167                 return _waypointName;
168         }
169
170         /**
171          * @return true if point has a waypoint name
172          */
173         public boolean isWaypoint()
174         {
175                 return (_waypointName != null && !_waypointName.equals(""));
176         }
177
178
179         /**
180          * Compare two DataPoint objects to see if they are duplicates
181          * @param inOther other object to compare
182          * @return true if the points are equivalent
183          */
184         public boolean isDuplicate(DataPoint inOther)
185         {
186                 if (inOther == null) return false;
187                 if (_longitude == null || _latitude == null
188                         || inOther._longitude == null || inOther._latitude == null)
189                 {
190                         return false;
191                 }
192                 // Make sure photo points aren't specified as duplicates
193                 if (_photo != null) return false;
194                 // Compare latitude and longitude
195                 if (!_longitude.equals(inOther._longitude) || !_latitude.equals(inOther._latitude))
196                 {
197                         return false;
198                 }
199                 // Note that conversion from decimal to dms can make non-identical points into duplicates
200                 // Compare waypoint name (if any)
201                 if (!isWaypoint())
202                 {
203                         return !inOther.isWaypoint();
204                 }
205                 else
206                 {
207                         return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
208                 }
209         }
210
211
212         /**
213          * Set the photo for this data point
214          * @param inPhoto Photo object
215          */
216         public void setPhoto(Photo inPhoto)
217         {
218                 _photo = inPhoto;
219         }
220
221
222         /**
223          * @return associated Photo object
224          */
225         public Photo getPhoto()
226         {
227                 return _photo;
228         }
229
230
231         /**
232          * @return true if the point is valid
233          */
234         public boolean isValid()
235         {
236                 return _latitude.isValid() && _longitude.isValid();
237         }
238
239
240         /**
241          * Interpolate a set of points between this one and the given one
242          * @param inEndPoint end point of interpolation
243          * @param inNumPoints number of points to generate
244          * @return the DataPoint array
245          */
246         public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints)
247         {
248                 DataPoint[] range = new DataPoint[inNumPoints];
249                 Coordinate endLatitude = inEndPoint.getLatitude();
250                 Coordinate endLongitude = inEndPoint.getLongitude();
251                 Altitude endAltitude = inEndPoint.getAltitude();
252
253                 // Loop over points
254                 for (int i=0; i<inNumPoints; i++)
255                 {
256                         Coordinate latitude = Coordinate.interpolate(_latitude, endLatitude, i, inNumPoints);
257                         Coordinate longitude = Coordinate.interpolate(_longitude, endLongitude, i, inNumPoints);
258                         Altitude altitude = Altitude.interpolate(_altitude, endAltitude, i, inNumPoints);
259                         range[i] = new DataPoint(latitude, longitude, altitude);
260                 }
261                 return range;
262         }
263
264
265         /**
266          * Calculate the number of radians between two points (for distance calculation)
267          * @param inPoint1 first point
268          * @param inPoint2 second point
269          * @return angular distance between points in radians
270          */
271         public static double calculateRadiansBetween(DataPoint inPoint1, DataPoint inPoint2)
272         {
273                 if (inPoint1 == null || inPoint2 == null)
274                         return 0.0;
275                 final double TO_RADIANS = Math.PI / 180.0;
276                 // Get lat and long from points
277                 double lat1 = inPoint1.getLatitude().getDouble() * TO_RADIANS;
278                 double lat2 = inPoint2.getLatitude().getDouble() * TO_RADIANS;
279                 double lon1 = inPoint1.getLongitude().getDouble() * TO_RADIANS;
280                 double lon2 = inPoint2.getLongitude().getDouble() * TO_RADIANS;
281                 // Formula given by Wikipedia:Great-circle_distance as follows:
282                 // angle = 2 arcsin( sqrt( (sin ((lat2-lat1)/2))^^2 + cos(lat1)cos(lat2)(sin((lon2-lon1)/2))^^2))
283                 double firstSine = Math.sin((lat2-lat1) / 2.0);
284                 double secondSine = Math.sin((lon2-lon1) / 2.0);
285                 double term2 = Math.cos(lat1) * Math.cos(lat2) * secondSine * secondSine;
286                 double answer = 2 * Math.asin(Math.sqrt(firstSine*firstSine + term2));
287                 // phew
288                 return answer;
289         }
290
291
292         /**
293          * Resize the value array
294          * @param inNewIndex new index to allow
295          */
296         private void resizeValueArray(int inNewIndex)
297         {
298                 int newSize = inNewIndex + 1;
299                 if (newSize > _fieldValues.length)
300                 {
301                         String[] newArray = new String[newSize];
302                         System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length);
303                         _fieldValues = newArray;
304                 }
305         }
306
307
308         /**
309          * @return a clone object with copied data
310          */
311         public DataPoint clonePoint()
312         {
313                 // Copy all values
314                 String[] valuesCopy = new String[_fieldValues.length];
315                 System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
316                 // Make new object to hold cloned data
317                 DataPoint point = new DataPoint(valuesCopy, _fieldList, _altitude.getFormat());
318                 return point;
319         }
320
321
322         /**
323          * Restore the contents from another point
324          * @param inOther point containing contents to copy
325          * @return true if successful
326          */
327         public boolean restoreContents(DataPoint inOther)
328         {
329                 if (inOther != null)
330                 {
331                         // Copy string values across
332                         _fieldValues = inOther._fieldValues;
333                         if (_altitude != null)
334                         {
335                                 parseFields(_altitude.getFormat());
336                         }
337                         else
338                         {
339                                 // use default altitude format of metres
340                                 parseFields(Altitude.FORMAT_METRES);
341                         }
342                         return true;
343                 }
344                 return false;
345         }
346 }