1 package tim.prune.data;
3 import tim.prune.config.Config;
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
10 public class DataPoint
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 private int _modifyCount = 0;
28 * @param inValueArray array of String values
29 * @param inFieldList list of fields
30 * @param inAltFormat altitude format
32 public DataPoint(String[] inValueArray, FieldList inFieldList, Altitude.Format inAltFormat)
35 _fieldValues = inValueArray;
36 // save list of fields
37 _fieldList = inFieldList;
38 // parse fields into objects
39 parseFields(null, inAltFormat);
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
48 private void parseFields(Field inField, Altitude.Format inAltFormat)
50 if (inField == null || inField == Field.LATITUDE) {
51 _latitude = new Latitude(getFieldValue(Field.LATITUDE));
53 if (inField == null || inField == Field.LONGITUDE) {
54 _longitude = new Longitude(getFieldValue(Field.LONGITUDE));
56 if (inField == null || inField == Field.ALTITUDE) {
57 _altitude = new Altitude(getFieldValue(Field.ALTITUDE), inAltFormat);
59 if (inField == null || inField == Field.TIMESTAMP) {
60 _timestamp = new Timestamp(getFieldValue(Field.TIMESTAMP));
62 if (inField == null || inField == Field.WAYPT_NAME) {
63 _waypointName = getFieldValue(Field.WAYPT_NAME);
65 if (inField == null || inField == Field.NEW_SEGMENT)
67 String segmentStr = getFieldValue(Field.NEW_SEGMENT);
68 if (segmentStr != null) {segmentStr = segmentStr.trim();}
69 _startOfSegment = (segmentStr != null && (segmentStr.equals("1") || segmentStr.toUpperCase().equals("Y")));
75 * Constructor for additional points (eg interpolated, photos)
76 * @param inLatitude latitude
77 * @param inLongitude longitude
78 * @param inAltitude altitude
80 public DataPoint(Coordinate inLatitude, Coordinate inLongitude, Altitude inAltitude)
82 // Only these three fields are available
83 _fieldValues = new String[3];
84 Field[] fields = {Field.LATITUDE, Field.LONGITUDE, Field.ALTITUDE};
85 _fieldList = new FieldList(fields);
86 _latitude = inLatitude;
87 _fieldValues[0] = inLatitude.output(Coordinate.FORMAT_NONE);
88 _longitude = inLongitude;
89 _fieldValues[1] = inLongitude.output(Coordinate.FORMAT_NONE);
90 if (inAltitude == null) {
91 _altitude = Altitude.NONE;
94 _altitude = inAltitude;
95 _fieldValues[2] = "" + inAltitude.getValue();
97 _timestamp = new Timestamp(null);
102 * Get the value for the given field
103 * @param inField field to interrogate
104 * @return value of field
106 public String getFieldValue(Field inField)
108 return getFieldValue(_fieldList.getFieldIndex(inField));
113 * Get the value at the given index
114 * @param inIndex index number starting at zero
115 * @return field value, or null if not found
117 public String getFieldValue(int inIndex)
119 if (_fieldValues == null || inIndex < 0 || inIndex >= _fieldValues.length)
121 return _fieldValues[inIndex];
126 * Set (or edit) the specified field value
127 * @param inField Field to set
128 * @param inValue value to set
129 * @param inUndo true if undo operation, false otherwise
131 public void setFieldValue(Field inField, String inValue, boolean inUndo)
133 // See if this data point already has this field
134 int fieldIndex = _fieldList.getFieldIndex(inField);
135 // Add to field list if necessary
138 // If value is empty & field doesn't exist then do nothing
139 if (inValue == null || inValue.equals(""))
143 // value isn't empty so extend field list
144 fieldIndex = _fieldList.extendList(inField);
146 // Extend array of field values if necessary
147 if (fieldIndex >= _fieldValues.length)
149 resizeValueArray(fieldIndex);
151 // Set field value in array
152 _fieldValues[fieldIndex] = inValue;
153 // Increment edit count on all field edits except segment
154 if (inField != Field.NEW_SEGMENT) {
157 // Change Coordinate, Altitude, Name or Timestamp fields after edit
158 if (_altitude != null && _altitude.getFormat() != Altitude.Format.NO_FORMAT) {
159 // Altitude already present so reuse format
160 parseFields(inField, _altitude.getFormat());
163 // use default altitude format from config
164 parseFields(inField, Config.getConfigBoolean(Config.KEY_METRIC_UNITS)?Altitude.Format.METRES:Altitude.Format.FEET);
169 * Either increment or decrement the modify count, depending on whether it's an undo or not
170 * @param inUndo true for undo, false otherwise
172 public void setModified(boolean inUndo)
183 * @return field list for this point
185 public FieldList getFieldList()
190 /** @param inFlag true for start of track segment */
191 public void setSegmentStart(boolean inFlag)
193 setFieldValue(Field.NEW_SEGMENT, inFlag?"1":null, false);
197 * Mark the point for deletion
198 * @param inFlag true to delete, false to keep
200 public void setMarkedForDeletion(boolean inFlag) {
201 _markedForDeletion = inFlag;
204 /** @return latitude */
205 public Coordinate getLatitude()
209 /** @return longitude */
210 public Coordinate getLongitude()
214 /** @return true if point has altitude */
215 public boolean hasAltitude()
217 return _altitude.isValid();
219 /** @return altitude */
220 public Altitude getAltitude()
224 /** @return true if point has timestamp */
225 public boolean hasTimestamp()
227 return _timestamp.isValid();
229 /** @return timestamp */
230 public Timestamp getTimestamp()
234 /** @return waypoint name, if any */
235 public String getWaypointName()
237 return _waypointName;
240 /** @return true if start of new track segment */
241 public boolean getSegmentStart()
243 return _startOfSegment;
246 /** @return true if point marked for deletion */
247 public boolean getDeleteFlag()
249 return _markedForDeletion;
253 * @return true if point has a waypoint name
255 public boolean isWaypoint()
257 return (_waypointName != null && !_waypointName.equals(""));
261 * @return true if point has been modified since loading
263 public boolean isModified()
265 return _modifyCount > 0;
269 * Compare two DataPoint objects to see if they are duplicates
270 * @param inOther other object to compare
271 * @return true if the points are equivalent
273 public boolean isDuplicate(DataPoint inOther)
275 if (inOther == null) return false;
276 if (_longitude == null || _latitude == null
277 || inOther._longitude == null || inOther._latitude == null)
281 // Make sure photo points aren't specified as duplicates
282 if (_photo != null) return false;
283 // Compare latitude and longitude
284 if (!_longitude.equals(inOther._longitude) || !_latitude.equals(inOther._latitude))
288 // Note that conversion from decimal to dms can make non-identical points into duplicates
289 // Compare waypoint name (if any)
292 return !inOther.isWaypoint();
294 return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName));
299 * Set the photo for this data point
300 * @param inPhoto Photo object
302 public void setPhoto(Photo inPhoto)
309 * @return associated Photo object
311 public Photo getPhoto()
318 * @return true if the point is valid
320 public boolean isValid()
322 return _latitude.isValid() && _longitude.isValid();
326 * Interpolate a set of points between this one and the given one
327 * @param inEndPoint end point of interpolation
328 * @param inNumPoints number of points to generate
329 * @return the DataPoint array
331 public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints)
333 DataPoint[] range = new DataPoint[inNumPoints];
335 for (int i=0; i<inNumPoints; i++)
337 Coordinate latitude = Coordinate.interpolate(_latitude, inEndPoint.getLatitude(), i, inNumPoints);
338 Coordinate longitude = Coordinate.interpolate(_longitude, inEndPoint.getLongitude(), i, inNumPoints);
339 Altitude altitude = Altitude.interpolate(_altitude, inEndPoint.getAltitude(), i, inNumPoints);
340 range[i] = new DataPoint(latitude, longitude, altitude);
346 * Interpolate between the two given points
347 * @param inStartPoint start point
348 * @param inEndPoint end point
349 * @param inFrac fractional distance from first point (0.0 to 1.0)
350 * @return new DataPoint object between two given ones
352 public static DataPoint interpolate(DataPoint inStartPoint, DataPoint inEndPoint, double inFrac)
354 if (inStartPoint == null || inEndPoint == null) {return null;}
355 return new DataPoint(
356 Coordinate.interpolate(inStartPoint.getLatitude(), inEndPoint.getLatitude(), inFrac),
357 Coordinate.interpolate(inStartPoint.getLongitude(), inEndPoint.getLongitude(), inFrac),
358 Altitude.interpolate(inStartPoint.getAltitude(), inEndPoint.getAltitude(), inFrac)
363 * Calculate the number of radians between two points (for distance calculation)
364 * @param inPoint1 first point
365 * @param inPoint2 second point
366 * @return angular distance between points in radians
368 public static double calculateRadiansBetween(DataPoint inPoint1, DataPoint inPoint2)
370 if (inPoint1 == null || inPoint2 == null)
372 final double TO_RADIANS = Math.PI / 180.0;
373 // Get lat and long from points
374 double lat1 = inPoint1.getLatitude().getDouble() * TO_RADIANS;
375 double lat2 = inPoint2.getLatitude().getDouble() * TO_RADIANS;
376 double lon1 = inPoint1.getLongitude().getDouble() * TO_RADIANS;
377 double lon2 = inPoint2.getLongitude().getDouble() * TO_RADIANS;
378 // Formula given by Wikipedia:Great-circle_distance as follows:
379 // angle = 2 arcsin( sqrt( (sin ((lat2-lat1)/2))^^2 + cos(lat1)cos(lat2)(sin((lon2-lon1)/2))^^2))
380 double firstSine = Math.sin((lat2-lat1) / 2.0);
381 double secondSine = Math.sin((lon2-lon1) / 2.0);
382 double term2 = Math.cos(lat1) * Math.cos(lat2) * secondSine * secondSine;
383 double answer = 2 * Math.asin(Math.sqrt(firstSine*firstSine + term2));
390 * Resize the value array
391 * @param inNewIndex new index to allow
393 private void resizeValueArray(int inNewIndex)
395 int newSize = inNewIndex + 1;
396 if (newSize > _fieldValues.length)
398 String[] newArray = new String[newSize];
399 System.arraycopy(_fieldValues, 0, newArray, 0, _fieldValues.length);
400 _fieldValues = newArray;
406 * @return a clone object with copied data
408 public DataPoint clonePoint()
410 // Copy all values (note that photo not copied)
411 String[] valuesCopy = new String[_fieldValues.length];
412 System.arraycopy(_fieldValues, 0, valuesCopy, 0, _fieldValues.length);
413 // Make new object to hold cloned data
414 DataPoint point = new DataPoint(valuesCopy, _fieldList, _altitude.getFormat());
420 * Get string for debug
421 * @see java.lang.Object#toString()
423 public String toString()
425 return "[Lat=" + getLatitude().toString() + ", Lon=" + getLongitude().toString() + "]";