1 package tim.prune.data;
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
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;
25 * @param inValueArray array of String values
26 * @param inFieldList list of fields
27 * @param inAltFormat altitude format
29 public DataPoint(String[] inValueArray, FieldList inFieldList, int inAltFormat)
32 _fieldValues = inValueArray;
33 // save list of fields
34 _fieldList = inFieldList;
35 // parse fields into objects
36 parseFields(inAltFormat);
41 * Parse the string values into objects eg Coordinates
42 * @param inAltFormat altitude format
44 private void parseFields(int inAltFormat)
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")));
58 * Constructor for additional points (eg interpolated, photos)
59 * @param inLatitude latitude
60 * @param inLongitude longitude
61 * @param inAltitude altitude
63 public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude)
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;
77 _altitude = inAltitude;
78 _fieldValues[2] = "" + inAltitude.getValue(Altitude.FORMAT_METRES); // units are ignored
80 _timestamp = new Timestamp(null);
85 * Get the value for the given field
86 * @param inField field to interrogate
87 * @return value of field
89 public String getFieldValue(Field inField)
91 return getFieldValue(_fieldList.getFieldIndex(inField));
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
100 public String getFieldValue(int inIndex)
102 if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length)
104 return _fieldValues[inIndex];
109 * Set (or edit) the specified field value
110 * @param inField Field to set
111 * @param inValue value to set
113 public void setFieldValue(Field inField, String inValue)
115 // See if this data point already has this field
116 int fieldIndex = _fieldList.getFieldIndex(inField);
117 // Add to field list if necessary
120 // If value is empty & field doesn't exist then do nothing
121 if (inValue == null || inValue.equals(""))
125 // value isn't empty so extend field list
126 fieldIndex = _fieldList.extendList(inField);
128 // Extend array of field values if necessary
129 if (fieldIndex >= _fieldValues.length)
131 resizeValueArray(fieldIndex);
133 // Set field value in array
134 _fieldValues[fieldIndex] = inValue;
135 // Change Coordinate, Altitude, Name or Timestamp fields after edit
136 if (_altitude != null)
138 parseFields(_altitude.getFormat());
142 // use default altitude format of metres
143 parseFields(Altitude.FORMAT_METRES);
147 /** @param inFlag true for start of track segment */
148 public void setSegmentStart(boolean inFlag)
150 setFieldValue(Field.NEW_SEGMENT, inFlag?"1":null);
153 /** @return latitude */
154 public Coordinate getLatitude()
158 /** @return longitude */
159 public Coordinate getLongitude()
163 /** @return true if point has altitude */
164 public boolean hasAltitude()
166 return _altitude.isValid();
168 /** @return altitude */
169 public Altitude getAltitude()
173 /** @return true if point has timestamp */
174 public boolean hasTimestamp()
176 return _timestamp.isValid();
178 /** @return timestamp */
179 public Timestamp getTimestamp()
183 /** @return waypoint name, if any */
184 public String getWaypointName()
186 return _waypointName;
189 /** @return true if start of new track segment */
190 public boolean getSegmentStart()
192 return _startOfSegment;
196 * @return true if point has a waypoint name
198 public boolean isWaypoint()
200 return (_waypointName != null && !_waypointName.equals(""));
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
209 public boolean isDuplicate(DataPoint inOther)
211 if (inOther == null) return false;
212 if (_longitude == null || _latitude == null
213 || inOther._longitude == null || inOther._latitude == null)
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))
224 // Note that conversion from decimal to dms can make non-identical points into duplicates
225 // Compare waypoint name (if any)
228 return !inOther.isWaypoint();
230 return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
235 * Set the photo for this data point
236 * @param inPhoto Photo object
238 public void setPhoto(Photo inPhoto)
245 * @return associated Photo object
247 public Photo getPhoto()
254 * @return true if the point is valid
256 public boolean isValid()
258 return _latitude.isValid() && _longitude.isValid();
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
268 public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints)
270 DataPoint[] range = new DataPoint[inNumPoints];
272 for (int i=0; i<inNumPoints; i++)
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);
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
289 public static DataPoint interpolate(DataPoint inStartPoint, DataPoint inEndPoint, double inFrac)
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)
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
305 public static double calculateRadiansBetween(DataPoint inPoint1, DataPoint inPoint2)
307 if (inPoint1 == null || inPoint2 == null)
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));
327 * Resize the value array
328 * @param inNewIndex new index to allow
330 private void resizeValueArray(int inNewIndex)
332 int newSize = inNewIndex + 1;
333 if (newSize > _fieldValues.length)
335 String[] newArray = new String[newSize];
336 System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length);
337 _fieldValues = newArray;
343 * @return a clone object with copied data
345 public DataPoint clonePoint()
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());
357 * Restore the contents from another point
358 * @param inOther point containing contents to copy
359 * @return true if successful
361 public boolean restoreContents(DataPoint inOther)
365 // Copy string values across
366 _fieldValues = inOther._fieldValues;
367 if (_altitude != null)
369 parseFields(_altitude.getFormat());
373 // use default altitude format of metres
374 parseFields(Altitude.FORMAT_METRES);
383 * Get string for debug
384 * @see java.lang.Object#toString()
386 public String toString()
388 return "[Lat=" + getLatitude().toString() + ", Lon=" + getLongitude().toString() + "]";