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